Санитизация входных данных

Санитизация входных данных в контексте использования class-validator в JavaScript-проектах опирается на тесную интеграцию с механизмами преобразования объектов и строгого разделения ответственности между проверкой данных и их приведением к безопасному и предсказуемому виду. В реальных приложениях эти процессы почти всегда используются совместно: валидация отвечает за соответствие структуры и ограничений, а санитизация — за нормализацию и очистку входных значений до выполнения бизнес-логики.

В экосистеме Node.js и TypeScript часто наблюдается смешение двух процессов, однако их логика принципиально различается:

  • Валидация проверяет корректность данных (тип, диапазон, формат)
  • Санитизация изменяет данные для приведения к безопасному виду

class-validator реализует именно валидационный слой и не предназначен для модификации входных данных. Например, декораторы @IsString(), @IsEmail(), @Length() не изменяют значение, а только проверяют его.

Санитизация в таких архитектурах обычно делегируется class-transformer, который работает совместно с class-validator, формируя связку преобразования и проверки DTO-объектов.

Роль class-transformer в санитизации

Основной механизм очистки и нормализации данных реализуется через декоратор @Transform():

import { Transform } from 'class-transformer';
import { IsString } from 'class-validator';

export class CreateUserDto {
  @Transform(({ value }) => value?.trim())
  @IsString()
  username: string;
}

Здесь происходит классическая операция санитизации — удаление пробелов по краям строки. Важно, что преобразование выполняется до валидации, если включена опция transform: true при использовании plainToInstance.

Порядок преобразования и валидации

Ключевым аспектом является порядок выполнения операций:

  1. Преобразование plain-объекта в экземпляр класса
  2. Выполнение трансформаций через @Transform
  3. Применение валидаторов class-validator

Пример конфигурации:

import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';

const dto = plainToInstance(CreateUserDto, requestBody, {
  enableImplicitConversion: true,
});

const errors = await validate(dto);

Такой порядок позволяет сначала нормализовать входные данные, а затем проверить их корректность уже в очищенном виде.

Типовые операции санитизации

В прикладных системах наиболее часто применяются следующие преобразования:

Обрезка строк

@Transform(({ value }) => typeof value === 'string' ? value.trim() : value)
name: string;

Используется для устранения случайных пробелов, влияющих на уникальность и сравнение значений.

Приведение регистра

@Transform(({ value }) => typeof value === 'string' ? value.toLowerCase() : value)
email: string;

Позволяет унифицировать данные перед проверкой уникальности или поиском.

Нормализация чисел

@Transform(({ value }) => Number(value))
age: number;

Часто используется при получении данных из HTTP-запросов, где числа приходят строками.

Удаление потенциально опасных символов

@Transform(({ value }) =>
  typeof value === 'string'
    ? value.replace(/[<>]/g, '')
    : value
)
comment: string;

Такая операция применяется для минимальной фильтрации пользовательского ввода перед дальнейшей обработкой.

Взаимодействие санитизации и строгой валидации

Санитизация не должна подменять валидацию. Например, следующая ошибка архитектуры приводит к скрытым проблемам:

@Transform(({ value }) => value.replace(/[^0-9]/g, ''))
@IsNumber()
phone: string;

Здесь происходит попытка превратить произвольную строку в число через удаление символов, что может маскировать некорректные данные. Более корректный подход:

@Transform(({ value }) => value)
@IsString()
@Matches(/^\+?[0-9]{10,15}$/)
phone: string;

Санитизация должна быть предсказуемой и не скрывать ошибки входных данных.

Implicit conversion и его влияние

Флаг enableImplicitConversion позволяет автоматически приводить типы:

plainToInstance(Dto, data, {
  enableImplicitConversion: true,
});

Это влияет на санитизацию следующим образом:

  • строки "123" могут стать числом 123
  • "true" может быть преобразовано в true

Однако такое поведение требует осторожности, так как может снижать строгость проверки и приводить к неожиданным результатам в сложных DTO-структурах.

Санитизация вложенных объектов

При работе с вложенными DTO санитизация должна явно распространяться на дочерние структуры:

export class AddressDto {
  @Transform(({ value }) => value?.trim())
  city: string;
}

export class UserDto {
  @ValidateNested()
  @Type(() => AddressDto)
  address: AddressDto;
}

Без @Type() преобразование вложенных объектов не выполняется, и санитизация внутри них не сработает.

Обработка массивов

Санитизация массивов требует отдельного подхода:

@Transform(({ value }) =>
  Array.isArray(value)
    ? value.map(v => v.trim())
    : []
)
tags: string[];

Часто используется нормализация списков тегов, категорий или идентификаторов.

Безопасные ограничения трансформаций

Санитизация должна учитывать риск потери информации:

  • удаление символов может изменять смысл данных
  • агрессивное приведение типов может скрывать ошибки
  • автоматическая нормализация может нарушать бизнес-правила

Поэтому трансформации должны быть минимальными и детерминированными.

Комбинация с кастомными валидаторами

При необходимости сложной логики санитизации используется связка кастомного декоратора и трансформации:

@Transform(({ value }) => value?.trim())
@ValidatorConstraint({ name: 'isSafeText' })
class IsSafeTextConstraint implements ValidatorConstraintInterface {
  validate(text: string) {
    return !/[<>]/.test(text);
  }
}

Такой подход разделяет очистку (trim) и проверку безопасности (валидация).

Поток обработки данных в DTO

Типичный pipeline выглядит следующим образом:

  • получение сырого JSON из запроса
  • преобразование в экземпляр класса
  • применение трансформаций @Transform
  • валидация через class-validator
  • использование уже очищенного объекта в бизнес-логике

Это обеспечивает предсказуемость состояния данных на каждом этапе.

Ограничения подхода

Использование class-validator совместно с class-transformer не заменяет полноценные механизмы защиты входных данных на уровне инфраструктуры:

  • не решает проблему SQL-инъекций без параметризованных запросов
  • не заменяет CSP и HTML-экранирование
  • не гарантирует безопасность при небезопасной сериализации

Санитизация на уровне DTO — это лишь первый слой нормализации данных перед дальнейшей обработкой.