Security Review — validar, apontar e corrigir
Premissa: código gerado por IA é código não confiável até passar por validação, testes e
revisão de segurança. Isso vale para o código que você está revisando e para o que você mesmo
acabou de escrever.
| Modo |
Quando |
Escopo |
| Auditoria |
"revisa a segurança do projeto" |
o repositório inteiro |
| Revisão de PR |
"revisa a segurança desse PR/diff" |
só o que mudou (git diff develop...HEAD) |
| Correção |
"corrige o que encontrou" |
os achados aprovados, um por vez |
Sempre nesta ordem: detectar → escanear → revisar → classificar → reportar → corrigir.
Nunca corrija antes de reportar — a pessoa decide o que entra.
A referência completa (riscos, exemplos, checklists por tema) está em checklist.md nesta
pasta. Este arquivo é o procedimento.
1. Detectar a stack e o que há disponível
ls composer.json package.json requirements.txt pyproject.toml go.mod Dockerfile .env .env.example 2>/dev/null
| Encontrou |
Stack |
Sinais de IA/LLM no projeto |
composer.json |
PHP — Laravel se laravel/framework |
openai-php, anthropic |
package.json |
Node/JS/TS — Express, Next, Nest, Fastify |
openai, @anthropic-ai/sdk, langchain, @pinecone-database, chromadb |
requirements.txt / pyproject.toml |
Python — Django, FastAPI, Flask |
openai, anthropic, langchain, llama-index |
Se houver sinais de LLM, a seção 5 é obrigatória. Verifique também quais scanners estão
instalados (which gitleaks semgrep trivy) — use os que existirem; não instale nada sem
perguntar.
2. Escanear com as ferramentas do ecossistema
Rode o que se aplica e guarde a saída para o relatório:
| Stack |
Dependências (SCA) |
Secrets |
Estático |
| Node |
npm audit --json (ou pnpm audit / yarn audit) |
gitleaks detect --no-git -v |
semgrep --config p/owasp-top-ten --config p/javascript |
| PHP/Laravel |
composer audit --format=json |
idem |
semgrep --config p/php |
| Python |
pip-audit ou pip audit |
idem |
semgrep --config p/python + bandit -r . |
| Qualquer |
— |
git log -p --all -S "sk-" | head (secrets no histórico) |
— |
Sem gitleaks, faça a busca básica de secrets:
grep -rnE "(sk-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|-----BEGIN (RSA |EC )?PRIVATE KEY|password\s*=\s*['\"][^'\"]{6,}|api[_-]?key\s*[:=]\s*['\"][^'\"]{10,})" \
--include=*.{js,ts,php,py,env,json,yml,yaml} --exclude-dir={node_modules,vendor,.git,dist,build} .
Confira também se .env está no .gitignore e se nunca foi commitado
(git log --all --oneline -- .env).
3. Procurar os padrões perigosos no código
Grep é triagem, não veredito: abra cada match e confirme se a entrada é controlada pelo
usuário e se existe validação/escape antes. Só reporte o que confirmou.
Injection (SQL, comando, template)
| Stack |
Padrão suspeito |
Seguro |
| Node |
query(`...${ · query("..." + |
placeholders ? / $1, query builder com bindings |
| Laravel |
DB::raw(, whereRaw(, selectRaw(, DB::statement( com variável |
where('col', $v), whereRaw('col = ?', [$v]) |
| Python |
f"SELECT ... {, "..." % (, .format( em SQL |
parâmetros do driver, ORM |
| Qualquer |
exec(, spawn( com shell, shell_exec(, system(, passthru(, subprocess(..., shell=True) |
lista de argumentos sem shell; allowlist do comando |
| Qualquer |
eval(, new Function(, unserialize(, pickle.loads( em dado externo |
não usar; JSON com schema |
XSS e saída para o navegador
| Padrão suspeito |
Seguro |
innerHTML =, outerHTML =, document.write(, insertAdjacentHTML( com dado externo |
textContent; sanitizar com DOMPurify se HTML for inevitável |
React dangerouslySetInnerHTML |
idem |
Blade {!! $x !!} |
{{ $x }} (escapado) |
Django/Jinja |safe, mark_safe(, {% autoescape off %} |
escape padrão |
res.send( / echo de entrada sem escape em HTML |
template com escape; Content-Type correto |
Autorização, IDOR e acesso
- Rota que recebe
id na URL e busca direto (find($id), findById(req.params.id)) sem
checar o dono (where user_id = auth(), policy, guard) → IDOR.
- Laravel:
$request->all() em create/update sem $fillable/$guarded → mass assignment.
- Middleware de auth ausente em rota que muda estado; checagem só no front-end.
- Multi-tenant: consulta sem filtro de
tenant_id.
Upload, arquivos e rede
- Path traversal: caminho montado com entrada do usuário (
path.join(base, req.query.file),
storage_path($name)) sem normalizar e conferir que ficou dentro da base.
- Upload: extensão/MIME não validados, arquivo servido do mesmo domínio, nome original mantido.
- SSRF:
fetch(url), axios.get(url), Http::get($url), file_get_contents($url),
requests.get(url) com URL vinda do usuário, sem allowlist de domínio e bloqueio de IP privado.
- CORS
* com credenciais; cookies sem HttpOnly/Secure/SameSite.
- Redirect com destino vindo da query (
redirect($request->url)) sem allowlist.
Inclusão de arquivos (LFI / RFI)
include/require/include_once com caminho vindo de entrada (include($_GET['page']),
require "pages/$name.php") → LFI; com allow_url_include=On, vira RFI (código remoto).
- Node:
require(userInput), import(userInput), res.render(req.query.view); Python:
__import__(name), importlib.import_module(user), open(path) com caminho externo.
- Seguro: allowlist de nomes válidos (
['home','about']), nunca o caminho; allow_url_include=Off.
CSRF e clickjacking
| Padrão suspeito |
Seguro |
Laravel: rota em web.php fora do middleware web, ou $except no VerifyCsrfToken com rotas de estado |
manter @csrf nos forms e o middleware ativo; exceções só para webhooks com assinatura própria |
Node: POST/PUT/DELETE com sessão por cookie e sem token CSRF (csurf/csrf-csrf/double-submit) |
token por sessão ou SameSite=Lax/Strict e checagem de Origin |
Django: @csrf_exempt em view que muda estado; CsrfViewMiddleware removido |
manter o middleware; exemption só com justificativa |
Estado mudado via GET (/delete?id=) |
mutação só em POST/PUT/DELETE |
Sem X-Frame-Options nem Content-Security-Policy: frame-ancestors |
frame-ancestors 'self' (ou 'none') — clickjacking; helmet no Node, SecurityHeaders/middleware no Laravel |
Sem Strict-Transport-Security, X-Content-Type-Options: nosniff, Referrer-Policy |
headers de segurança no servidor ou no middleware |
Autenticação, sessão e bypass
- Bypass: rota que muda estado sem middleware de auth; auth checada só no front;
if (user)
onde user pode vir do cliente (header/cookie forjável, X-User-Id); endpoint de debug/admin
esquecido (/debug, /admin sem guard); ordem errada de middleware (handler antes do auth).
- JWT: sem expiração; sem verificar assinatura (
alg: none, verify: false, decode() em vez de
verify()); segredo fraco/hardcoded; aceitar token de outro emissor/audience; sem revogação.
- Brute force: sem rate limit em login/reset/OTP/2FA; sem bloqueio ou atraso progressivo após
tentativas; enumeração de usuário por mensagem diferente ("senha errada" vs "usuário não existe").
- Session fixation: id de sessão não regenerado no login (
session()->regenerate(),
req.session.regenerate(), request.session.cycle_key()); id aceito pela URL ou por cookie
pré-existente.
- Session hijacking: cookie sem
HttpOnly/Secure/SameSite; sessão sem expiração ou sem
invalidação no logout e na troca de senha; token de sessão em URL, log ou localStorage exposto a XSS.
- Comparação de token/hash com
== em vez de comparação em tempo constante.
- Senha com hash fraco (MD5/SHA1) ou sem salt;
bcrypt/argon2 é o esperado.
Condições de corrida (race condition)
- Padrão ler → decidir → gravar sem lock ou transação em recurso disputado: saldo, estoque,
cupom de uso único, "já votou?", limite de uso — permite gastar/usar duas vezes com requisições
simultâneas.
- Seguro:
SELECT ... FOR UPDATE / lockForUpdate() dentro de transação; UPDATE ... WHERE saldo >= ?
atômico; constraint UNIQUE para "uma vez só"; chave de idempotência em operações de pagamento.
- Teste negativo: disparar N requisições em paralelo e conferir que só uma passa.
Exposição de dados sensíveis, erros e logs
- Stack trace, query ou payload retornados ao cliente (
APP_DEBUG=true em produção,
app.use(errorHandler) que vaza err.stack).
- Dado pessoal, token ou senha em log, em URL (
?token=) ou em resposta de API além do necessário
(serializar o model inteiro: return $user, res.json(user) com hash/e-mail/documento).
- Tráfego sem TLS;
.git/, .env, backups ou phpinfo servidos publicamente; listagem de diretório.
- Dado sensível armazenado em claro quando deveria ser hash ou cifrado; sem mascaramento em telas/logs.
4. Dependências e supply chain
Além do audit: versões fixadas e lockfile versionado; pacotes com nome parecido com o de
uma lib conhecida (typosquatting); dependência adicionada só para uma função trivial;
implementação própria de cripto/auth onde existe solução consolidada; imagem Docker
latest ou sem scan (trivy image se houver).
5. Se o projeto usa LLM, RAG ou agentes
Trate como fronteira de confiança nova — o modelo não é camada de segurança.
| Risco |
Como identificar |
Como corrigir |
| Insecure output handling |
saída do modelo vai para query(, exec(, eval(, innerHTML, fetch(url) sem passar por validação |
schema (JSON Schema/zod/Pydantic) → validação → autorização no código → execução controlada |
| Prompt injection |
entrada do usuário ou documento do RAG concatenado ao system prompt; ações disparadas só pelo conteúdo recuperado |
separar instrução de dado; tratar RAG como não confiável; decisão de ação fora do LLM |
| Excessive agency |
tool executar_sql, credencial admin, tool de deleção sem confirmação, sem limite de chamadas |
menor privilégio por tool; autorização própria em cada tool; human-in-the-loop em ação destrutiva; limite de iterações |
| Vazamento via RAG |
vector DB compartilhado sem filtro de tenant_id/user_id no retrieval |
filtrar antes de recuperar; metadados de acesso por documento; remover embeddings de documento excluído |
| Vazamento em logs |
prompt/resposta com PII, token ou documento privado gravados |
logar id da requisição, usuário, tool, decisão de autorização, resultado — não o conteúdo |
| Secrets no prompt |
chave/senha dentro de prompt ou system prompt |
secret manager / variável de ambiente |
| SSRF via tool |
tool HTTP com URL controlada pelo modelo/usuário |
allowlist de domínio, bloqueio de IP privado, timeout, limite de resposta |
Regra de arquitetura a cobrar: INPUT → VALIDATE → AUTHENTICATE → AUTHORIZE → LLM → VALIDATE OUTPUT → BUSINESS RULES → EXECUTE → AUDIT. Nunca INPUT → LLM → EXECUTE.
6. Classificar
| Nível |
Critério |
Exemplos |
| 🔴 Crítica |
explorável remotamente sem autenticação, ou vaza/destrói dados de terceiros |
SQL injection, RCE, secret válido no repo, IDOR em dado de outro usuário, output de LLM executado direto |
| 🟠 Alta |
explorável por usuário autenticado ou com pré-condição plausível |
XSS armazenado, SSRF interno, mass assignment em campo sensível, JWT sem expiração |
| 🟡 Média |
reduz defesa ou exige cadeia de falhas |
CORS permissivo, sem rate limit, cookie sem flags, dependência com CVE sem caminho claro de exploração |
| 🔵 Baixa |
higiene e boas práticas |
log verboso, header de segurança ausente, versão desatualizada sem CVE |
Falso positivo confirmado não entra no relatório. Achado que você não conseguiu confirmar
entra como "a verificar", separado.
7. Reportar
Sempre antes de corrigir. Formato:
## Security Review — <projeto ou PR #N>
Stack: Laravel 11 · Node 20 (front) · usa OpenAI SDK
Ferramentas: composer audit ✓ · npm audit ✓ · gitleaks ✗ (não instalado) · semgrep ✗
### Resumo
🔴 2 · 🟠 3 · 🟡 4 · 🔵 2 · a verificar: 1
### Achados
🔴 SQL injection — app/Http/Controllers/ReportController.php:48
`DB::select("... WHERE status = '$status'")` com $status vindo da query string.
Correção: binding `DB::select('... WHERE status = ?', [$status])`.
🟠 IDOR — routes/api.php:31 + OrderController@show
`Order::findOrFail($id)` sem checar o dono. Correção: `auth()->user()->orders()->findOrFail($id)` ou Policy.
…
### Dependências
composer audit: 1 pacote com CVE (…) · npm audit: 3 high, 12 moderate
### Recomendação
Bloquear merge até resolver as 🔴. As 🟠 entram no mesmo PR ou em card próprio, à escolha do time.
Cada achado tem arquivo:linha, o que é, por que é explorável e a correção proposta. Sem
os quatro, não é achado — é palpite.
8. Corrigir (só depois do OK)
- Um achado por vez, começando pelas 🔴. Peça confirmação antes de mexer em autenticação,
autorização ou cripto — são as mudanças com mais efeito colateral.
- Corrija a causa, não o sintoma: placeholder em vez de escapar a string; policy em vez de
if solto na rota; schema de saída em vez de regex na resposta do LLM.
- Escreva o teste negativo junto com a correção (a entrada maliciosa deve falhar) — é o que
impede a regressão.
- Secret encontrado no repositório: rotacione primeiro, depois remova do código e do
histórico. Remover sem rotacionar não resolve nada.
- Não "arrume" desabilitando a proteção (CSRF off,
verify: false, rejectUnauthorized: false,
eslint-disable em regra de segurança). Isso é achado novo, não correção.
- Se o projeto usa a skill
git-flow-delivery, a correção segue o fluxo dela (branch, commit
com o card, PR). Secret exposto e 🔴 explorável em produção são hotfix.
Mapa de cobertura (Pentest Web)
Para conferir que a revisão não deixou buraco — cada item e onde ele é tratado neste arquivo:
| Vulnerabilidade |
Seção |
| SQL Injection |
Injection |
| Cross-Site Scripting (XSS) |
XSS e saída para o navegador |
| Cross-Site Request Forgery (CSRF) |
CSRF e clickjacking |
| Broken Access Control · IDOR |
Autorização, IDOR e acesso |
| Authentication Bypass · Brute Force · JWT Misconfiguration |
Autenticação, sessão e bypass |
| Session Fixation · Session Hijacking |
Autenticação, sessão e bypass |
| CORS Misconfiguration · Open Redirect |
Upload, arquivos e rede |
| Sensitive Data Exposure |
Exposição de dados sensíveis, erros e logs |
| SSRF · Path Traversal |
Upload, arquivos e rede |
| Local / Remote File Inclusion |
Inclusão de arquivos (LFI / RFI) |
| File Upload · Unrestricted File Upload |
Upload, arquivos e rede |
| Clickjacking |
CSRF e clickjacking |
| Race Condition |
Condições de corrida |
| Prompt Injection · Excessive Agency · Insecure Output · RAG |
Se o projeto usa LLM, RAG ou agentes |
Checklist rápido
Adaptando ao seu time
| O que |
Aqui |
No seu time |
| Ferramentas |
gitleaks, semgrep, trivy se existirem |
as que o CI já roda (Snyk, SonarQube, CodeQL…) |
| Severidade |
4 níveis acima |
o esquema do seu programa de segurança (CVSS, etc.) |
| Política de merge |
bloquear com 🔴 |
o acordo do time |
| Regras extras de domínio |
— |
ex.: idempotência em cobranças, mascaramento de documentos, LGPD/GDPR |
| Onde registrar |
relatório no PR/card |
ferramenta de vulnerabilidades do time |
1---2name: security-review3description: Auditoria de segurança de projetos web (Laravel/PHP, Node/JavaScript/TypeScript, Python e apps com LLM/RAG/agentes): detecta a stack, roda os scanners disponíveis (npm/composer/pip audit, gitleaks, semgrep), procura padrões perigosos no código (SQL por concatenação, XSS, IDOR, secrets hardcoded, SSRF, path traversal, output de LLM executado sem validação, agente com privilégio excessivo), classifica por severidade, reporta com arquivo e linha e corrige com confirmação. Invocar quando pedirem para revisar a segurança de um projeto ou PR, apontar vulnerabilidades, checar OWASP, ou antes de aprovar código gerado por IA.4---56# Security Review — validar, apontar e corrigir78Premissa: **código gerado por IA é código não confiável até passar por validação, testes e9revisão de segurança.** Isso vale para o código que você está revisando e para o que você mesmo10acabou de escrever.1112| Modo | Quando | Escopo |13|---|---|---|14| **Auditoria** | "revisa a segurança do projeto" | o repositório inteiro |15| **Revisão de PR** | "revisa a segurança desse PR/diff" | só o que mudou (`git diff develop...HEAD`) |16| **Correção** | "corrige o que encontrou" | os achados aprovados, um por vez |1718Sempre nesta ordem: **detectar → escanear → revisar → classificar → reportar → corrigir**.19Nunca corrija antes de reportar — a pessoa decide o que entra.2021A referência completa (riscos, exemplos, checklists por tema) está em `checklist.md` nesta22pasta. Este arquivo é o procedimento.2324## 1. Detectar a stack e o que há disponível2526```bash27ls composer.json package.json requirements.txt pyproject.toml go.mod Dockerfile .env .env.example 2>/dev/null28```2930| Encontrou | Stack | Sinais de IA/LLM no projeto |31|---|---|---|32| `composer.json` | PHP — Laravel se `laravel/framework` | `openai-php`, `anthropic` |33| `package.json` | Node/JS/TS — Express, Next, Nest, Fastify | `openai`, `@anthropic-ai/sdk`, `langchain`, `@pinecone-database`, `chromadb` |34| `requirements.txt` / `pyproject.toml` | Python — Django, FastAPI, Flask | `openai`, `anthropic`, `langchain`, `llama-index` |3536Se houver sinais de LLM, a seção 5 é **obrigatória**. Verifique também quais scanners estão37instalados (`which gitleaks semgrep trivy`) — use os que existirem; **não instale nada** sem38perguntar.3940## 2. Escanear com as ferramentas do ecossistema4142Rode o que se aplica e guarde a saída para o relatório:4344| Stack | Dependências (SCA) | Secrets | Estático |45|---|---|---|---|46| Node | `npm audit --json` (ou `pnpm audit` / `yarn audit`) | `gitleaks detect --no-git -v` | `semgrep --config p/owasp-top-ten --config p/javascript` |47| PHP/Laravel | `composer audit --format=json` | idem | `semgrep --config p/php` |48| Python | `pip-audit` ou `pip audit` | idem | `semgrep --config p/python` + `bandit -r .` |49| Qualquer | — | `git log -p --all -S "sk-" \| head` (secrets no histórico) | — |5051Sem gitleaks, faça a busca básica de secrets:5253```bash54grep -rnE "(sk-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|-----BEGIN (RSA |EC )?PRIVATE KEY|password\s*=\s*['\"][^'\"]{6,}|api[_-]?key\s*[:=]\s*['\"][^'\"]{10,})" \55 --include=*.{js,ts,php,py,env,json,yml,yaml} --exclude-dir={node_modules,vendor,.git,dist,build} .56```5758Confira também se `.env` está no `.gitignore` **e** se nunca foi commitado59(`git log --all --oneline -- .env`).6061## 3. Procurar os padrões perigosos no código6263Grep é triagem, não veredito: **abra cada match e confirme** se a entrada é controlada pelo64usuário e se existe validação/escape antes. Só reporte o que confirmou.6566### Injection (SQL, comando, template)6768| Stack | Padrão suspeito | Seguro |69|---|---|---|70| Node | `` query(`...${ `` · `query("..." + ` | placeholders `?` / `$1`, query builder com bindings |71| Laravel | `DB::raw(`, `whereRaw(`, `selectRaw(`, `DB::statement(` com variável | `where('col', $v)`, `whereRaw('col = ?', [$v])` |72| Python | `f"SELECT ... {`, `"..." % (`, `.format(` em SQL | parâmetros do driver, ORM |73| Qualquer | `exec(`, `spawn(` com shell, `shell_exec(`, `system(`, `passthru(`, `subprocess(..., shell=True)` | lista de argumentos sem shell; allowlist do comando |74| Qualquer | `eval(`, `new Function(`, `unserialize(`, `pickle.loads(` em dado externo | não usar; JSON com schema |7576### XSS e saída para o navegador7778| Padrão suspeito | Seguro |79|---|---|80| `innerHTML =`, `outerHTML =`, `document.write(`, `insertAdjacentHTML(` com dado externo | `textContent`; sanitizar com DOMPurify se HTML for inevitável |81| React `dangerouslySetInnerHTML` | idem |82| Blade `{!! $x !!}` | `{{ $x }}` (escapado) |83| Django/Jinja `\|safe`, `mark_safe(`, `{% autoescape off %}` | escape padrão |84| `res.send(` / `echo` de entrada sem escape em HTML | template com escape; `Content-Type` correto |8586### Autorização, IDOR e acesso8788- Rota que recebe `id` na URL e busca direto (`find($id)`, `findById(req.params.id)`) **sem89 checar o dono** (`where user_id = auth()`, policy, guard) → IDOR.90- Laravel: `$request->all()` em `create`/`update` sem `$fillable`/`$guarded` → mass assignment.91- Middleware de auth ausente em rota que muda estado; checagem só no front-end.92- Multi-tenant: consulta sem filtro de `tenant_id`.9394### Upload, arquivos e rede9596- Path traversal: caminho montado com entrada do usuário (`path.join(base, req.query.file)`,97 `storage_path($name)`) sem normalizar e conferir que ficou dentro da base.98- Upload: extensão/MIME não validados, arquivo servido do mesmo domínio, nome original mantido.99- SSRF: `fetch(url)`, `axios.get(url)`, `Http::get($url)`, `file_get_contents($url)`,100 `requests.get(url)` com URL vinda do usuário, sem allowlist de domínio e bloqueio de IP privado.101- CORS `*` com credenciais; cookies sem `HttpOnly`/`Secure`/`SameSite`.102- Redirect com destino vindo da query (`redirect($request->url)`) sem allowlist.103104### Inclusão de arquivos (LFI / RFI)105106- `include`/`require`/`include_once` com caminho vindo de entrada (`include($_GET['page'])`,107 `require "pages/$name.php"`) → LFI; com `allow_url_include=On`, vira RFI (código remoto).108- Node: `require(userInput)`, `import(userInput)`, `res.render(req.query.view)`; Python:109 `__import__(name)`, `importlib.import_module(user)`, `open(path)` com caminho externo.110- Seguro: **allowlist** de nomes válidos (`['home','about']`), nunca o caminho; `allow_url_include=Off`.111112### CSRF e clickjacking113114| Padrão suspeito | Seguro |115|---|---|116| Laravel: rota em `web.php` fora do middleware `web`, ou `$except` no `VerifyCsrfToken` com rotas de estado | manter `@csrf` nos forms e o middleware ativo; exceções só para webhooks com assinatura própria |117| Node: `POST/PUT/DELETE` com sessão por cookie e sem token CSRF (`csurf`/`csrf-csrf`/double-submit) | token por sessão ou `SameSite=Lax/Strict` **e** checagem de `Origin` |118| Django: `@csrf_exempt` em view que muda estado; `CsrfViewMiddleware` removido | manter o middleware; exemption só com justificativa |119| Estado mudado via `GET` (`/delete?id=`) | mutação só em `POST/PUT/DELETE` |120| Sem `X-Frame-Options` nem `Content-Security-Policy: frame-ancestors` | `frame-ancestors 'self'` (ou `'none'`) — clickjacking; `helmet` no Node, `SecurityHeaders`/middleware no Laravel |121| Sem `Strict-Transport-Security`, `X-Content-Type-Options: nosniff`, `Referrer-Policy` | headers de segurança no servidor ou no middleware |122123### Autenticação, sessão e bypass124125- **Bypass:** rota que muda estado sem middleware de auth; auth checada só no front; `if (user)`126 onde `user` pode vir do cliente (header/cookie forjável, `X-User-Id`); endpoint de debug/admin127 esquecido (`/debug`, `/admin` sem guard); ordem errada de middleware (handler antes do auth).128- **JWT:** sem expiração; sem verificar assinatura (`alg: none`, `verify: false`, `decode()` em vez de129 `verify()`); segredo fraco/hardcoded; aceitar token de outro emissor/audience; sem revogação.130- **Brute force:** sem rate limit em login/reset/OTP/2FA; sem bloqueio ou atraso progressivo após131 tentativas; enumeração de usuário por mensagem diferente ("senha errada" vs "usuário não existe").132- **Session fixation:** id de sessão não regenerado no login (`session()->regenerate()`,133 `req.session.regenerate()`, `request.session.cycle_key()`); id aceito pela URL ou por cookie134 pré-existente.135- **Session hijacking:** cookie sem `HttpOnly`/`Secure`/`SameSite`; sessão sem expiração ou sem136 invalidação no logout e na troca de senha; token de sessão em URL, log ou `localStorage` exposto a XSS.137- Comparação de token/hash com `==` em vez de comparação em tempo constante.138- Senha com hash fraco (MD5/SHA1) ou sem salt; `bcrypt`/`argon2` é o esperado.139140### Condições de corrida (race condition)141142- Padrão **ler → decidir → gravar** sem lock ou transação em recurso disputado: saldo, estoque,143 cupom de uso único, "já votou?", limite de uso — permite gastar/usar duas vezes com requisições144 simultâneas.145- Seguro: `SELECT ... FOR UPDATE` / `lockForUpdate()` dentro de transação; `UPDATE ... WHERE saldo >= ?`146 atômico; constraint `UNIQUE` para "uma vez só"; chave de idempotência em operações de pagamento.147- Teste negativo: disparar N requisições em paralelo e conferir que só uma passa.148149### Exposição de dados sensíveis, erros e logs150151- Stack trace, query ou payload retornados ao cliente (`APP_DEBUG=true` em produção,152 `app.use(errorHandler)` que vaza `err.stack`).153- Dado pessoal, token ou senha em log, em URL (`?token=`) ou em resposta de API além do necessário154 (serializar o model inteiro: `return $user`, `res.json(user)` com hash/e-mail/documento).155- Tráfego sem TLS; `.git/`, `.env`, backups ou `phpinfo` servidos publicamente; listagem de diretório.156- Dado sensível armazenado em claro quando deveria ser hash ou cifrado; sem mascaramento em telas/logs.157158## 4. Dependências e supply chain159160Além do `audit`: versões fixadas e **lockfile versionado**; pacotes com nome parecido com o de161uma lib conhecida (typosquatting); dependência adicionada só para uma função trivial;162implementação própria de cripto/auth onde existe solução consolidada; imagem Docker163`latest` ou sem scan (`trivy image` se houver).164165## 5. Se o projeto usa LLM, RAG ou agentes166167Trate como **fronteira de confiança nova** — o modelo não é camada de segurança.168169| Risco | Como identificar | Como corrigir |170|---|---|---|171| **Insecure output handling** | saída do modelo vai para `query(`, `exec(`, `eval(`, `innerHTML`, `fetch(url)` sem passar por validação | schema (JSON Schema/zod/Pydantic) → validação → **autorização no código** → execução controlada |172| **Prompt injection** | entrada do usuário ou documento do RAG concatenado ao system prompt; ações disparadas só pelo conteúdo recuperado | separar instrução de dado; tratar RAG como não confiável; decisão de ação fora do LLM |173| **Excessive agency** | tool `executar_sql`, credencial admin, tool de deleção sem confirmação, sem limite de chamadas | menor privilégio por tool; autorização própria em cada tool; human-in-the-loop em ação destrutiva; limite de iterações |174| **Vazamento via RAG** | vector DB compartilhado sem filtro de `tenant_id`/`user_id` **no retrieval** | filtrar **antes** de recuperar; metadados de acesso por documento; remover embeddings de documento excluído |175| **Vazamento em logs** | prompt/resposta com PII, token ou documento privado gravados | logar id da requisição, usuário, tool, decisão de autorização, resultado — não o conteúdo |176| **Secrets no prompt** | chave/senha dentro de prompt ou system prompt | secret manager / variável de ambiente |177| **SSRF via tool** | tool HTTP com URL controlada pelo modelo/usuário | allowlist de domínio, bloqueio de IP privado, timeout, limite de resposta |178179Regra de arquitetura a cobrar: `INPUT → VALIDATE → AUTHENTICATE → AUTHORIZE → LLM → VALIDATE180OUTPUT → BUSINESS RULES → EXECUTE → AUDIT`. Nunca `INPUT → LLM → EXECUTE`.181182## 6. Classificar183184| Nível | Critério | Exemplos |185|---|---|---|186| 🔴 **Crítica** | explorável remotamente sem autenticação, ou vaza/destrói dados de terceiros | SQL injection, RCE, secret válido no repo, IDOR em dado de outro usuário, output de LLM executado direto |187| 🟠 **Alta** | explorável por usuário autenticado ou com pré-condição plausível | XSS armazenado, SSRF interno, mass assignment em campo sensível, JWT sem expiração |188| 🟡 **Média** | reduz defesa ou exige cadeia de falhas | CORS permissivo, sem rate limit, cookie sem flags, dependência com CVE sem caminho claro de exploração |189| 🔵 **Baixa** | higiene e boas práticas | log verboso, header de segurança ausente, versão desatualizada sem CVE |190191Falso positivo confirmado **não entra** no relatório. Achado que você não conseguiu confirmar192entra como "a verificar", separado.193194## 7. Reportar195196Sempre antes de corrigir. Formato:197198```199## Security Review — <projeto ou PR #N>200Stack: Laravel 11 · Node 20 (front) · usa OpenAI SDK201Ferramentas: composer audit ✓ · npm audit ✓ · gitleaks ✗ (não instalado) · semgrep ✗202203### Resumo204🔴 2 · 🟠 3 · 🟡 4 · 🔵 2 · a verificar: 1205206### Achados207🔴 SQL injection — app/Http/Controllers/ReportController.php:48208 `DB::select("... WHERE status = '$status'")` com $status vindo da query string.209 Correção: binding `DB::select('... WHERE status = ?', [$status])`.210211🟠 IDOR — routes/api.php:31 + OrderController@show212 `Order::findOrFail($id)` sem checar o dono. Correção: `auth()->user()->orders()->findOrFail($id)` ou Policy.213…214215### Dependências216composer audit: 1 pacote com CVE (…) · npm audit: 3 high, 12 moderate217218### Recomendação219Bloquear merge até resolver as 🔴. As 🟠 entram no mesmo PR ou em card próprio, à escolha do time.220```221222Cada achado tem **arquivo:linha, o que é, por que é explorável e a correção proposta**. Sem223os quatro, não é achado — é palpite.224225## 8. Corrigir (só depois do OK)226227- **Um achado por vez**, começando pelas 🔴. Peça confirmação antes de mexer em autenticação,228 autorização ou cripto — são as mudanças com mais efeito colateral.229- Corrija a **causa**, não o sintoma: placeholder em vez de escapar a string; policy em vez de230 `if` solto na rota; schema de saída em vez de regex na resposta do LLM.231- **Escreva o teste negativo** junto com a correção (a entrada maliciosa deve falhar) — é o que232 impede a regressão.233- Secret encontrado no repositório: **rotacione primeiro**, depois remova do código e do234 histórico. Remover sem rotacionar não resolve nada.235- Não "arrume" desabilitando a proteção (CSRF off, `verify: false`, `rejectUnauthorized: false`,236 `eslint-disable` em regra de segurança). Isso é achado novo, não correção.237- Se o projeto usa a skill `git-flow-delivery`, a correção segue o fluxo dela (branch, commit238 com o card, PR). Secret exposto e 🔴 explorável em produção são **hotfix**.239240## Mapa de cobertura (Pentest Web)241242Para conferir que a revisão não deixou buraco — cada item e onde ele é tratado neste arquivo:243244| Vulnerabilidade | Seção |245|---|---|246| SQL Injection | Injection |247| Cross-Site Scripting (XSS) | XSS e saída para o navegador |248| Cross-Site Request Forgery (CSRF) | CSRF e clickjacking |249| Broken Access Control · IDOR | Autorização, IDOR e acesso |250| Authentication Bypass · Brute Force · JWT Misconfiguration | Autenticação, sessão e bypass |251| Session Fixation · Session Hijacking | Autenticação, sessão e bypass |252| CORS Misconfiguration · Open Redirect | Upload, arquivos e rede |253| Sensitive Data Exposure | Exposição de dados sensíveis, erros e logs |254| SSRF · Path Traversal | Upload, arquivos e rede |255| Local / Remote File Inclusion | Inclusão de arquivos (LFI / RFI) |256| File Upload · Unrestricted File Upload | Upload, arquivos e rede |257| Clickjacking | CSRF e clickjacking |258| Race Condition | Condições de corrida |259| Prompt Injection · Excessive Agency · Insecure Output · RAG | Se o projeto usa LLM, RAG ou agentes |260261## Checklist rápido262263- [ ] Stack detectada; sinais de LLM verificados264- [ ] `audit` de dependências rodado; secrets procurados (código **e** histórico)265- [ ] Todas as linhas do **mapa de cobertura** revisadas — matches **confirmados**266- [ ] Se há LLM: output handling, prompt injection, agency, RAG e logs checados267- [ ] Cada achado com arquivo:linha, exploração e correção; severidade atribuída268- [ ] Relatório entregue **antes** de qualquer alteração269- [ ] Correções aprovadas, uma por vez, com teste negativo; secrets rotacionados270271## Adaptando ao seu time272273| O que | Aqui | No seu time |274|---|---|---|275| Ferramentas | gitleaks, semgrep, trivy se existirem | as que o CI já roda (Snyk, SonarQube, CodeQL…) |276| Severidade | 4 níveis acima | o esquema do seu programa de segurança (CVSS, etc.) |277| Política de merge | bloquear com 🔴 | o acordo do time |278| Regras extras de domínio | — | ex.: idempotência em cobranças, mascaramento de documentos, LGPD/GDPR |279| Onde registrar | relatório no PR/card | ferramenta de vulnerabilidades do time |