Todos los posts

· 8 min de lectura

Un agente de seguridad que revisa tu código antes de cada commit

Cómo crear un subagente de Claude Code que revisa el diff en staged y un hook pre-commit que bloquea el commit si encuentra secretos o inyección SQL. Con los archivos reales y dónde falla.

En esta página
  1. Qué es un subagente en Claude Code
  2. Paso 1: crear el agente
  3. Paso 2: probarlo a mano
  4. Paso 3: el hook pre-commit
  5. Instalarlo
  6. Qué pasó en la prueba
  7. Dónde falla
  8. Lo que me llevo

Recursos del video

Archivos del episodio, listos para copiar o descargar.

  • Agente

    security-reviewer · reviewer.md

    Subagente de Claude Code que revisa el diff en staged y termina con VERDICT: PASS o VERDICT: BLOCK.

    Instalar en.claude/agents/reviewer.md

    Ver contenido
    ---
    name: security-reviewer
    description: Revisa cambios de código buscando vulnerabilidades de seguridad antes de un commit o PR. Úsalo cuando haya un diff para revisar.
    tools: Read, Grep, Glob, Bash
    ---
    
    Eres un revisor de seguridad de aplicaciones. Revisa SOLO los cambios que te pasen
    (el diff), usando el resto del repo únicamente como contexto.
    
    Busca:
    
    - Secretos o credenciales en el código (API keys, tokens, contraseñas, llaves privadas)
    - Inyección SQL, de comandos o de rutas
    - Input del usuario sin validar o sin escapar (XSS, deserialización insegura)
    - Autenticación o autorización rota o eliminada
    - Datos sensibles en logs o mensajes de error
    - Dependencias nuevas o sospechosas
    
    Reglas:
    
    - No inventes. Si no estás seguro, márcalo como "Posible" y explica qué falta para confirmarlo.
    - No comentes estilo ni rendimiento.
    - Por cada hallazgo: archivo:línea, severidad (CRÍTICA/ALTA/MEDIA/BAJA), por qué es un problema y cómo arreglarlo.
    - Si no hay hallazgos, dilo en una línea.
    
    La última línea de tu respuesta debe ser exactamente una de estas:
    VERDICT: BLOCK (si hay al menos un hallazgo CRÍTICO o ALTO)
    VERDICT: PASS (en cualquier otro caso)
    
  • Hook

    pre-commit · pre-commit

    Hook de Git que le pasa el diff en staged al agente y detiene el commit si responde BLOCK. Después de copiarlo: chmod +x .git/hooks/pre-commit.

    Instalar en.git/hooks/pre-commit

    Ver contenido
    #!/usr/bin/env bash
    # Revisión de seguridad con Claude Code antes de cada commit
    
    DIFF=$(git diff --cached --unified=5)
    [ -z "$DIFF" ] && exit 0
    
    REPORT=$(printf '%s' "$DIFF" | claude -p \
      "Revisa este diff en staged según tus instrucciones." \
      --agent security-reviewer \
      --allowedTools "Read,Grep,Glob")
    
    echo "$REPORT"
    
    if printf '%s' "$REPORT" | tail -n 1 | grep -q "VERDICT: BLOCK"; then
      echo "❌ Commit bloqueado por security-reviewer. Revisa el reporte."
      exit 1
    fi
    

Este archivo tiene dos problemas: una API key escrita directo en el código y una consulta SQL armada con un f-string. Hice git add y git commit, y Git lo aceptó sin decir una palabra.

import sqlite3

STRIPE_KEY = "sk_live_FAKE_0000000000000003"  # ficticia

def get_user(username):
    conn = sqlite3.connect("app.db")
    query = f"SELECT * FROM users WHERE name = '{username}'"
    return conn.execute(query).fetchone()

La key es falsa, pero si fuera real ya estaría en el historial, y en cuanto hagas push la puede ver cualquiera. Así se filtran las credenciales: no porque nadie sepa que no se hace, sino porque a las once de la noche nadie revisa su propio diff. Lo uso a diario: un agente de Claude Code que revisa lo que está en staged justo antes del commit y lo detiene si hay algo grave.

El video está en youtu.be/Cmw1km8dR5U y los dos archivos están en el repo del video. Aquí va el paso a paso y, al final, dónde falla.

Qué es un subagente en Claude Code

