We actively provide security updates for the following versions of polyglot-i18n:
| Version | Supported | End of Life |
|---|---|---|
| 0.16.x | ✅ | - |
| 0.15.x | ✅ | TBD |
| 0.14.x | 2025-12-31 | |
| < 0.14.0 | ❌ | EOL |
If you discover a security vulnerability in polyglot-i18n, please report it responsibly:
Preferred Method:
- GitHub Security Advisory: https://github.com/hyperpolymath/polyglot-i18n/security/advisories/new
Alternative Method:
- Open a private security advisory on GitHub
Please include the following information in your report:
- Description of the vulnerability
- Steps to reproduce the issue
- Potential impact and attack scenarios
- Affected versions (if known)
- Suggested fix (if you have one)
- Your contact information for follow-up
- Acknowledgment: Within 48 hours of report
- Initial Assessment: Within 7 days
- Fix Timeline:
- Critical: 7-14 days
- High: 14-30 days
- Medium: 30-60 days
- Low: Next planned release
We follow coordinated disclosure:
- Vulnerability reported to maintainers
- Fix developed and tested (private)
- Security advisory prepared
- Fix released with CVE (if applicable)
- Public disclosure 7 days after fix release
// ❌ DANGEROUS - Arbitrary file system access
const userInput = req.query.key; // Could be "../../../etc/passwd"
const translation = res.__(userInput);
// ✅ SAFE - Whitelist approach
const allowedKeys = ['welcome', 'goodbye', 'hello'];
const key = allowedKeys.includes(req.query.key) ? req.query.key : 'welcome';
const translation = res.__(key);Risk: Directory traversal, arbitrary file reads/writes, catalog pollution
// ❌ DANGEROUS - XSS vulnerability
const username = req.query.name; // Could be "<script>alert('XSS')</script>"
const message = res.__('Welcome {{name}}', { name: username });
// Output: Welcome <script>alert('XSS')</script>
// ✅ SAFE - Escape HTML
const escapeHtml = (str) => str.replace(/[&<>"']/g, (m) => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
})[m]);
const username = escapeHtml(req.query.name);
const message = res.__('Welcome {{name}}', { name: username });
// ✅ BETTER - Use templating engine with auto-escaping
// In EJS: <%= __('Welcome {{name}}', { name: username }) %>
// In Handlebars: {{__ 'Welcome {{name}}' name=username}}// ❌ DANGEROUS - Allows raw HTML injection
const userBio = req.body.bio; // Could contain malicious HTML
const message = res.__('Bio: {{{bio}}}', { bio: userBio });
// ✅ SAFE - Use double mustache (escaped)
const message = res.__('Bio: {{bio}}', { bio: userBio });
// ✅ ACCEPTABLE - Only if you control the content
const trustedHTML = '<strong>Admin</strong>';
const message = res.__('User: {{{role}}}', { role: trustedHTML });// ✅ SAFE Configuration
i18n.configure({
directory: path.resolve(__dirname, 'locales'), // Use absolute paths
updateFiles: false, // Disable in production
autoReload: false, // Disable in production
directoryPermissions: '755', // Restrictive permissions
// Validate locale names to prevent traversal
locales: ['en', 'de', 'fr'] // Explicit whitelist
});
// ❌ DANGEROUS - Production settings
i18n.configure({
updateFiles: true, // Allows arbitrary file writes
autoReload: true, // Performance + security risk
locales: undefined // Auto-detects from directory (risky)
});// ❌ DANGEROUS - Arbitrary locale setting
app.get('/set-locale/:locale', (req, res) => {
req.setLocale(req.params.locale); // Could be "../../../etc/passwd"
res.redirect('/');
});
// ✅ SAFE - Validate against allowed locales
app.get('/set-locale/:locale', (req, res) => {
const locale = req.params.locale;
const allowedLocales = i18n.getLocales();
if (allowedLocales.includes(locale)) {
req.setLocale(locale);
} else {
// Log potential attack
console.warn(`Invalid locale attempted: ${locale}`);
req.setLocale(i18n.getLocale()); // Keep current locale
}
res.redirect('/');
});// ❌ DANGEROUS - User controls MessageFormat syntax
const userFormat = req.body.format; // "{x, select, , {,}} ..." - malformed
const message = res.__mf(userFormat, { x: 'test' }); // May crash
// ✅ SAFE - Only use predefined MessageFormat strings
const formats = {
items: '{N, plural, one{# item} other{# items}}',
users: '{N, plural, one{# user} other{# users}}'
};
const formatKey = req.query.type;
const format = formats[formatKey] || formats.items;
const message = res.__mf(format, { N: count });✅ Recommended cookie configuration:
// Set locale cookie securely
res.cookie('locale', locale, {
httpOnly: true, // Prevents XSS access
secure: true, // HTTPS only (production)
sameSite: 'strict', // CSRF protection
maxAge: 31536000000, // 1 year
signed: true // Requires cookie-parser secret
});
// Express middleware
app.use(cookieParser('your-secret-key-here')); // Use strong secret
i18n.configure({
cookie: 'locale'
});// ❌ POTENTIALLY DANGEROUS with objectNotation: true
const key = req.query.key; // Could be "__proto__.polluted"
const value = res.__(key);
// ✅ SAFE - Disable object notation if using user input
i18n.configure({
objectNotation: false // Disable if keys come from user input
});
// ✅ SAFE - Validate keys
const allowedPattern = /^[a-z0-9._-]+$/i;
if (!allowedPattern.test(key)) {
throw new Error('Invalid translation key');
}// ✅ Recommendations:
// 1. Limit locale file sizes (< 1MB recommended)
// 2. Use staticCatalog in production (faster, safer)
// 3. Rate-limit locale switching endpoints
// 4. Cache translations in memory (default behavior)
// Production configuration
i18n.configure({
staticCatalog: {
en: require('./locales/en.json'),
de: require('./locales/de.json')
},
updateFiles: false,
autoReload: false
});
// Rate limiting for locale switching
const rateLimit = require('express-rate-limit');
app.use('/set-locale', rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10 // 10 requests per window
}));Keep dependencies updated:
# Check for vulnerabilities
npm audit
# Fix automatically
npm audit fix
# Update dependencies
npm update
# Check outdated packages
npm outdated- Set
updateFiles: falsein production - Set
autoReload: falsein production - Use
staticCatalogfor better performance and security - Validate all user input before using in translations
- Escape HTML in translation replacements
- Use absolute paths for
directoryconfiguration - Whitelist allowed locales, validate locale switching
- Use secure cookie settings (httpOnly, secure, sameSite)
- Never use user input as translation keys
- Keep dependencies updated (
npm audit) - Disable object notation if keys come from user input
- Rate-limit locale switching endpoints
- Use Content Security Policy (CSP) headers
- Regularly review and sanitize translation files
- Monitor for suspicious locale file changes
- CVE-2025-57353 - @messageformat/runtime Prototype Pollution
- Status: Fixed in v0.15.3
- Action: Updated @messageformat/runtime to 3.0.2
- Severity: Medium
We continuously monitor:
- Dependency vulnerabilities via npm audit
- Security advisories for MessageFormat and Mustache
- Community-reported issues
When using i18n in web applications, implement these security headers:
// Express helmet middleware
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"], // No inline scripts
styleSrc: ["'self'", "'unsafe-inline'"] // For dynamic styles only
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));- OWASP Top 10
- OWASP i18n Security Cheat Sheet
- Node.js Security Best Practices
- Express Security Best Practices
For security-related questions or concerns:
- Security Issues: Use GitHub Security Advisory or email security contact
- General Questions: Open a GitHub issue
- Commercial Support: Contact through Tidelift
Last Updated: 2025-12-07