Um revisor de segurança de IA read-only
O post anterior explicou o que são subagents. Esse aqui é um walkthrough de um que eu realmente uso, dia a dia, em toda mudança que toca algo sensível: o subagent de security-review.
É um único arquivo Markdown. Vinte linhas de frontmatter e prompt, mais uma pasta de documentos de regras que ele consulta. O aparato todo cabe num pequeno repo wrapper que eu adiciono ao workspace junto com qualquer projeto em que esteja trabalhando. Uma vez que está lá, pedir pro agente pai “revisa isso por segurança” spawna o subagent, que lê as regras relevantes, compara a implementação contra elas, e retorna um relatório ranqueado por severidade com citações.
Aqui vai o que tem dentro, por que funciona, e as escolhas que distinguem um revisor de segurança útil de um barulhento.
O arquivo de definição
O subagent inteiro vive num único arquivo:
---
name: org-standards-security-review
description: >
Security and privacy review against the org standards in
standards/rules/. Use when implementing or reviewing auth,
APIs, data handling, uploads, crypto, sessions, or infra.
Use proactively for sensitive features.
model: inherit
readonly: true
---
You review work against the org standards in `standards/rules/`.
## When invoked
1. From the parent prompt, infer scope (features, files, or APIs).
2. Identify which topics under `standards/rules/` apply.
Examples: CSRF and auth tokens for state-changing HTTP;
parameterized DB queries for SQL; hardcoded-secrets and
sensitive-data-* for credentials and logs;
file-upload-validation for uploads.
3. Read the relevant `standards/rules/*.md` files and compare
the implementation to the written rules. Cite filenames in
findings.
4. Report by severity (Critical / High / Medium / Low). Each
finding must reference a rule file.
Do not invent stricter requirements than the written rules.
If the rules are silent or ambiguous, say so explicitly.
Isso é tudo. Umas vinte e poucas linhas. As escolhas importantes são a description, o readonly: true, e a guardrail no final.
A description não é decorativa. É a superfície de disparo que o agente pai usa pra reconhecer intenção: auth, APIs, data handling, uploads, crypto, sessions, infra. A frase “Use proactively for sensitive features” diz ao pai que ele não precisa esperar eu pedir.
readonly: true é a linha que muda como eu uso isso. Um subagent read-only pode ler, buscar, navegar e resumir, mas o resultado é puramente um relatório. Não preciso revisar o que ele vai fazer antes de fazer; apenas reviso o que ele encontrou. Pra trabalho de revisão, esse é o default certo.
O corpo — o que o subagent realmente faz
O system prompt é curto de propósito. Quatro passos numerados, mais uma guardrail.
Passo dois: identificar quais regras se aplicam. É aqui que o subagent mostra seu valor. O diretório de regras tem dezenas de arquivos, cada um cobrindo um tópico — CSRF, auth tokens, parameterized DB queries, tratamento de secrets, file uploads, dependency scanning, e assim por diante. O agente pai não quer carregar tudo isso. O subagent — tendo delimitado a mudança — pega os relevantes.
O prompt dá exemplos pra ancorar: “CSRF and auth tokens for state-changing HTTP; parameterized DB queries for SQL; hardcoded-secrets and sensitive-data- for credentials and logs.”* São sementes. O subagent extrapola a partir delas com base no que vê na mudança.
Passo três: ler as regras e comparar. O subagent abre cada arquivo de regra relevante e compara a implementação. O detalhe crucial é “Cite filenames in findings.” Todo finding que o subagent reporta tem que apontar pra uma regra específica que está citando. Essa citação é o que torna a saída verificável. Eu posso abrir a regra que o subagent referenciou, ler eu mesmo, e decidir se o finding está correto.
A guardrail no final — “Do not invent stricter requirements than the written rules. If the rules are silent or ambiguous, say so explicitly” — é a linha que previne o subagent de sair dos trilhos. Sem ela, o subagent às vezes produz findings baseados no que ele geralmente acredita sobre segurança, não no que está realmente nos arquivos de regra. Isso é pior do que inútil: mina as citações e infla o relatório com afirmações que eu não consigo rastrear de volta a nada.
O diretório de regras que o subagent lê
O comportamento do subagent é tão bom quanto as regras que ele consulta. O diretório se parece mais ou menos com isso:
standards/rules/
├── auth-tokens-expiration.md
├── csrf-protection.md
├── csp-headers.md
├── dependency-vulnerability-scanning.md
├── error-handling-with-no-silent-failures.md
├── file-upload-validation.md
├── hardcoded-secrets.md
├── input-sanitization.md
├── least-privilege-principle.md
├── parameterized-db-queries.md
├── password-requirements.md
├── predictable-ids.md
├── rate-limiting.md
├── secure-session-management.md
├── security-events-logging.md
├── security-misconfiguration.md
├── sensitive-data-in-client-side.md
├── sensitive-data-in-errors.md
├── sensitive-data-in-logs.md
├── sensitive-data-in-querystring.md
├── ssrf-protection.md
└── validate-all-inputs.md
Cada arquivo cobre um tópico, em Markdown simples, com dos e don’ts concretos. Não são romances. Cada arquivo tem uma ou duas páginas: uma declaração curta da política, o raciocínio, exemplos de código conforme e não-conforme, armadilhas comuns.
O subagent adora esse formato porque pode pegar só o que precisa. Uma mudança numa query SQL carrega parameterized-db-queries.md e pula o resto. Uma feature de file-upload carrega file-upload-validation.md e validate-all-inputs.md. O contexto do subagent permanece pequeno, focado e citável.
(Vou escrever mais sobre como o diretório de regras em si funciona no post de amanhã — incluindo como é um git submodule compartilhado entre projetos, e por que torná-lo portável importa.)
Como a saída realmente se parece
Uma execução típica, numa hipotética pequena mudança de auth, volta parecendo assim:
## Security review — auth.py changes
**Scope:** apps/api/routes/auth.py, apps/api/services/sessions.py
**Rules consulted:** auth-tokens-expiration.md, secure-session-management.md,
hardcoded-secrets.md, security-events-logging.md
#
## Critical — none
#
## High
1. **apps/api/services/sessions.py:42** — Session token does not
expire. The current implementation issues tokens with no `exp`
claim. See `auth-tokens-expiration.md` §"All tokens must have
a maximum lifetime." Recommend adding an explicit `exp` based
on the policy in that rule.
#
## Medium
2. **apps/api/routes/auth.py:18** — Failed login attempts are not
logged. See `security-events-logging.md` §"Authentication
events." Recommend adding a structured log entry on failure,
without the credential value.
#
## Low
3. **apps/api/routes/auth.py** — Module-level constant
`DEFAULT_REDIRECT` is defined but never used. Not a security
issue; cleanup opportunity. Not cited from any rule.
#
## Notes
- The rules are silent on session token storage location
(cookie vs localStorage). The implementation uses an
HTTP-only cookie, which aligns with general best practice
but is not explicitly required by the consulted rules.
Repare no que essa saída está fazendo.
Cada finding cita um arquivo de regra específico. Nenhum finding é “de boa prática geral” — esses são explicitamente chamados como notes, não findings. Essa separação é o que mantém o relatório confiável.
A severidade é honesta. Um finding “low” é genuinamente low. A tentação, com qualquer ferramenta de revisão, é inflar findings pra parecer detalhista. A guardrail no prompt — “Do not invent stricter requirements than the written rules” — resiste a isso.
A seção “Notes” captura ambiguidade. Onde as regras são silenciosas, o subagent diz isso. Não finge que o silêncio é um finding. Apenas anota a lacuna. Eu leio essas notes e decido se devo atualizar as próprias regras.
A saída é curta. Sem introdução, sem recapitulação da mudança, sem encorajamento, sem despedida cordial. Headers de severidade, findings numerados, citações, notes. Todo o resto foi cortado.
O que isso pega que eu não pegaria
Três categorias de finding consistentemente voltam desse subagent que eu teria perdido de outra forma.
A coisa que eu esqueci de fazer. Um token sem expiração. Uma chamada de log que inclui uma credencial. Um parâmetro de query que guarda um session ID. Todas são individualmente óbvias, e eu individualmente esqueço pelo menos uma delas por mudança não-trivial. O subagent não cansa.
A coisa que as regras dizem mas eu não lembro. Algumas regras têm requisitos numéricos específicos — comprimento mínimo de senha, lifetime máximo de token, headers obrigatórios. Não consigo manter todos esses na cabeça. O subagent lê a regra, vê o número, e me diz quando a implementação está fora.
A coisa que ninguém tinha notado antes. Às vezes um finding que o subagent traz aponta pra uma lacuna real no código existente, não na mudança. Esses são os findings mais valiosos, porque o processo de revisão humana estava deixando passar. O subagent não tem incentivo pra educadamente ignorar.
O que não pega
Não é mágica.
Vulnerabilidades de nível de lógica. Um fluxo de auth corretamente implementado que tem uma premissa de business-logic falha — “qualquer um com a URL pode fazer upgrade do plano” — não vai aparecer. As regras não cobrem business logic; o subagent não inventa regras. Revisão de lógica ainda é dos humanos.
Problemas de nível de design. “Você não deveria estar guardando isso num banco de dados” é uma conversa de design. O subagent vai verificar que o storage que você escolheu segue as regras; não vai dizer que o design está errado.
Coisas que as regras não cobrem. O único superpoder do subagent é consultar fielmente as regras. Se as regras estão com uma categoria faltando, o subagent vai ser silencioso naquela categoria. O remédio não é pedir pro subagent ser mais esperto; é escrever a regra que falta.
Por que funciona como subagent especificamente
Isso funciona como subagent, e não como rule ou skill, porque o trabalho é isolado, pesado em leitura, especializado, e retorna um relatório estruturado pequeno. Esse era o formato do post anterior; aqui é a instância concreta.
Esse é o padrão todo. A instância não é “mais inteligente que o pai.” É o mesmo modelo numa configuração mais focada. A configuração é o que faz o trabalho.