Работа с внешними данными в 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)
});
Любое значение, не соответствующее этой структуре, считается потенциально опасным или некорректным для дальнейшей обработки.
Ключевой принцип безопасной работы с данными заключается в строгом разделении этапов:
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)
});
Жёсткие границы предотвращают:
Внешние источники часто передают данные в виде строк. Без преобразования это приводит к ошибкам типов.
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();
}
В продакшн-среде часто применяется упрощение сообщений до общего вида:
Это снижает информационную утечку о внутренней структуре схем.
Вложенные структуры являются частым источником ошибок, особенно при работе с 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 наиболее эффективен на границах приложения:
Внутри системы предполагается работа исключительно с уже проверенными структурами, что снижает вероятность появления неконсистентных состояний и упрощает аудит кода.