Ketrion / Reglas

Haz que tu asistente piense en seguridad antes de escribir código.

Ketrion genera directivas adaptadas a tu stack para que tu asistente evite errores comunes de seguridad mientras programa.

51directivas
instrucciones para tu asistente
10áreas de seguridad
de autorización a fiabilidad
109reglas las respaldan
las del escáner de Ketrion
Configurar mis reglas

Gratis y sin registro. Se genera en tu navegador.

Configurar

¿De qué está hecho tu proyecto?

No sale de tu navegador: ni lo que pegues aquí ni el archivo que salga llegan a ningún servidor nuestro.

O márcalo a mano:

Asistente

¿Qué asistente usas?

Cada uno busca su archivo en un sitio distinto. El contenido es el mismo.

Archivo resultante

CLAUDE.md

En la raíz del proyecto · Claude Code y Claude Desktop

Directivas
51
Líneas
119
Adaptado a
nada todavía — salen las 51 directivas
# 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.

Explora lo que Ketrion le enseña a tu asistente

39 de las 51 directivas están vinculadas directamente a controles concretos del escáner de Ketrion, y una prueba automática comprueba que esos controles existen. Las 12 restantes no tienen actualmente un control del escáner asociado en este catálogo.

Categorías

Directivas

51 directivas

Detalle

Comprueba la sesión en el servidor

auth-sesionAutorizació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.

Comprobación en el escáner

Esta directiva no tiene actualmente un control del escáner asociado.

Esto es lo que Ketrion detecta en código real

Las directivas intentan evitar el fallo antes de que exista. El escáner comprueba el código que ya está escrito.

Código vulnerable

app/api/pedidos/route.js
const id = new URL(req.url).searchParams.get("id");const rows = await db.query("SELECT * FROM pedidos WHERE id = " + id);

Ketrion detecta

Altaroute.js:2

Consulta SQL construida con concatenación

La consulta SQL se arma pegando variables con + o con plantillas. Un atacante puede inyectar comandos y leer o borrar tu base de datos (inyección SQL).

Cómo se arregla

Usa consultas preparadas / parámetros (por ejemplo db.query('... WHERE id = $1', [id])) en vez de concatenar texto.

Regla del escáner
sql-string-concat

Resultado generado por una regla real del escáner de Ketrion, ejecutada sobre el código de la izquierda.

La seguridad no termina cuando generas el archivo.

Ketrion intenta prevenir errores antes de que se escriban, revisa el código que ya existe y puede seguir vigilando después del lanzamiento.

  1. Evitar
  2. Revisar
  3. Vigilar

Evitar

Antes de escribir

Las directivas reducen errores antes de escribir código.

  • 51 directivas, filtradas por tu stack
  • Las lee tu asistente en cada petición

Las reglas no sustituyen un análisis del código real.

Generar mi archivo →

Revisar

Cuando el código ya existe

El escáner revisa el código real y señala lo que encuentra.

  • 109 reglas sobre el código
  • Archivo y línea de cada hallazgo

Un análisis puntual no sustituye la vigilancia posterior.

Escanear código, sin cuenta →

Vigilar

Después de publicar

Con un repositorio o un dominio conectado, Ketrion vuelve a mirar solo.

  • El bot revisa los pull requests y comenta los hallazgos en las líneas añadidas
  • La auditoría repasa el repositorio entero cada 28 días
  • 26 comprobaciones del dominio, una vez por semana
  • Te escribe solo si hay algo que decir

Hay que conectar el repositorio o el dominio primero; no se enciende solo.

Ver cómo funciona la vigilancia →

De dónde salen estas reglas

Están escritas en imperativo, para que las lea tu asistente antes de escribir código — no para describir un hallazgo después.

Cuando una directiva está vinculada a controles del escáner, esos vínculos se comprueban automáticamente: si mañana se retira una regla del motor, esta página deja de citarla en vez de quedarse prometiendo algo que ya no existe.