Un subagente es un archivo Markdown. Arriba lleva un frontmatter con el nombre, una descripción de cuándo usarlo y las herramientas que tiene permitidas. Abajo van las instrucciones: su rol, qué buscar y cómo responder.

¿Por qué esto y no escribir «revisa mi código» cada vez? Por tres razones. Las instrucciones son siempre las mismas, así que la revisión es consistente. Puedes limitarle las herramientas. Y se puede invocar desde un script, que es lo que nos permite engancharlo a Git.

Paso 1: crear el agente

Dentro del proyecto creo .claude/agents/ y adentro un archivo, reviewer.md. El archivo se llama reviewer.md, pero el agente se identifica por el campo name, que es security-reviewer. Ese es el nombre que usa el hook más adelante.

Este es el archivo completo, tal cual lo usé:

---
name: security-reviewer
description: Revisa cambios de código buscando vulnerabilidades de seguridad antes de un commit o PR. Úsalo cuando haya un diff para revisar.
tools: Read, Grep, Glob, Bash
---

Eres un revisor de seguridad de aplicaciones. Revisa SOLO los cambios que te pasen
(el diff), usando el resto del repo únicamente como contexto.

Busca:

- Secretos o credenciales en el código (API keys, tokens, contraseñas, llaves privadas)
- Inyección SQL, de comandos o de rutas
- Input del usuario sin validar o sin escapar (XSS, deserialización insegura)
- Autenticación o autorización rota o eliminada
- Datos sensibles en logs o mensajes de error
- Dependencias nuevas o sospechosas

Reglas:

- No inventes. Si no estás seguro, márcalo como "Posible" y explica qué falta para confirmarlo.
- No comentes estilo ni rendimiento.
- Por cada hallazgo: archivo:línea, severidad (CRÍTICA/ALTA/MEDIA/BAJA), por qué es un problema y cómo arreglarlo.
- Si no hay hallazgos, dilo en una línea.

La última línea de tu respuesta debe ser exactamente una de estas:
VERDICT: BLOCK (si hay al menos un hallazgo CRÍTICO o ALTO)
VERDICT: PASS (en cualquier otro caso)

Hay tres decisiones de diseño. Le pido que no invente: si duda, que diga «Posible» en vez de presentarlo como hallazgo. Le exijo archivo, línea, severidad y arreglo, para que el reporte sea accionable. Y la última línea es siempre VERDICT: BLOCK o VERDICT: PASS. Esa línea es la que va a leer el script de Git, así que tiene que ser predecible.

Fíjate también en tools: Read, Grep, Glob, Bash. Ese Bash lo discuto más abajo, porque es lo que menos me gusta de mi propio archivo.

Paso 2: probarlo a mano

Antes de automatizar nada, lo probé a mano. Como el repo de la demo todavía no tenía Git, primero git init, luego git add y abrí Claude Code con la instrucción de correr el agente sobre lo que está en staged.

Esto fue lo que pasó en la grabación: Claude detectó los cambios, lanzó un subagente (el security-reviewer) y llegó a la conclusión de que el commit debía bloquearse. Explicó por qué: había una llave de Stripe en el código, y aunque la mía decía FAKE, tenía formato de llave real. También marcó la inyección SQL, indicó cómo arreglarla con una consulta parametrizada y sugirió guardar el secreto en una variable de entorno.

Si aquí no encuentra lo que esperas, este es el momento de ajustar las instrucciones, no después de engancharlo a Git.

Paso 3: el hook pre-commit

Git tiene hooks: scripts que corren en momentos concretos. pre-commit corre justo antes de crear el commit, y si termina con error, el commit no se hace. Este es el script completo, en .git/hooks/pre-commit:

#!/usr/bin/env bash
# Revisión de seguridad con Claude Code antes de cada commit

DIFF=$(git diff --cached --unified=5)
[ -z "$DIFF" ] && exit 0

REPORT=$(printf '%s' "$DIFF" | claude -p \
  "Revisa este diff en staged según tus instrucciones." \
  --agent security-reviewer \
  --allowedTools "Read,Grep,Glob")

echo "$REPORT"

if printf '%s' "$REPORT" | tail -n 1 | grep -q "VERDICT: BLOCK"; then
  echo "❌ Commit bloqueado por security-reviewer. Revisa el reporte."
  exit 1
fi

