В прикладных JavaScript-приложениях входные данные практически всегда являются внешним источником риска. Любое поле формы, query-параметр, JSON-тело запроса или данные из стороннего API могут содержать не только некорректные значения, но и внедрённые скрипты. Именно поэтому валидация рассматривается как первый слой защиты, уменьшающий вероятность появления XSS (Cross-Site Scripting), хотя и не заменяющий экранирование вывода и другие меры безопасности.
Библиотека class-validator предоставляет декларативный подход к проверке данных через декораторы, позволяя формализовать ограничения на уровне DTO-моделей. Такой подход снижает вероятность случайного пропуска опасных значений и делает контракт данных явным.
XSS возникает тогда, когда неподконтрольные данные попадают в контекст выполнения JavaScript или HTML без должного экранирования. Основные типы атак:
Reflected XSS Вредоносный код передаётся через запрос и сразу возвращается в ответе сервера.
Stored XSS Скрипт сохраняется в базе данных и затем воспроизводится для других пользователей.
DOM-based XSS Уязвимость возникает на стороне клиента при небезопасной обработке данных в DOM.
Ключевая особенность всех типов — наличие неконтролируемого пользовательского ввода, который проходит без достаточной проверки или очистки.
Валидация не устраняет XSS напрямую, но уменьшает количество допустимых входных вариантов, что снижает вероятность внедрения вредоносных конструкций.
Основная цель валидации:
Пример: если поле должно содержать email, любые HTML-теги или скриптовые конструкции автоматически исключаются на уровне структуры данных.
Критическая особенность: валидация не является механизмом санитизации.
Даже корректное значение, прошедшее проверку, может содержать потенциально опасные данные:
<img src=x oner ror=alert(1)> может пройти
проверку IsStringПоэтому class-validator используется как структурный фильтр, но не как инструмент очистки.
Class-validator реализует подход, при котором правила описываются через декораторы DTO-классов.
import { IsString, MaxLength, MinLength } from "class-validator";
export class CommentDto {
@IsString()
@MinLength(1)
@MaxLength(500)
content: string;
}
Такая модель формирует строгий контракт: поле content
всегда строка ограниченной длины. Это уже исключает часть атак,
связанных с переполнением, вложенными структурами или неожиданными
типами.
Ключевые принципы, которые усиливают защиту от XSS при использовании class-validator:
Запрещает передачу неизвестных свойств:
import { validate } from "class-validator";
validate(dto, {
whitelist: true,
forbidNonWhitelisted: true,
});
Это предотвращает внедрение дополнительных полей, которые могут быть использованы в цепочках атак.
DTO должен описывать только необходимые поля без избыточности:
import { IsEmail, IsString } from "class-validator";
export class UserDto {
@IsEmail()
email: string;
@IsString()
username: string;
}
Чем меньше поверхность входных данных, тем меньше вероятность эксплуатации.
import { Matches, MaxLength } from "class-validator";
export class ProfileDto {
@MaxLength(30)
@Matches(/^[a-zA-Z0-9_]+$/)
nickname: string;
}
Регулярные выражения позволяют исключить HTML-символы и управляющие последовательности.
Фильтрация по типу снижает риск передачи структурированных payload вместо примитивов.
Ограничивают возможность вставки длинных скриптовых payload.
Позволяет реализовать строгий whitelist символов.
Форматная проверка снижает вероятность внедрения нестандартных схем.
Позволяет явно управлять необязательными полями, исключая скрытые null/undefined обходы логики.
При работе с вложенными объектами важно использовать:
import { ValidateNested } from "class-validator";
import { Type } from "class-transformer";
export class AddressDto {
@IsString()
city: string;
}
export class UserDto {
@ValidateNested()
@Type(() => AddressDto)
address: AddressDto;
}
Без ValidateNested вложенные объекты могут проходить без
проверки, создавая обходные пути для внедрения вредоносных данных.
Class-validator отвечает только за проверку. Очистка данных выполняется отдельно.
Типичная архитектура:
Пример опасной ошибки:
// Небезопасно
const html = dto.content;
document.innerHTML = html;
Даже при успешной валидации это создаёт XSS-уязвимость.
Даже идеально провалидированные данные могут быть опасны при неправильном выводе.
Контексты:
Валидация не учитывает контекст, поэтому защита должна дополняться экранированием.
data: any;
Полностью отключает защитный слой типов.
Приводит к принятию неожиданных полей.
Клиентская валидация легко обходится.
Без ValidateNested структура остаётся уязвимой.
Каждый endpoint должен иметь собственный DTO.
Whitelist включается глобально.
Любые данные считаются потенциально вредоносными до проверки.
Любые пользовательские строки имеют максимальную длину.
Массивы часто используются как обходной канал для атак.
import { IsArray, IsString, MaxLength } from "class-validator";
export class TagsDto {
@IsArray()
@IsString({ each: true })
@MaxLength(20, { each: true })
tags: string[];
}
Без each: true элементы массива могут оставаться
непроверенными.
Валидация усиливается дополнительными механизмами:
CSP особенно важна, так как даже при ошибке валидации она ограничивает выполнение внедрённого кода.
Если приложение допускает HTML, классическая валидация недостаточна:
В таких случаях требуется отдельная санитизация:
Каждое слабое звено усиливает следующее, и отсутствие строгой валидации на первом этапе делает остальные меры менее эффективными.
Преобразование типов перед проверкой уменьшает количество неожиданных сценариев:
import { Transform } from "class-transformer";
export class IdDto {
@Transform(({ value }) => Number(value))
id: number;
}
Это устраняет случаи, когда строка с вредоносным содержимым маскируется под числовой тип.