OWASP Top 10 para APIs
Asigna los riesgos del Top 10 de Seguridad de API de OWASP a mitigaciones concretas de Node.js: validación Zod, autenticación a nivel de objeto, límites de tasa y escaneo de dependencias.
Busca en todas las páginas de la documentación
Asigna los riesgos del Top 10 de Seguridad de API de OWASP a mitigaciones concretas de Node.js: validación Zod, autenticación a nivel de objeto, límites de tasa y escaneo de dependencias.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
// Patrón: validar -> autenticar -> autorizar -> manejador
app.get("/orders/:id", async (req, res) => {
const user = requireAuth(req);
const orderId = z.string().uuid().parse(req.params.id);
const order = await ordersRepo.findById(orderId);
if (!order || order.tenantId !== user.tenantId) {
return res.status(404).json({ error: "Not found" });
}
res.json(order);
});Cuándo usarlo:
import express from "express";
import rateLimit from "express-rate-limit";
import { z } from "zod";
import helmet from "helmet";
class HttpError extends Error {
constructor(public status: number, message: string) {
super(message);
}
}
const usersRepo = {
async update(id: string, patch: { name: string }) {
return { id, ...patch };
},
};
const app = express();
app.use(helmet());
app.use(express.json({ limit: "1mb" }));
const authLimiter = rateLimit({ windowMs: 60_000, max: 10 });
app.post("/auth/login", authLimiter, async (req, res) => {
const body = z.object({
email: z.string().email(),
password: z.string().min(8),
}).safeParse(req.body);
if (!body.success) return res.status(400).json({ error: "Invalid input" });
// comparación de tiempo constante en un servicio de autenticación real
res.json({ token: "..." });
});
type User = { id: string; tenantId: string; role: string };
function requireAuth(req: express.Request): User {
const user = (req as express.Request & { user?: User }).user;
if (!user) throw new HttpError(401, "Unauthorized");
return user;
}
app.patch("/users/:id", async (req, res) => {
const actor = requireAuth(req);
const targetId = z.string().uuid().parse(req.params.id);
if (actor.id !== targetId && actor.role !== "admin") {
return res.status(403).json({ error: "Forbidden" });
}
const patch = z.object({ name: z.string().min(1).max(120) }).parse(req.body);
res.json(await usersRepo.update(targetId, patch));
});Lo que esto demuestra:
tenantId y la propiedad en el acceso a objetos (404, no 403, para evitar la enumeración).| Riesgo | Mitigación de Node |
|---|---|
| API1: BOLA | Comprueba resource.ownerId === user.id (o inquilino) en cada ruta de ID |
| API2: Autenticación Rota | bcrypt/argon2, JWT de corta duración, límite de tasa /login, rotar secretos |
| API3: Asignación Masiva | Zod parse con campos permitidos explícitos; nunca extiendas req.body en ORM |
| API4: Consumo de Recursos | Límites de cuerpo, límites de paginación, límites de tasa, poner en cola trabajos pesados |
| API5: BFLA | Comprobaciones de rol por endpoint; rutas de administrador detrás de middleware separado |
| API6: Flujo Irrestricto | Límites de tasa de OTP, captcha en el registro, claves de idempotencia en pagos |
| API7: SSRF | Lista blanca de URL, resolución de DNS + bloqueo de IPs privadas (SSRF Guards) |
| API8: Mala Configuración | Helmet, deshabilitar x-powered-by, sin credenciales predeterminadas, npm audit en CI |
| API9: Inventario | Especificación OpenAPI, lint de rutas, desaprobar endpoints no utilizados |
| API10: Consumo Inseguro | Valida cargas útiles de webhook de terceros; tiempo de espera para recuperaciones salientes |
// Fastify: JSON Schema en la ruta + preHandler auth
app.get("/orders/:id", {
schema: { params: { type: "object", properties: { id: { type: "string", format: "uuid" } } } },
preHandler: [authenticate, authorizeOrderRead],
}, handler);# Genera OpenAPI y diff en PR
npx @redocly/cli lint openapi.yamlapp.get, app.post en CI.req.user de middleware opcional - ruta abierta si se omite el middleware. Solución: requireAuth explícito por ruta protegida.| Alternativa | Usar Cuándo | No Usar Cuándo |
|---|---|---|
| Zod manual + middleware | Control total, servicios pequeños | Aplicación NestJS grande con pipes incorporados |
| NestJS Guards + Pipes | Autorización basada en decoradores | Greenfield que no sea Nest |
| Fastify JSON Schema | Validación crítica para el rendimiento | El equipo prefiere solo Zod |
| Autenticación de API gateway | Política de borde centralizada | Aún se necesitan comprobaciones BOLA en la aplicación |
Sí, en la práctica. Los desarrolladores autentican a los usuarios pero olvidan verificar que son dueños del :id en la ruta.
Sí para BOLA, autenticación y SSRF. Los límites de tasa pueden ser más flexibles, pero los límites de recursos siguen siendo importantes.
La lista de API es específica para backends REST/GraphQL. Combínala con Encabezados de Seguridad y CORS para clientes de navegador.
Sí. BOLA se convierte en autorización a nivel de campo; las consultas por lotes son consumo de recursos de API4.
Pruebas de integración: el token del usuario A no puede leer el recurso del usuario B por ID. Automatiza en CI.
Define el alcance de las claves por inquilino y por operación. Rota y audita el uso. Nunca las pases en cadenas de consulta.
Parcialmente, es un riesgo de dependencia. Agrega Helmet, revisión de configuración y pruebas de penetración para una API8 completa.
Usa @UseGuards en los controladores, no en métodos individuales de forma inconsistente. Pipe de validación global con whitelist: true.
Versiones de la pila: Esta página fue escrita para Node.js 24.18.0 (LTS Activo), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.
Revisado por Chris St. John·Última actualización: 19 jul 2026