Gratis, sin registro
Que tu asistente no escriba el fallo
Un escáner te dice qué tiene mal el código que ya escribiste. Esto es lo contrario: un archivo de reglas que tu asistente de IA lee antes de escribir, para que la vulnerabilidad no llegue a existir.
Genera el archivo
Dinos de qué está hecho tu proyecto y para qué asistente lo quieres.
Guárdalo en tu repositorio
En la raíz, con el nombre que te indicamos. Súbelo como un archivo más.
Ya está
Tu asistente lo lee en cada petición, sin que tengas que recordárselo nunca.
1. ¿De qué está hecho tu proyecto?
Pega tu package.json (o tu requirements.txt, tu go.mod…) y lo deducimos. No sale de tu navegador: esta página no manda nada a ningún servidor.
O márcalo a mano:
2. ¿Qué asistente usas?
Cada uno busca su archivo en un sitio distinto. El contenido es el mismo.
3. Guárdalo como CLAUDE.md
51 reglas, en la raíz de tu proyecto. Tu asistente las lee solo, en cada petición.
# Reglas de seguridad del proyecto
> Generado por [Ketrion](https://www.ketrion.com). Reglas generales, sin adaptar a ningún stack concreto.
> Respaldadas por 109 reglas del escáner de Ketrion, que las comprueba sobre el código ya escrito.
Estas son las reglas de seguridad de este proyecto. Aplícalas a TODO el código que escribas o modifiques, sin que haga falta que te las recuerden. Si una petición del usuario choca con una de estas reglas, dilo y propón la forma segura de conseguir lo mismo, en vez de escribir el código inseguro y avisar después.
## Autorización y datos de otros
- **Comprueba la sesión en el SERVIDOR en toda ruta, acción de servidor o manejador que lea o modifique datos. Ocultar un botón en la interfaz no protege nada: la petición sigue funcionando.**
- En su lugar: Empieza cada manejador leyendo la sesión y cortando si no hay: si no hay usuario, responde 401 antes de tocar la base de datos.
- **Comprobar que hay sesión NO es suficiente: comprueba además que el recurso pertenece a quien lo pide. Es el fallo más repetido de todos — el usuario cambia un id en la URL y ve el pedido, la factura o el perfil de otro.**
- En su lugar: Filtra siempre por el dueño en la propia consulta (`where id = :id AND user_id = :sesion`), en vez de leer primero y comparar después.
- **No aceptes NUNCA el identificador del usuario desde el cuerpo, la query o una cabecera. Quien lo manda puede cambiarlo.**
- En su lugar: El id del usuario sale siempre de la sesión del servidor, nunca de la petición.
- **El rol y los permisos se leen de la base de datos en el servidor. No confíes en un rol que venga en la petición, en el estado del cliente ni en un campo que el usuario pueda actualizar.**
- **Devuelve solo los campos que la pantalla necesita. No serialices la fila entera de un usuario ni uses `include`/`select *` en endpoints públicos: ahí van hashes de contraseña, tokens de OAuth y secretos de doble factor.**
- En su lugar: Enumera las columnas explícitamente en cada consulta.
- **Toda tarea programada, webhook o endpoint de administración necesita su propio secreto o comprobación de rol. Una ruta sin autenticar es pública aunque no esté enlazada en ninguna parte.**
- En su lugar: Compara el secreto en tiempo constante (`timingSafeEqual`, `hmac.compare_digest`) y responde 401 si no cuadra.
## Secretos y credenciales
- **No escribas ninguna credencial en el código, ni siquiera de forma temporal o 'para probar'. Claves de API, contraseñas de base de datos, tokens y llaves privadas van en variables de entorno.**
- En su lugar: Lee el valor del entorno y falla al arrancar si falta, en vez de dejar un valor por defecto.
- **Nunca pongas un valor por defecto a un secreto (`process.env.AUTH_SECRET || 'dev'`). En el día en que esa variable falte en producción, cualquiera que conozca el valor por defecto puede firmar sesiones válidas.**
- **Todo lo que lleve el prefijo `NEXT_PUBLIC_`, `VITE_` o `REACT_APP_` acaba dentro del JavaScript que descarga cualquiera. Ahí solo van valores que puedas publicar en un cartel.**
- **Tampoco en los archivos de configuración: `docker-compose.yml`, los `values` de Helm, un Secret de Kubernetes o un workflow de CI cuentan como código y se suben al repositorio igual.**
- En su lugar: Referencia el valor (`${VAR}`, `secretKeyRef`) y guarda el real en el gestor de secretos del entorno.
- **No registres nunca contraseñas, tokens ni el cuerpo entero de una petición de login. Los registros se guardan, se reenvían y los lee gente que no debería ver esos valores.**
- En su lugar: Registra el identificador del usuario y el resultado, no el contenido.
## Base de datos
- **Activa la seguridad por fila (RLS) en TODA tabla con datos de usuarios, y escribe una política que compare de verdad: `USING (auth.uid() = user_id)`. Una política con `USING (true)` deja la puerta igual de abierta que no tener ninguna.**
- En su lugar: Después de crear cada tabla, añade `ALTER TABLE x ENABLE ROW LEVEL SECURITY` y sus políticas en la misma migración.
- **La clave `service_role` se salta toda la RLS. Solo en el servidor, y solo tras comprobar quién llama: usarla en un manejador sin autenticar equivale a publicar la base entera.**
- En su lugar: En el navegador va únicamente la clave `anon`, apoyada en políticas RLS que sí filtren.
- **Construye las consultas con parámetros, siempre. No pegues valores dentro del texto de la consulta ni con concatenación, ni con plantillas, ni con `format()`, ni con `Sprintf`.**
- En su lugar: `db.query('SELECT * FROM u WHERE id = $1', [id])`, y en el ORM sus ayudantes en vez de `raw`.
- **No pases nunca un objeto de la petición directamente como filtro o como datos a actualizar. `updateMany({ where: body.where, data: body.data })` deja que el cliente reescriba la tabla entera.**
- En su lugar: Construye el filtro campo a campo con lo que validaste, y añade siempre el dueño.
- **No consultes dentro de un bucle. Con diez filas no se nota; con diez mil, tumba la respuesta y dispara la factura.**
- En su lugar: Pide todo de una vez con un `WHERE ... IN (...)` o un join, y reagrupa en memoria.
- **Toda lista lleva límite y paginación desde el primer día, y el límite lo fija el servidor aunque el cliente pida más.**
## Dinero
- **El importe, el precio y el descuento se calculan SIEMPRE en el servidor leyéndolos de tu base de datos. No aceptes nunca un `amount`, `price` o `unit_amount` que venga del navegador.**
- En su lugar: El cliente manda qué producto y cuántos; el precio lo pones tú.
- **Verifica la firma de todo webhook de pagos con el CUERPO CRUDO, antes de parsearlo. Si vuelves a serializar el JSON la firma no cuadra nunca, y si el `catch` sigue adelante la verificación es decorativa.**
- En su lugar: Lee el cuerpo como texto, verifica, y solo entonces parsea. Si la firma falla, responde 400 y no proceses nada.
- **Los webhooks llegan dos veces. Guarda el identificador del evento y descarta los repetidos, o acabarás cobrando, enviando o acreditando dos veces.**
- **No llames a un modelo de lenguaje dentro de un bucle. Cada vuelta es una llamada facturada aparte, y si el número de vueltas lo decide el usuario, tu factura también la decide él.**
- En su lugar: Manda los elementos juntos en una sola petición, usa la API de lotes del proveedor, y pon un tope duro al número de elementos antes de empezar.
- **Comprobar el saldo y luego descontarlo en dos pasos es una condición de carrera. Dos peticiones simultáneas pasan las dos.**
- En su lugar: Hazlo en una sola sentencia con la condición dentro (`UPDATE ... SET saldo = saldo - 1 WHERE id = ? AND saldo > 0`) y mira cuántas filas cambiaron.
## Sesiones y tokens
- **Verifica siempre la firma de un JWT antes de creerte lo que dice. `decode` no verifica nada: solo lee. Y no aceptes nunca el algoritmo `none`.**
- En su lugar: `jwt.verify(token, secreto, { algorithms: ['HS256'] })`, con la lista de algoritmos escrita a mano.
- **La cookie de sesión va con `HttpOnly`, `Secure` y `SameSite=Lax` o `Strict`. Sin `HttpOnly` cualquier script inyectado se la lleva.**
- **No guardes tokens de sesión en `localStorage` ni en `sessionStorage`: cualquier script de la página los lee.**
- En su lugar: Una cookie `HttpOnly` puesta por el servidor.
- **Compara secretos, firmas y claves de API en tiempo constante. Un `===` normal se detiene en el primer carácter distinto y filtra el valor a quien mida los tiempos.**
- **Genera tokens, códigos de recuperación y identificadores de sesión con un generador criptográfico (`crypto.randomBytes`, `secrets`, `SecureRandom`). `Math.random()` es predecible.**
- **Las contraseñas se guardan con bcrypt, scrypt o argon2. Nunca con MD5, SHA-1 ni SHA-256 a secas.**
## Entrada del usuario
- **Valida el cuerpo y los parámetros de toda petición contra un esquema (zod, pydantic, lo que uses) en la primera línea del manejador. Trata como sospechoso todo lo que no haya pasado por ahí.**
- **No ejecutes nunca texto como código: `eval`, `new Function`, `exec` de plantillas.**
- **No construyas comandos del sistema pegando texto del usuario, y no uses el shell para ejecutarlos.**
- En su lugar: Pasa el programa y sus argumentos por separado, en una lista: `execFile('git', ['clone', url])`, `subprocess.run(['ping', ip])`.
- **No construyas rutas de archivo con texto del usuario. `path.join` no protege de `../`: sigue saliendo de la carpeta.**
- En su lugar: Quédate solo con el nombre (`path.basename`), genera tú el nombre en el servidor, o comprueba que la ruta final sigue dentro de la carpeta permitida.
- **No deserialices datos que no controlas con formatos que pueden ejecutar código: `pickle`, `unserialize` de PHP, `yaml.load` sin `SafeLoader`, `BinaryFormatter`.**
- En su lugar: Usa JSON, o firma el contenido y verifica la firma antes de deserializar.
- **No redirijas a una dirección que llegue en la petición (`next`, `returnTo`, `callbackUrl`) sin comprobarla. Un enlace con tu dominio que acaba en una copia de tu login es phishing con tu marca.**
- En su lugar: Acepta solo rutas relativas que empiecen por una sola barra (`/cuenta`, nunca `//otro.com`), o contrástalo con una lista tuya.
- **Si parseas XML de fuera, desactiva las entidades externas (`disallow-doctype-decl`). Si no, un archivo XML puede leer archivos de tu servidor.**
## Navegador
- **No metas datos del usuario en el HTML como marcado: `innerHTML`, `outerHTML`, `insertAdjacentHTML`, `document.write` y `dangerouslySetInnerHTML` ejecutan lo que les pongas.**
- En su lugar: Usa `textContent`, o deja que el framework escape solo. Si de verdad necesitas HTML del usuario, pásalo antes por DOMPurify.
- **No desactives el escapado de las plantillas: `mark_safe`, el filtro `|safe`, `{% autoescape off %}`, `template.HTML`. El escapado automático es lo que te protege.**
- **No pongas CORS en `*` ni reflejes el `Origin` que llega. Y `origin: true` en el middleware `cors` es peor que `*`, porque además funciona con credenciales.**
- En su lugar: Una lista fija de tus propios dominios.
- **Nunca caches en el CDN una respuesta que dependa de quién la pide. Un `s-maxage` sobre `/api/me` sirve los datos de un usuario a otro.**
- En su lugar: Marca esas rutas como dinámicas y sin caché.
- **No desactives la protección CSRF para que 'funcione' una petición. Si estorba, es que falta el token, no que sobre la protección.**
## Peticiones a otros servidores
- **No hagas peticiones a una URL que venga del usuario sin validarla. Desde tu servidor se llega a direcciones internas que desde fuera no son accesibles.**
- En su lugar: Acepta solo dominios de una lista tuya y rechaza cualquier destino que resuelva a una IP privada.
- **No desactives nunca la verificación del certificado (`rejectUnauthorized: false`, `verify=False`, `InsecureSkipVerify`). Si falla en desarrollo, arregla el certificado.**
- **Toda llamada a un servicio externo lleva tiempo límite y un número acotado de reintentos. Sin eso, una caída ajena cuelga tu servidor entero.**
## Infraestructura
- **No ejecutes el contenedor como root ni con `privileged: true`, y fija la versión de la imagen base en vez de usar `latest`.**
- **No descargues y ejecutes un script en el mismo paso (`curl … | sh`). Descarga, comprueba la suma y ejecuta después.**
- **No abras nada a `0.0.0.0/0`, no dejes buckets públicos, no crees bases de datos accesibles desde internet, no uses `Action: "*"` en IAM y cifra el almacenamiento.**
- **No dejes las reglas de Firestore o Realtime Database en `allow read, write: if true`. Eso publica la base entera.**
- **No dejes el modo debug encendido fuera de tu máquina: enseña el código, las variables de entorno y una consola.**
## Fiabilidad
- **No te tragues un error con un `catch` vacío. Registra al menos qué pasó y decide si hay que reintentar, avisar o propagar.**
- **No devuelvas al usuario el mensaje de la excepción ni la traza. Da un mensaje genérico y deja el detalle en tus registros.**
- **Pon límite de peticiones a lo que cuesta dinero o manda correos, y tamaño máximo a lo que se sube. Un endpoint de IA sin límite es una factura esperando a ocurrir.**
---
Cuando termines un cambio, repasa esta lista antes de darlo por bueno. Si algo de lo que escribiste incumple una regla, arréglalo tú y explica qué cambiaste.
De dónde salen estas reglas
Son 51 instrucciones escritas a partir de las 109reglas del escáner de Ketrion: las mismas que corren sobre el código de nuestros clientes, traducidas de «encontré esto» a «no hagas esto».
Unas cuantas no vienen del escáner sino de nuestro análisis con IA, porque ningún patrón de texto las ve: que una ruta compruebe que el dato es de quien lo pide, que el precio se calcule en el servidor, que un webhook que llega dos veces no cobre dos veces. Son las que más caro salen, y por eso están aquí.
Cada directiva declara internamente qué reglas del escáner la comprueban, y hay una prueba automática que verifica que existen. Si mañana borramos una regla, esta página deja de prometerla — no puede quedarse diciendo que revisamos algo que ya no revisamos.
¿Y lo que ya está escrito?
Estas reglas protegen lo que escribas a partir de ahora. Para lo que ya tienes, pasa el escáner: te dice qué hay y cómo arreglarlo.
Analizar mi código