Los sitios web AI-Ready tienen endpoints de API publicos. Ese es todo el punto -- los agentes de IA deben poder usar servicios sin intervencion humana. Pero endpoints publicos tambien significa: cualquiera puede llamarlos. No solo agentes de IA utiles, sino tambien bots, scrapers y atacantes.
Quien construye un sitio web AI-Ready y no piensa en la seguridad desde el dia uno, esta construyendo una puerta abierta.
El problema: Publico significa atacable
Un sitio web clasico tiene un formulario de contacto. Proteccion contra spam? reCAPTCHA, campos honeypot, quizas un rate limit. Eso funciona porque los humanos rellenan formularios -- y la mayoria de los bots no pueden resolver CAPTCHAs.
Un sitio web AI-Ready tiene endpoints de API como /api/v1/quote o /api/v1/consultation. Estos endpoints estan disenados para uso automatizado. Un CAPTCHA destruiria todo el proposito -- los agentes de IA deben acceder programaticamente a ellos.
Esto crea un dilema: La puerta debe estar abierta para agentes legitimos, pero cerrada para el abuso. La solucion no es un unico mecanismo, sino multiples capas.
Capa 1: Validacion de entrada con Zod
La primera linea de defensa es la mas simple: verificar si los datos entrantes son validos antes de que activen cualquier cosa.
Zod es una biblioteca de validacion para TypeScript que verifica datos contra un esquema definido. Sin magia, sin IA -- pura validacion estructural.
Por que no simplemente sentencias if?
Se podria. Pero con estructuras de datos complejas, esto rapidamente se vuelve inmanejable y propenso a errores. Zod impone una estructura clara:
import { z } from "zod";
const quoteSchema = z.object({
projectType: z.enum(["website", "webshop", "webapp", "redesign"]),
pages: z.string().optional(),
features: z.string().optional(),
});
// En la ruta de la API:
const parsed = quoteSchema.safeParse(body);
if (!parsed.success) {
return ApiError.validation(
"Datos de entrada invalidos",
parsed.error.issues
);
}
Que previene Zod concretamente
- Campos obligatorios faltantes: Una solicitud sin
projectTypese rechaza inmediatamente - Tipos incorrectos: Un valor de
pagescomotrueen lugar de un string se detecta - Valores inesperados: Un
projectTypede"hacking"se bloquea por el enum - Intentos de inyeccion: La inyeccion SQL o NoSQL a traves de campos manipulados se dificulta por la tipificacion estricta
Suena basico -- y lo es. Pero la mayoria de las vulnerabilidades de seguridad no surgen de ataques sofisticados, sino de la falta de validacion de entrada.
Practica: Validar una solicitud de consulta
const consultationSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
phone: z.string().optional(),
topic: z.string().min(5).max(500),
});
Esta validacion de esquema asegura:
- Nombre tiene al menos 2 y maximo 100 caracteres (sin cadenas vacias, sin entradas de longitud novelistica)
- Email es un formato de email valido (sin inyeccion de texto libre)
- Tema tiene 5-500 caracteres (sin envio vacio, sin payload de 10MB)
Capa 2: Rate Limiting
La validacion protege contra datos incorrectos. El rate limiting protege contra demasiadas solicitudes.
Como funciona el Rate Limiting
El principio es simple: contar cuantas solicitudes hace una direccion IP dentro de una ventana de tiempo. Si supera el limite, recibe un estado 429 Too Many Requests.
IP 192.168.1.1:
Ventana: 60 segundos
Limite: 60 solicitudes
Actual: 47
→ Permitido
IP 10.0.0.5:
Ventana: 60 segundos
Limite: 60 solicitudes
Actual: 61
→ Bloqueado (Retry-After: 23s)
Diferentes limites para diferentes endpoints
No todos los endpoints necesitan el mismo limite:
| Tipo de endpoint | Limite | Razon |
|---|---|---|
| Endpoints GET (Portfolio, Services) | 60/minuto por IP | Leer es barato, mayor tolerancia |
| Endpoints POST (Quote, Consultation) | 10/minuto por IP | Escribir es costoso, control mas estricto |
| Endpoints de llamadas externas (AI-Ready Check) | 10/minuto por IP | Hacen llamadas HTTP externas, alto riesgo de abuso |
Por que la diferencia? Un endpoint GET lee datos de la base de datos -- eso es rapido y barato. Un endpoint POST escribe datos, potencialmente envia emails o dispara otras acciones. Y un endpoint que llama a sitios web externos (como nuestro AI-Ready Check) podria ser abusado para ataques DDoS contra terceros.
La respuesta en Rate Limiting
Cuando se alcanza un limite, el cliente recibe una respuesta clara:
{
"error": "Rate limit exceeded. Max 60 requests per minute.",
"retryAfterMs": 23000
}
Ademas de headers HTTP que le dicen al cliente (o agente) donde esta:
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1739654400
Un agente de IA bien construido lee estos headers y espera automaticamente. Un atacante al menos recibe un freno.
Capa 3: Sanitizacion de errores
Cuando algo sale mal -- y siempre sale mal eventualmente -- el mensaje de error no debe revelar detalles internos.
Que aprende un atacante de los mensajes de error
Un error sin sanitizar podria verse asi:
{
"error": "PrismaClientKnownRequestError: Invalid `prisma.client.create()` invocation in /home/user/app/lib/db.ts:47:3",
"stack": "at Object.create (/home/user/node_modules/@prisma/client/runtime/library.js:123:45)"
}
Que revela esto? El ORM (Prisma), la ruta del archivo, la linea en el codigo, la version de Node.js. Un atacante ahora sabe donde buscar.
Como devolvemos errores
Nuestra API siempre devuelve el mismo formato:
{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error"
}
}
En modo desarrollo, los desarrolladores ven el mensaje de error completo. En produccion, cada llamante ve solo "Internal server error". Sin stack trace, sin ruta de archivo, sin nombre de ORM.
Los codigos de error
Usamos seis codigos de error definidos:
| Codigo | Estado HTTP | Significado |
|---|---|---|
UNAUTHORIZED | 401 | API key faltante o invalida |
FORBIDDEN | 403 | La key no tiene el permiso necesario |
RATE_LIMITED | 429 | Demasiadas solicitudes |
VALIDATION_ERROR | 400 | Datos de entrada invalidos |
NOT_FOUND | 404 | Recurso no encontrado |
INTERNAL_ERROR | 500 | Error del servidor (sin detalles) |
Un agente de IA puede leer estos codigos y reaccionar: esperar en 429, corregir la solicitud en 400, autenticarse en 401.
Capa 4: Configuracion CORS
CORS (Cross-Origin Resource Sharing) controla quien puede acceder a la API y desde donde.
Para endpoints de discovery AI-Ready (como agents.json), CORS debe estar abierto -- los agentes de IA vienen de todas partes. Pero hay matices:
Endpoints de Discovery (solo GET):
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
→ Cualquiera puede leer, pero solo leer.
Endpoints de API:
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
→ Acceso completo, pero solo con headers permitidos.
El metodo OPTIONS es para solicitudes preflight -- los navegadores primero preguntan "Puedo?" antes de enviar la solicitud real. Cacheamos esta respuesta preflight por 24 horas (Access-Control-Max-Age: 86400) para evitar solicitudes innecesarias.
Capa 5: Lo que no se expone
Igual de importante que asegurar los endpoints publicos es la pregunta: Que permanece privado?
En nuestro caso, eso incluye:
- Herramientas MCP internas (242 herramientas para desarrollo web -- no publicas)
- IDs y estructuras de base de datos (sin IDs de Prisma en las respuestas de la API)
- Tokens de autenticacion (nunca en logs ni en respuestas)
- Configuracion del servidor (sin numeros de version, sin rutas)
- Datos personales de usuarios (RGPD de todos modos, pero tambien aislados tecnicamente en la API)
Suena obvio, pero no lo es. Muchas APIs devuelven IDs de base de datos ("id": 47), de los que se puede deducir el numero total de registros. O registran stack traces en logs que son legibles publicamente a traves de herramientas de error-tracking.
Conceptos de autenticacion: Hoy y manana
Lo que tenemos hoy
Para los endpoints publicos de negocio (Portfolio, Services, Quote), deliberadamente no hay autenticacion. La razon: estos endpoints entregan informacion que tambien es visible publicamente en el sitio web. Una calculadora de precios no es un secreto.
Para endpoints avanzados (API de Animaciones, Analytics), usamos API keys con permisos con alcance definido:
Bearer sk_live_abc123...
Scopes: animations:read, animations:generate
Rate Limit: 100/min (configurable por key)
Lo que trae el futuro
A medida que A2A y protocolos similares ganen adopcion mas amplia, la cuestion de la autenticacion de agentes se volvera central. Como demuestra un agente de IA que actua en nombre de un usuario especifico?
Posibles enfoques:
- OAuth 2.0 Token Relay: El agente recibe un token del usuario y lo reenvie
- Certificados de identidad de agente: Certificados digitales para agentes verificados
- DID (Identificadores Descentralizados): Identidades auto-soberanas para agentes
A dia de hoy, ninguno de estos enfoques esta estandarizado para comunicacion entre agentes. Pero los fundamentos (OAuth, certificados) existen -- lo que falta es la estandarizacion en el contexto A2A.
La regla de oro: La seguridad no es una funcionalidad, es un prerrequisito
El error mas comun con sitios web AI-Ready: construir primero los endpoints, asegurarlos "despues". En nuestra experiencia, "despues" nunca llega -- o llega solo despues del primer incidente.
Nuestra checklist para cada nuevo endpoint
[ ] Esquema Zod definido?
[ ] Rate limit configurado (GET: 60/min, POST: 10/min)?
[ ] Respuestas de error sanitizadas (sin stack traces)?
[ ] Headers CORS configurados?
[ ] Sin IDs de base de datos en la respuesta?
[ ] Sin datos personales sin autenticacion?
[ ] Logging sin datos sensibles?
Esta checklist no es sobrecarga. Es la diferencia entre un endpoint que es util y uno que se convierte en un problema.
Resumen
| Capa | Protege contra | Mecanismo |
|---|---|---|
| Validacion de entrada | Datos incorrectos/manipulados | Esquemas Zod con tipos estrictos |
| Rate Limiting | Sobrecarga y fuerza bruta | Contadores por IP con ventanas de tiempo |
| Sanitizacion de errores | Fuga de informacion | Mensajes de error genericos en produccion |
| CORS | Acceso cross-origin no autorizado | Control de acceso basado en headers |
| Auth (API Keys) | Acceso no autorizado a endpoints premium | Bearer tokens con permisos con alcance |
| No exposicion | Divulgacion accidental de datos | Decisiones conscientes sobre que permanece privado |
La seguridad para sitios web AI-Ready no es un tema especial. Sigue los mismos principios fundamentales que cualquier API -- validacion, rate limiting, manejo limpio de errores. La diferencia es que los endpoints estan disenados para acceso automatizado, lo que hace la superficie de ataque mas grande desde el dia uno.
Quien piensa en esto desde el principio no tiene problemas. Quien lo anade despues tiene trabajo por delante.
