Do rate limit ao anti-XSS
O pipeline de segurança completo utilizado neste projeto: cada camada com código real, justificativa e exemplos para você replicar.
Os exemplos mostram código real de produção deste projeto. Adapte os limites de rate limit e regras de sanitização ao seu caso de uso.
Cada requisição passa por múltiplas camadas de defesa antes de chegar à lógica de negócio.
Body Size Check
Rejeita payloads acima do limite antes de fazer parse.
Rate Limiting
Limita requisições por IP — in-memory ou Redis distribuído.
API Key Check
Valida variáveis de ambiente antes de chamar serviços externos.
Zod Schema
Valida estrutura, tipos e tamanhos de todos os campos do body.
Input Sanitization
Remove padrões de prompt injection e código malicioso.
Output Sanitization
Remove HTML/scripts da resposta — anti-XSS antes de enviar.
Secure Headers
nosniff, no-store, CORS — respostas com headers de segurança.
Dois modos: in-memory (leve, sem Redis) ou Redis distribuído (consistente entre instâncias serverless). O projeto usa Redis para rotas críticas.
In-memory (rate-limit.ts)
Fixed-window simples. Funciona para MVPs e desenvolvimento local. Reseta em cold starts — não escala para múltiplas instâncias.
// src/lib/rate-limit.tsimport { getClientIp, rateLimit, rateLimitResponse } from "@/lib/rate-limit";export async function POST(request: Request) {const ip = getClientIp(request);const rl = rateLimit(ip, {prefix: "contact",limit: 3,windowSeconds: 600, // 10 min});if (!rl.success) return rateLimitResponse(rl);// ...}
Redis distribuído (redis-rate-limit.ts)
INCR atômico + EXPIRE. Consistente entre todas as instâncias serverless da Vercel. Fallback automático para in-memory se Redis indisponível.
// src/lib/redis-rate-limit.tsimport { getClientIp, rateLimitResponse } from "@/lib/rate-limit";import { rateLimitAsync } from "@/lib/redis-rate-limit";export async function POST(request: Request) {const ip = getClientIp(request);const rl = await rateLimitAsync(ip, {prefix: "reactions",limit: 15,windowSeconds: 60,});if (!rl.success) return rateLimitResponse(rl);// ...}
| Rota | Limite | Janela | Backend |
|---|---|---|---|
| /api/chat | 30 req | 1 min | Redis |
| /api/code-review | 8 req | 5 min | Redis |
| /api/reactions | 15 req | 1 min | Redis |
| /api/contact | 3 req | 10 min | In-memory |
| /api/stats/track | 60 req | 1 min | In-memory |
Todo input é validado com schemas Zod antes de qualquer processamento. Tipos, tamanhos, formatos e regras customizadas.
Schema de Input
import { z } from "zod";const bodySchema = z.object({code: z.string().min(10).max(5000),language: z.string().max(30).regex(/^[a-zA-Z0-9+#. -]*$/).optional(),});const parsed = bodySchema.safeParse(await request.json());if (!parsed.success) {return jsonError("Invalid input", 400);}
Schema de Output da IA
// Validação do output da IA antes de retornar ao clienteconst reviewSchema = z.object({score: z.number().min(0).max(100),summary: z.string().max(500),issues: z.array(z.object({severity: z.enum(["critical", "warning", "info"]),message: z.string().max(300),})).max(20),});const validated = reviewSchema.safeParse(aiResponse);if (!validated.success) return jsonError("Invalid AI response", 500);
Por que Zod?
Anti Prompt Injection
Padrões detectados e substituídos por [filtered] antes de enviar à IA:
ignore previous instructionsyou are now / act as / pretendsystem: / new instruction / overridereveal system prompt / show instructionsimport { sanitizeUserInput } from "@/lib/api-security";// Detecta e filtra padrões de prompt injection:// "ignore previous instructions", "act as", "you are now",// "system:", "reveal system prompt" etc.const safeCode = sanitizeUserInput(parsed.data.code);
Anti XSS no Output
Toda resposta da IA é sanitizada antes de enviar ao cliente:
import { sanitizeOutput, sanitizeStreamChunk } from "@/lib/api-security";// Para respostas JSON — percorre o objeto recursivamenteconst safe = sanitizeOutput(aiResponse);return Response.json(safe, { headers: secureJsonHeaders() });// Para streamingsanitizeStreamChunk(chunk); // remove <script>, javascript:, event handlers
Todas as respostas da API incluem headers de segurança para prevenir MIME sniffing, cache de dados sensíveis e outros vetores.
X-Content-Type-Options:nosniffPrevine MIME type sniffing
Cache-Control:no-store, no-cacheNão cacheia respostas sensíveis
Content-Type:application/jsonTipo explícito sempre declarado
import { secureJsonHeaders } from "@/lib/api-security";// Headers aplicados em todas as respostas:// X-Content-Type-Options: nosniff// Cache-Control: no-store, no-cache// Content-Type: application/jsonreturn Response.json(data, { headers: secureJsonHeaders() });
Itens obrigatórios ao criar uma nova API route que envolva dados do usuário ou serviços externos.
Template Completo
export async function POST(request: Request) {try {// 1. Body sizeconst sizeError = checkBodySize(request, 12_000);if (sizeError) return sizeError;// 2. Rate limitconst ip = getClientIp(request);const rl = await rateLimitAsync(ip, {prefix: "my-route",limit: 10,windowSeconds: 60,});if (!rl.success) return rateLimitResponse(rl);// 3. API Key (se usar IA)const keyError = checkApiKey();if (keyError) return keyError;// 4. Parse + Validaçãoconst bodyResult = await safeParseBody(request);if ("error" in bodyResult) return bodyResult.error;const parsed = mySchema.safeParse(bodyResult.data);if (!parsed.success) return jsonError("Invalid input", 400);// 5. Sanitizar inputconst safeText = sanitizeUserInput(parsed.data.text);// 6. Lógica de negócio + retorno seguroconst result = await doSomething(safeText);return Response.json(sanitizeOutput(result),{ headers: secureJsonHeaders() },);} catch {return jsonError("Internal server error", 500);}}
Todo o módulo de segurança está em src/lib/api-security.ts. Leia o código, entenda as decisões e adapte ao seu projeto.