Валидация входных данных в JavaScript через JSON Schema и библиотеку Ajv часто воспринимается как чисто прикладная задача: проверить структуру объекта и пропустить его дальше по пайплайну. Однако сама схема валидации может становиться источником уязвимостей класса DoS (Denial of Service), когда злоумышленник передаёт специально подобранные данные, приводящие к чрезмерному потреблению CPU или памяти.
В случае Ajv такие проблемы обычно связаны не с самой библиотекой, а с тем, как построены схемы: сложные регулярные выражения, глубоко вложенные структуры, неограниченные массивы, избыточные логические комбинации и рекурсивные ссылки.
Одним из наиболее опасных источников DoS является использование
pattern в схемах JSON Schema. Ajv компилирует регулярные
выражения в JavaScript RegExp, а значит подвержен классическим проблемам
ReDoS (Regular Expression Denial of Service).
Опасность возникает, когда выражение допускает катастрофическое бэктрекинг-поведение:
(a+)+(a|aa)+При обработке длинных строк время проверки может расти экспоненциально.
Пример проблемного паттерна:
{
"type": "string",
"pattern": "^(a+)+$"
}
Даже умеренно длинная строка из множества символов a
может приводить к значительным задержкам.
JSON Schema позволяет описывать рекурсивные структуры через
$ref, allOf, anyOf,
oneOf. Неправильно спроектированные схемы могут приводить
к:
Особенно опасны конструкции:
allOf с пересекающимися условиями$ref без ограничения глубиныanyOf + oneOf на больших
структурахПример проблемной схемы:
{
"allOf": [
{ "$ref": "#/definitions/node" },
{ "$ref": "#/definitions/node" }
]
}
При неудачной архитектуре это приводит к многократной повторной валидации одних и тех же узлов.
Отсутствие ограничений maxItems,
maxProperties и maxLength создаёт возможность
подачи огромных структур данных.
Проблемные случаи:
Даже при линейной сложности алгоритма это приводит к перегрузке CPU и памяти.
Библиотека Ajv оптимизирована для скорости, но её производительность сильно зависит от схемы. При компиляции схемы в функцию валидатора происходит генерация JavaScript-кода, и ошибки проектирования схемы напрямую влияют на runtime.
Ajv компилирует каждую схему:
const validate = ajv.compile(schema);
Если схема сложная, компиляция может стать узким местом. При массовой динамической генерации схем возможен DoS уже на этапе подготовки валидаторов.
Рекомендуется:
Комбинаторные операторы увеличивают количество проверок:
allOf требует прохождения всех схемanyOf проверяет до первого успеха, но может проверять
все вариантыoneOf требует проверки всех вариантов для исключения
множественных совпаденийПример:
{
"oneOf": [
{ "$ref": "#/defs/a" },
{ "$ref": "#/defs/b" },
{ "$ref": "#/defs/c" }
]
}
При вложенных oneOf сложность может расти
экспоненциально.
JSON Schema не имеет универсального встроенного ограничения глубины, поэтому его нужно задавать архитектурно:
maxItems и maxProperties на
каждом уровнеПример защитного подхода:
{
"type": "object",
"maxProperties": 20,
"properties": {
"children": {
"type": "array",
"maxItems": 10,
"items": {
"$ref": "#"
}
}
}
}
Однако даже рекурсивные схемы должны иметь логическое ограничение глубины на уровне бизнес-логики.
Критически важно всегда задавать:
maxItemsminItemsmaxLengthminLengthБез этих ограничений схема становится потенциальным вектором перегрузки.
При использовании pattern следует избегать:
Безопасные практики:
^ и $.* внутри сложных выраженийИногда более безопасным решением является замена regex на:
enum)formatAjv поддерживает строгий режим, который помогает выявлять потенциально опасные конструкции схем.
Полезные опции:
strict: truestrictTypes: truestrictSchema: trueОни помогают обнаружить:
Строгий режим не предотвращает DoS напрямую, но снижает вероятность появления сложных и неконтролируемых схем.
Особое внимание требуется при использовании:
if / then / elseanyOf внутри условийПример опасной конструкции:
{
"if": {
"properties": { "type": { "const": "A" } }
},
"then": { "$ref": "#/defs/a" },
"else": {
"if": {
"properties": { "type": { "const": "B" } }
},
"then": { "$ref": "#/defs/b" }
}
}
Такие конструкции могут привести к каскадной проверке множества ветвей.
Рекомендуется:
discriminator)ifДаже при идеальной схеме важно ограничивать данные до попадания в Ajv:
Это предотвращает ситуацию, когда валидатор вообще не успевает начать работу.
Циклические ссылки сами по себе допустимы, но при сложной структуре они могут приводить к:
Пример:
{
"$ref": "#/definitions/node"
}
где node ссылается на самого себя через цепочку
объектов.
Важно:
Защита от DoS в контексте Ajv строится не на одном механизме, а на комбинации:
oneOf/anyOf/allOfДаже при использовании высокопроизводительного валидатора ключевым фактором остаётся архитектура схемы: именно она определяет, будет ли проверка линейной или экспоненциальной по затратам ресурсов.