Hace cuatro cosas:

  1. Saca el diff de lo que está en staged con git diff --cached. Si está vacío, sale con éxito y no gasta nada.
  2. Abre Claude en modo headless (claude -p), le pasa el diff por stdin y le pide usar el agente security-reviewer. Con --allowedTools "Read,Grep,Glob" indico qué herramientas debe usar.
  3. Imprime el reporte completo para que lo leas.
  4. Mira la última línea. Si contiene VERDICT: BLOCK, sale con exit 1 y Git cancela el commit.
PASS BLOCK git commit pre-commit hook agente security-reviewer VERDICT commit creado commit cancelado

Instalarlo

Los dos archivos están en el repo del video. Copia reviewer.md a .claude/agents/ en tu proyecto, y el script a .git/hooks/pre-commit. Después dale permiso de ejecución:

chmod +x .git/hooks/pre-commit

Recuerda que .git/hooks/ no se versiona: cada persona del equipo tiene que instalarlo en su clon.

Qué pasó en la prueba

Hice un cambio y volví a hacer commit. El hook se tardó un poco, devolvió el reporte y el commit quedó bloqueado por las credenciales en el código. Seguí las sugerencias del reporte: la key pasó a una variable de entorno y la consulta a parámetros.

import os
import sqlite3

STRIPE_KEY = os.environ["STRIPE_KEY"]

def get_user(username):
    conn = sqlite3.connect("app.db")
    return conn.execute("SELECT * FROM users WHERE name = ?", (username,))

Volví a agregar y a hacer commit, y esta vez pasó. Eso es lo que quería comprobar: el mismo tipo de commit que Git aceptaba sin chistar ahora se detiene, y el arreglo lo deja pasar.

Dónde falla

Esto no es infalible, y prefiero decirlo yo.

Es un modelo de lenguaje. Puede dar falsos positivos y, peor, puede no ver algo. El reporte sobre el mismo diff puede cambiar de una ejecución a otra; no lo medí, pero con un modelo no lo descartaría. Para secretos, ponle al lado una herramienta determinista como gitleaks, que busca patrones y no se cansa. El agente es la segunda capa, la que entiende contexto.

git commit --no-verify se salta el hook. Es una ayuda personal, no un control obligatorio. Si necesitas imponerlo, tiene que vivir también en CI.

Tu diff sale de tu máquina. El código en staged se manda a la API del modelo. Lo noté en la propia grabación: el hook corre Claude en headless y el código va hacia afuera. Si trabajas con código que no puede salir de tu entorno, revisa la política de datos antes de activarlo.

Tarda y cuesta. En la prueba se notó la espera. No medí segundos ni costo, así que no te doy números. Con commits grandes tarda más. Commits pequeños ayudan, y no solo para esto.

El agente tiene Bash y el hook no lo permite. Mi reviewer.md publicado lista Bash en tools, mientras el hook pasa --allowedTools "Read,Grep,Glob". La intención del hook es que el revisor solo lea. Pero el archivo del agente declara más de lo que debería, y no verifiqué en la grabación qué ocurre exactamente si el modelo intenta usar Bash en modo -p. Para un revisor que solo lee, Bash sobra. Mi recomendación es quitarlo del frontmatter: un agente que solo analiza código no debería poder correr comandos, y esa garantía no debe depender de un flag en otro archivo.

Dos detalles del script. Si claude falla o no hay red, REPORT queda vacío, no hay VERDICT: BLOCK y el commit pasa. Y como el hook mira solo la última línea, si el modelo agrega texto después del veredicto, el bloqueo no se activa. Si te preocupa, cambia tail -n 1 por grep -q "^VERDICT: BLOCK" sobre todo el reporte.

Lo que me llevo

Un Markdown con instrucciones claras, un hook de pocas líneas y tienes un segundo par de ojos en cada commit. No reemplaza una revisión de seguridad de verdad ni a gitleaks, pero atrapa justo lo que se te escapa tarde en la noche. Y no delegues todo a la IA: valida tú lo que lees y entiende el arreglo, porque un error que no entiendes hoy no lo vas a poder corregir mañana.

Lo armé para validar secretos e inyección SQL, pero lo puedes adaptar a tus reglas. Pruébalo en tu repo y cuéntame en los comentarios del video qué le agregaste y qué fue lo primero que te atrapó.