Безопасность и санитизация входных данных

Работа с внешними данными в JavaScript-проектах неизбежно связана с рисками: некорректные типы, неожиданные структуры объектов, попытки внедрения вредоносных значений, переполнения, логические обходы проверок. Библиотека Zod используется для строгого описания структуры данных и их последующей проверки на этапе выполнения, что позволяет формировать предсказуемые контракты между слоями приложения.

Базовый принцип защиты данных через схему

Основная идея заключается в том, что любые входные данные считаются недоверенными до момента прохождения схемы валидации. Схема в Zod выступает одновременно:

  • фильтром типов;
  • механизмом нормализации;
  • инструментом ограничения допустимых значений;
  • способом детектирования структурных аномалий.
import { z } fr om "zod";

const userSchema = z.object({
  id: z.number(),
  email: z.string().email(),
  age: z.number().min(0).max(120)
});

Любое значение, не соответствующее этой структуре, считается потенциально опасным или некорректным для дальнейшей обработки.

Разделение валидации и доверенной логики

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

  1. получение входных данных;
  2. валидация через схему;
  3. преобразование в типизированный объект;
  4. дальнейшая бизнес-логика только с валидированными данными.
const result = userSchema.safeParse(input);

if (!result.success) {
  throw new Error("Invalid input");
}

const user = result.data;

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

Строгость схемы и защита от лишних полей

Одной из частых проблем является наличие неожиданных полей в объектах, поступающих из внешних источников. Zod по умолчанию может пропускать дополнительные ключи, что иногда приводит к утечкам данных или логическим ошибкам.

Для усиления защиты применяется строгий режим:

const strictSchema = z.object({
  name: z.string()
}).strict();

В этом режиме любые лишние поля приводят к ошибке. Альтернативой является их явное удаление:

z.object({
  name: z.string()
}).strip();

Удаление неизвестных полей снижает риск их непреднамеренного использования в логике приложения.

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

Строки являются основным источником атак через инъекции и некорректные значения. Zod позволяет применять трансформации, которые выполняются сразу после проверки типа.

const nameSchema = z.string().trim().toLowerCase();

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

  • удаление пробелов;
  • приведение к единому регистру;
  • нормализация формата;
  • ограничение длины строки.
const commentSchema = z.string().max(500).trim();

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

Защита от инъекций через форматирование

Zod не является специализированным инструментом для защиты от SQL-инъекций или XSS, однако позволяет структурировать данные до безопасного состояния.

Пример ограничения URL:

const urlSchema = z.string().url();

Дополнительно можно ограничивать домены:

const allowedDomain = z.string().refine((val) =>
  val.startsWith("https://trusted.com")
);

Механизм refine позволяет внедрять произвольные проверки, которые служат дополнительным уровнем контроля.

Числовые ограничения и предотвращение логических атак

Числовые значения часто используются для параметров пагинации, идентификаторов и финансовых расчётов. Ошибки в них могут приводить к переполнению логики или обходу ограничений.

const paginationSchema = z.object({
  page: z.number().int().min(1),
  lim it: z.number().int().min(1).max(100)
});

Жёсткие границы предотвращают:

  • запросы чрезмерного объёма данных;
  • отрицательные значения;
  • дробные числа в местах, где требуются целые.

Преобразование входных данных (coercion)

Внешние источники часто передают данные в виде строк. Без преобразования это приводит к ошибкам типов.

const schema = z.object({
  age: z.coerce.number()
});

coerce выполняет автоматическое преобразование, но при этом сохраняет контроль над валидностью значения.

Работа с неизвестными и частичными данными

В реальных API часто встречаются неполные структуры. Для безопасной обработки используется комбинация optional, nullable и partial.

const schema = z.object({
  name: z.string(),
  bio: z.string().optional()
});
const partialSchema = schema.partial();

Частичные схемы позволяют безопасно обрабатывать PATCH-запросы и неполные обновления без потери контроля типов.

Дискриминированные объединения как защита от подмены структуры

Сложные API часто возвращают разные структуры в зависимости от состояния данных. Для предотвращения подмены формата используется discriminated union.

const responseSchema = z.discriminatedUnion("status", [
  z.object({
    status: z.literal("success"),
    data: z.string()
  }),
  z.object({
    status: z.literal("error"),
    message: z.string()
  })
]);

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

Обработка ошибок валидации как элемент безопасности

Ошибки валидации не должны раскрывать внутреннюю структуру системы. Детализированные сообщения могут использоваться для анализа уязвимостей.

const result = schema.safeParse(input);

if (!result.success) {
  const error = result.error.flatten();
}

В продакшн-среде часто применяется упрощение сообщений до общего вида:

  • “invalid input”;
  • “validation failed”.

Это снижает информационную утечку о внутренней структуре схем.

Глубокая валидация вложенных объектов

Вложенные структуры являются частым источником ошибок, особенно при работе с JSON API.

const schema = z.object({
  user: z.object({
    profile: z.object({
      age: z.number().min(0)
    })
  })
});

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

Защита через комбинацию схем и трансформаций

Санитизация часто достигается не только проверками, но и преобразованиями данных в безопасный формат.

const schema = z.object({
  tags: z.string().transform((val) =>
    val.split(",").map(t => t.trim())
  )
});

Такой подход обеспечивает единообразие данных на выходе схемы независимо от формата входа.

Ограничение набора допустимых значений

Whitelist-подход является ключевым элементом безопасной обработки данных.

const roleSchema = z.enum(["admin", "user", "guest"]);

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

Контроль типов на границе системы

Zod наиболее эффективен на границах приложения:

  • входящие HTTP-запросы;
  • данные из внешних API;
  • конфигурационные файлы;
  • сообщения из очередей.

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