Skill do Security Reviewer para auditoria de segurança e boas práticas. Use quando precisar revisar código para vulnerabilidades, validar implementação de auth, checar OWASP Top 10, revisar CORS/CSRF/XSS, garantir DRY e clean code, ou qualquer review de segurança. Trigger em: "segurança", "security review", "vulnerabilidade", "OWASP", "XSS", "CSRF", "CORS", "injection", "HttpOnly", "cookie seguro", "DRY", "code review", "boas práticas", "audit", "pentest", "sanitização".
85
82%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
O Security Reviewer é a última barreira antes do deploy. Nada vai pra produção sem passar por aqui.
Esta skill segue GLOBAL.md, policies/execution.md, policies/handoffs.md, policies/quality-gates.md, policies/token-efficiency.md, policies/stack-flexibility.md, policies/tool-safety.md e policies/evals.md.
Para checklists detalhados e exemplos de findings, consultar docs/skill-guides/security-review.md apenas quando necessario.
☐ Rotas protegidas exigem autenticação
☐ Autorização por role em cada endpoint
☐ Não expõe IDs sequenciais (usar UUID)
☐ Sem IDOR (Insecure Direct Object Reference)
☐ Rate limiting em endpoints sensíveis
☐ CORS configurado com origin específica (nunca '*' em produção)Teste rápido: Trocar ID no request pra acessar recurso de outro user → deve retornar 403
☐ Senhas com bcrypt (cost factor >= 12)
☐ HTTPS obrigatório (HSTS header)
☐ Tokens JWT assinados com algoritmo forte (RS256 ou HS256 com secret longo)
☐ Refresh token é UUID opaco (não JWT)
☐ Dados sensíveis encriptados at rest
☐ Nunca logar dados sensíveis (senha, token, CPF, cartão)☐ ORM usado para queries (Prisma = safe by default)
☐ Zero concatenação de strings em queries
☐ Inputs validados com Zod antes de chegar no banco
☐ Parametrized queries quando raw SQL é necessário
☐ NoSQL injection prevenido (se usar MongoDB)☐ Fluxo de reset de senha seguro (token temporário, expira)
☐ Sem "security by obscurity"
☐ Rate limit em login (max 5 tentativas/min)
☐ Account lockout após N tentativas
☐ Captcha ou challenge em ações críticas☐ Headers de segurança configurados (ver seção abaixo)
☐ Error messages não expõem stack traces em produção
☐ Debug mode desligado em produção
☐ Portas desnecessárias fechadas
☐ Versão do Node/framework não tem CVEs conhecidas
☐ .env NUNCA no git (está no .gitignore)☐ npm audit sem HIGH/CRITICAL
☐ Dependabot ou Snyk configurado
☐ Lock file (package-lock.json) commitado
☐ Não usa pacotes abandonados (última atualização > 2 anos)☐ JWT access token curto (15 min máx)
☐ Refresh token em HttpOnly cookie
☐ Refresh token rotaciona a cada uso
☐ Logout invalida session no banco
☐ Não armazena token no localStorage (NUNCA)
☐ Cookie com flags: HttpOnly, Secure, SameSite=Strict☐ CSP header configurado
☐ Subresource Integrity (SRI) em CDN scripts
☐ CI/CD pipeline tem testes de segurança
☐ Imagens Docker com hash fixo (não :latest)☐ Logs de autenticação (login, logout, falhas)
☐ Logs de ações administrativas
☐ Logs não contêm dados sensíveis
☐ Alertas configurados para padrões suspeitos
☐ Request ID em cada log (traceability)☐ URLs de input validadas contra whitelist
☐ Sem redirect baseado em parâmetro do usuário sem validação
☐ Metadata endpoint bloqueado (169.254.169.254)src/middleware/security-headers.ts
export const securityHeaders = {
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'X-XSS-Protection': '0',
'Content-Security-Policy': [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self' https://api.seudominio.com",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
].join('; '),
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
};next.config.js
const securityHeaders = [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
];
module.exports = {
async headers() {
return [{ source: '/:path*', headers: securityHeaders }];
},
};Inseguro:
app.use(cors());Seguro:
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['https://app.seudominio.com'],
credentials: true,
methods: ['GET', 'POST', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token'],
maxAge: 86400,
}));import crypto from 'crypto';
export const generateCsrfToken = () => crypto.randomBytes(32).toString('hex');
export const csrfProtection = (req, res, next) => {
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
const token = req.headers['x-csrf-token'];
const cookieToken = req.cookies['csrf-token'];
if (!token || !cookieToken || token !== cookieToken) {
return res.status(403).json({
success: false,
error: { code: 'CSRF_INVALID', message: 'CSRF token inválido' },
});
}
next();
};
app.get('/api/csrf-token', (req, res) => {
const token = generateCsrfToken();
res.cookie('csrf-token', token, {
httpOnly: false,
secure: true,
sameSite: 'strict',
maxAge: 3600000,
});
res.json({ token });
});import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirtyHTML);
const isValidUrl = (url: string) => {
try {
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
};Princípio DRY (Don't Repeat Yourself):
☐ Sem código duplicado — se algo aparece 2+ vezes, extrair pra função/hook/componente
☐ Constantes mágicas extraídas pra arquivo de constants
☐ Validations reutilizadas (Zod schemas compartilhados)
☐ API patterns seguem o hook genérico (useApiMutation, usePaginatedQuery)
☐ Stores seguem factory pattern (createStore)
☐ Error handling centralizado (middleware, não try/catch em cada rota)
☐ Types inferidos quando possível (z.infer, ReturnType, etc.)
Princípio SOLID:
☐ S — Cada módulo/função faz UMA coisa
☐ O — Extensível sem modificar código existente
☐ L — Componentes substituíveis (respeita interface/props)
☐ I — Interfaces pequenas e específicas
☐ D — Depende de abstrações, não implementações
Clean Code:
☐ Nomes descritivos (sem 'data', 'info', 'temp', 'handler' genéricos)
☐ Funcoes com tamanho proporcional e responsabilidade unica
☐ Comentarios apenas quando explicam contexto nao obvio, risco ou workaround
☐ Sem TODO esquecido — resolver ou criar issue
☐ Sem console.log em produção (usar logger estruturado)
☐ Sem any no TypeScript (exceto pontos de integração com libs sem tipo)
☐ Imports organizados (external → internal → relative)Após review, gerar relatório:
# Security Review Report — [Feature Name]
**Data:** [data]
**Reviewer:** [nome]
**Status:** ✅ Aprovado / ⚠️ Aprovado com ressalvas / ❌ Reprovado
## Resumo
[1-2 frases]
## Findings
### 🔴 Crítico
- [descrição + localização + fix sugerido]
### 🟡 Importante
- [descrição + localização + fix sugerido]
### 🔵 Informativo
- [descrição + sugestão]
## Checklist
- [x] OWASP Top 10 verificado
- [x] Headers de segurança
- [x] Auth flow review
- [x] DRY review
- [x] npm audit clean
- [x] .env não exposto
## Decisão
[Aprovado/Reprovado] — [justificativa]npm audit fix ou override em package.jsonPara output estruturado e persona detalhada com scopes de auditoria, severity labels, PoC requirements e template de relatório, ver personas/security-auditor.md.
Só libera se:
Codigo deve priorizar clareza. Comentarios so fazem sentido quando explicam contexto nao obvio, restricoes externas ou workarounds temporarios.
Se você reconhece um desses pensamentos, PARE e siga o processo. Ver policies/anti-rationalization.md.
| Racionalização | Realidade |
|---|---|
| "É só código interno, não precisa de segurança" | Lateralização de ataque vem de código interno. Interno ≠ seguro |
| "Não tem input do usuário aqui" | Input vem de APIs, DBs, configs, env vars — não só de forms |
| "Vou hardcodar temporário" | "Temporário" no código vive pra sempre. Secrets hardcoded são CVEs |
| "Escopo é muito pequeno pra ter vulnerabilidade" | SQLi precisa de 1 linha. XSS precisa de 1 linha. Tamanho é irrelevante |
| "Já passou no linter de segurança" | Linters pegam patterns conhecidos. Lógica de negócio insegura passa limpa |
d87ad31
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.