Prototype pollution — атака, при которой злоумышленник модифицирует
Object.prototype или Array.prototype, добавляя
свойства, которые наследуют все объекты. Применительно к timeago.js риск
невысок, но защита важна при обработке недоверенных данных.
// Атакующий добавляет свойство в прототип
const malicious = JSON.parse('{"__proto__": {"polluted": true}}');
const innocent = {};
console.log(innocent.polluted); // true — объект "заражён"
Если такой объект попадает в конфигурацию timeago.js или в данные,
передаваемые в format, это может привести к неожиданному
поведению.
timeago.js не обрабатывает произвольные объекты конфигурации из внешних источников. Основной риск — в коде, который:
Object.assign или spread с данными из
API.// Небезопасно — прямое слияние из внешнего источника
const userConfig = JSON.parse(req.body.config);
const config = { ...defaultConfig, ...userConfig }; // Может быть polluted
// Безопасно — белый список ключей
function sanitizeConfig(raw) {
const allowed = ['locale', 'fallback', 'updateInterval'];
const config = Object.create(null); // Без прототипа
allowed.forEach(key => {
if (Object.prototype.hasOwnProperty.call(raw, key)) {
config[key] = raw[key];
}
});
return config;
}
// Небезопасно — словарь с прототипом
const localeMap = {};
// Безопасно — словарь без прототипа
const localeMap = Object.create(null);
localeMap['ru'] = ruLocaleFunc;
localeMap['de'] = deLocaleFunc;
// Проверка
'ru' in localeMap; // true
'toString' in localeMap; // false — нет унаследованных свойств
// Небезопасно — проверяет прототип
if ('locale' in config) { /* ... */ }
// Безопасно — только собственные свойства
if (Object.prototype.hasOwnProperty.call(config, 'locale')) { /* ... */ }
// Или в ES2022:
if (Object.hasOwn(config, 'locale')) { /* ... */ }
class SafeLocaleRegistry {
// Map не подвержена prototype pollution
private locales = new Map<string, LocaleFunc>();
register(code: string, fn: LocaleFunc): void {
// Дополнительная проверка: только безопасные ключи
if (!/^[a-zA-Z_]{2,10}$/.test(code)) {
throw new TypeError(`Invalid locale code: "${code}"`);
}
this.locales.set(code, fn);
}
get(code: string): LocaleFunc | undefined {
return this.locales.get(code);
}
}
Map не наследует от Object.prototype — она
не подвержена prototype pollution.
const CONFIG = Object.freeze({
locale: 'ru',
updateInterval: 60_000,
batchSize: 50,
});
// CONFIG.locale = 'de'; // TypeError в strict mode
Замороженный объект нельзя изменить даже через pollution.
const SAFE_LOCALES = new Set(['ru', 'en_US', 'de', 'fr', 'zh_CN', 'ja', 'ko']);
function safeFormat(date, locale) {
// Только известные значения
const safeLocale = SAFE_LOCALES.has(locale) ? locale : 'ru';
return format(date, safeLocale);
}
npm install @fastify/secure-json-parse
import { parse } from '@fastify/secure-json-parse';
// Парсинг без опасности pollution
const data = parse('{"__proto__": {"polluted": true}}');
console.log({}.polluted); // undefined — защита сработала
npm audit
Следить за moderate и critical уязвимостями
в зависимостях timeago.js:
npm audit --audit-level=high
| Мера | Применение |
|---|---|
Object.create(null) для словарей |
Внутренние карты locale, конфиги |
Map вместо объектов |
Реестры, кеши |
Object.hasOwn вместо in |
Проверка наличия свойств |
| Белый список ключей конфига | При слиянии с внешними данными |
Object.freeze для констант |
Неизменяемые конфиги |
| Валидация locale по whitelist | Перед передачей в timeago.js |
@fastify/secure-json-parse |
Парсинг JSON из недоверенных источников |