Reglas de Semgrep que los desarrolladores mantienen: SAST que no se acaba desactivando
Casi todo el análisis estático muere de ruido. La forma de mantener vivo el SAST es empezar con un conjunto minúsculo de reglas de alta precisión, escribir reglas para los errores de tu propio código y escanear el diff, no el repositorio.
El modo de fallo del análisis estático de seguridad es siempre el mismo. Se activa la herramienta con todas las reglas, el primer escaneo informa de 4.000 hallazgos sobre un código que nadie tiene tiempo de arreglar, el equipo los marca todos como "no se va a corregir" y la herramienta se convierte en una casilla para el auditor.
Semgrep sobrevive a esto mejor que la mayoría, no porque sus reglas sean más listas sino porque escribir las tuyas es realista. Una regla es un patrón que se parece al código con el que casa.
Escanea el diff, no el repositorio
La decisión de configuración más importante: en las pull requests, escanea solo lo que ha cambiado.
semgrep ci --baseline-commit "$(git merge-base origin/main HEAD)"
Esto convierte una deuda impagable en una pagable. Los 4.000 hallazgos históricos se quedan en un backlog que trabajas a conciencia; la pull request solo tiene que estar limpia en las doce líneas que toca. Un desarrollador al que le piden arreglar un problema en código que acaba de escribir lo arregla. Un desarrollador al que le piden arreglar un problema en código de 2019 escrito por alguien que ya no está abre un ticket.
Escanea el repositorio completo de forma programada, por separado, y manda eso al backlog de seguridad.
Empieza por la precisión, no por la cobertura
Arranca con p/default o un conjunto por lenguaje, y luego borra toda regla que haya producido un falso positivo dos veces. Un conjunto de treinta reglas que aciertan el 95 por ciento de las veces cambia comportamientos. Un conjunto de novecientas reglas que aciertan el 40 por ciento se silencia.
Mídelo. Si se descarta más de un hallazgo de cada cinco, el conjunto está demasiado suelto y estás gastando un crédito con el equipo que vas a necesitar más adelante.
Las reglas que merece la pena escribirse uno mismo
Las reglas genéricas cazan fallos genéricos. Los hallazgos que de verdad importan en un código concreto salen de las convenciones de ese código, y esas hay que escribirlas. Son cortas:
rules:
- id: sql-crudo-en-handler
languages: [python]
severity: ERROR
message: >-
Construye consultas con la capa de repositorio (db.query), no con
interpolación de cadenas. Ver docs/db.md.
patterns:
- pattern-either:
- pattern: cursor.execute(f"...")
- pattern: cursor.execute("..." % ...)
- pattern: cursor.execute("..." + ...)
paths:
include: ["app/api/**"]
Las reglas propias de más valor casi siempre van de tus invariantes, no del OWASP Top 10:
- Un manejador de ruta nuevo registrado sin el decorador de autorización que usa el resto del código.
- Una llamada al cliente HTTP interno que no pasa el contexto de inquilino.
- Uso directo de
os.environpara un secreto en vez del ayudante de secretos que audita las lecturas. - Una llamada de log que recibe un objeto de petición o de usuario completo, que es como los datos personales acaban siete años en la agregación de logs.
- Un recurso de IaC nuevo sin la etiqueta de la que depende el reparto de costes.
Cada una es diez líneas de YAML y caza una clase de error que ningún conjunto de reglas de catálogo sabe que existe. Escríbelas cuando corrijas el fallo: todo postmortem de incidente debería acabar con un test o con una regla de Semgrep.
Que el mensaje sea la corrección
Un hallazgo que dice "CWE-89: SQL Injection" no le enseña nada a nadie. Un hallazgo que dice "usa db.query(), ver docs/db.md, y aquí está la llamada equivalente" consigue el cambio en el mismo commit. El campo message es la interfaz entre el equipo de seguridad y quien programa; escríbelo como documentación, con enlace.
Dónde encaja con lo demás
Semgrep ve el código fuente. No ve lo que está desplegado, ni qué versiones de biblioteca se resuelven de verdad en el fichero de bloqueo, ni qué expone la aplicación en marcha. Complementa al escaneo de dependencias y al escaneo dinámico en vez de solaparse con ellos, y los tres se dejan los fallos de autorización.
Además es genuinamente bueno con infraestructura como código, Dockerfiles y manifiestos de Kubernetes, algo que vale la pena habilitar aunque no lo uses para código de aplicación: la sintaxis de patrones maneja YAML, y "nada de hostNetwork: true fuera del namespace de ingress" es una regla de dos líneas.
Para el auditor, y con honestidad
Tanto SOC 2 como ISO 27001 van a preguntar si haces análisis estático de tu código, y un Semgrep configurado, en marcha y con un proceso de triaje evidenciado responde a eso con limpieza, siempre que el "triaje" sea real y no un descarte masivo. Guarda el registro de qué se descartó y por qué; ese registro es lo que convierte una herramienta en un control. Cómo funciona ese mapeo conviene planificarlo antes de la auditoría, no durante.
El enfoque honesto para el equipo es otro: esto no va del certificado, va de cazar en revisión la clase de error que ya sabes que cometes. Ese enfoque es además lo que mantiene vivo el conjunto de reglas cuando la auditoría termina.