Validación de formularios
La validación de formularios comprueba que los datos que entran a tu aplicación (el cuerpo de un POST, los parámetros de una query) tengan la forma correcta antes de llegar a tu servidor de origen, a tu sitio PHP o a una función Lambda. Definís el esquema una vez, con un formato declarativo estilo Zod, y Tero Services rechaza en el borde todo lo que no encaje.
Por qué es distinta del WAF
Sección titulada «Por qué es distinta del WAF»La validación de formularios y el WAF son capas complementarias e independientes. Resuelven problemas distintos:
| WAF | Validación de formularios | |
|---|---|---|
| Qué detecta | Ataques (inyección SQL, XSS, traversal de paths) | Datos de negocio inválidos (email mal formado, edad fuera de rango, enum no permitido) |
| Qué responde | 403 Forbidden, opaco | 422 Unprocessable Entity, con el detalle de cada error |
| Para quién | Defensa contra atacantes | Corrección de datos de usuarios legítimos |
La idea es descargar la validación de entrada del backend. En lugar de que cada servicio repita las mismas comprobaciones, las definís una vez en el borde y obtenés respuestas 422 consistentes en todos los sitios de tu cuenta.
Cómo responde un fallo
Sección titulada «Cómo responde un fallo»Cuando un dato no cumple el esquema, Tero Services responde con 422 Unprocessable Entity y un cuerpo application/problem+json (RFC 7807) que enumera cada problema, con la misma forma que los issues de Zod:
{ "type": "https://tero.proxy/errors/validation", "title": "Validation failed", "status": 422, "errors": [ { "path": ["age"], "code": "too_small", "message": "Number must be >= 18" }, { "path": ["email"], "code": "invalid_string", "message": "Invalid email" } ]}Cada error trae su ruta al campo (path), un código estable y legible por máquina (invalid_type, too_small, invalid_string, unrecognized_keys, entre otros) y un mensaje para humanos. Tu front puede mapear estos errores a los campos del formulario directamente. El código de estado es configurable por regla (422 por defecto).
El esquema (estilo Zod)
Sección titulada «El esquema (estilo Zod)»El esquema se escribe como un documento JSON que refleja la API de Zod. Si tu front ya usa Zod, podés exportar el esquema o reescribirlo casi 1:1. Por ejemplo:
// El equivalente Zod del esquema de abajo:z.object({ email: z.string().email(), age: z.number().int().gte(18), role: z.enum(['admin', 'user']), nick: z.string().min(3).max(20).optional(),}).strict(){ "type": "object", "strict": true, "fields": { "email": { "type": "string", "checks": [{ "email": true }] }, "age": { "type": "number", "checks": [{ "int": true }, { "gte": 18 }] }, "role": { "type": "enum", "values": ["admin", "user"] }, "nick": { "type": "string", "optional": true, "checks": [{ "min": 3 }, { "max": 20 }] } }}Tipos y comprobaciones soportados
Sección titulada «Tipos y comprobaciones soportados»| Tipo | Comprobaciones disponibles |
|---|---|
string | min, max, length, email, url, uuid, datetime (ISO-8601), regex, startsWith, endsWith |
number | int, gte, lte, gt, lt, positive, negative, multipleOf |
boolean | (sin comprobaciones adicionales) |
enum | lista de values permitidos |
array | min, max, length, items (esquema de cada elemento) |
object | fields (anidables), strict (rechaza claves no declaradas) |
Cualquier campo admite además optional (la clave puede faltar) y nullable (el valor puede ser null), igual que en Zod.
Coerción de tipos
Sección titulada «Coerción de tipos»En una query string o en un formulario application/x-www-form-urlencoded todo llega como texto: age=25 es el string "25", no el número 25. Para esos casos agregá la comprobación coerce (equivalente a z.coerce.number()), que convierte el valor antes de validarlo:
{ "type": "number", "checks": [{ "coerce": true }, { "gte": 18 }] }Sin coerce, los valores de texto se validan como texto.
Dónde se aplica
Sección titulada «Dónde se aplica»Una regla de validación apunta a una parte de la solicitud:
query: los parámetros de la query string. Se valida en cualquier método y no requiere leer el cuerpo, por lo que es la opción más barata.body: el cuerpo de la solicitud (POST,PUT,PATCH). Se admiteapplication/jsonyapplication/x-www-form-urlencoded.both: ambos.
La validación de cuerpo funciona en todos los tipos de ruta (Proxy inverso, PHP y Lambda). Para no romper el streaming del proxy inverso, el cuerpo solo se almacena temporalmente cuando una regla de cuerpo coincide con esa ruta; el resto del tráfico sigue fluyendo sin cambios. El tamaño máximo de cuerpo a validar es de 256 KiB por defecto; un cuerpo mayor se rechaza con 413 Payload Too Large.
Alcance: global, dominio o path
Sección titulada «Alcance: global, dominio o path»Las reglas se definen por cuenta y se aplican con un alcance de granularidad creciente:
| Alcance | Aplica a |
|---|---|
| Global | Todos los dominios y paths de tu cuenta. |
| Dominio | Todo un dominio (por ejemplo, api.miempresa.com). |
| Path | Un patrón de path dentro de un dominio (por ejemplo, /api/users/**). |
En los patrones de path, * coincide con un segmento y ** con el resto de la ruta. Cada regla puede además acotarse a ciertos métodos HTTP (POST, PUT, etc.).
Cuando varias reglas podrían aplicar a una misma solicitud, gana la más específica: un patrón de path tiene prioridad sobre una regla de dominio, y esta sobre una regla global. La resolución es determinista, sin mezclar reglas.
Modo “solo registro”
Sección titulada «Modo “solo registro”»Igual que el WAF, cada regla puede correr en uno de dos modos:
- Bloqueo (por defecto): la solicitud que no valida se rechaza con el estado configurado.
- Solo registro: la solicitud se deja pasar, pero el fallo se cuenta y se registra. Sirve para medir el impacto de una regla nueva antes de activar el bloqueo real, sin riesgo para el tráfico en producción.
Cómo se configura
Sección titulada «Cómo se configura»Definís tus esquemas y reglas y se los entregás al equipo de Host Admin (la autogestión vía panel está en la hoja de ruta). Antes de guardarse, cada esquema se compila y se verifica: una expresión regular mal formada o un tipo desconocido se rechazan en el momento del alta, no en producción. Los cambios se aplican sin reiniciar el servicio.
Observabilidad
Sección titulada «Observabilidad»- Métrica Prometheus
tero_form_validation_total{outcome}, conoutcomeenpass,fail,monitoroskip. - Cada bloqueo deja una entrada en el plano de auditoría de datos (método, path, dirección IP, regla aplicada y los primeros errores), sin registrar el contenido sensible del cuerpo.
- Las altas y modificaciones de reglas quedan en el Registro de auditoría.
Limitaciones
Sección titulada «Limitaciones»- Se admiten
application/jsonyapplication/x-www-form-urlencoded. Los cuerposmultipart/form-data(subida de archivos) quedan fuera del alcance actual. - La validación de cuerpo está acotada a 256 KiB por solicitud (configurable); cuerpos mayores se rechazan con
413. - No reemplaza la validación de tu aplicación: es una capa de defensa y consistencia adicional en el borde, no la única.
Preguntas frecuentes
Sección titulada «Preguntas frecuentes»- ¿Puedo reutilizar mis esquemas de Zod del front? Sí. El formato es un espejo declarativo de Zod; en la mayoría de los casos la traducción es directa.
- ¿Afecta el rendimiento de quien no la usa? No. Si tu cuenta no define reglas, la validación se omite por completo en el camino de la solicitud.
- ¿Qué diferencia hay con el WAF? El WAF detecta ataques y responde
403opaco; la validación comprueba datos de negocio y responde422con el detalle. Conviene usarlos juntos.