dev-security-patterns
Skill6 starsChanged 24 days agoMITView on GitHub
Padrões de segurança para software complexo — autenticação, autorização, RLS, OWASP top 10, validação de input, secrets management e a varredura das armadilhas de código gerado por IA (segredo no cliente, RLS aberta, auth por user_metadata, prompt injection).
- Reads credentials
In this repository124 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- accessibilitySkill
- ai-ml-data-scienceSkill
- ai-ml-timeseriesSkill
- brand-platformSkill
- brand-researchSkill
- brand-rolloutSkill
- brand-trackingSkill
- brand-verbal-identitySkill
- brand-visual-systemSkill
- copy-humanizeSkill
- core-web-vitalsSkill
- data-analytics-engineeringSkill
- data-lake-platformSkill
- data-sql-optimizationSkill
- data-supabase-patternsSkill
- deep-researchSkill
- dev-api-designSkill
- dev-database-patternsSkill
- dev-debuggingSkill
- dev-defuddleSkill
- dev-error-handlingSkill
- dev-git-workflowSkill
- dev-technical-writingSkill
- dev-testing-strategySkill
- dev-typescript-patternsSkill
- doc-to-markdownSkill
- finance-bookkeepingSkill
- finance-cash-flowSkill
- finance-receivables-payablesSkill
- finance-reportingSkill
- finance-tax-complianceSkill
- instagram-dm-flowsSkill
- instagram-publishingSkill
- instagram-strategySkill
- legal-clause-librarySkill
- legal-compliance-lgpdSkill
- legal-contract-draftingSkill
- legal-contract-lifecycleSkill
- legal-researchSkill
- linkedin-publishingSkill
- linkedin-strategySkill
- negotiationSkill
- nextjs-react-best-practicesSkill
- presentation-designSkill
- pricingSkill
- sala-de-controleSkill
- sales-deck-productionSkill
- sales-discovery-intakeSkill
- sales-enablementSkill
- sales-pricing-paybackSkill
- sales-proposal-copySkill
- sales-proposal-planningSkill
- security-audit-methodSkill
- security-incident-responseSkill
- security-privacy-data-mapSkill
- security-threat-modelingSkill
- seo-ahrefsSkill
- seo-auditSkill
- seo-backlinksSkill
- The first 60 shown.
The file
Files
Security Patterns — Software Complexo
JWT — Autenticação
// Geração
const accessToken = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET!,
{ expiresIn: '15m' } // curto
)
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' }
)
// Validação em middleware
const verifyToken = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1]
if (!token) return res.status(401).json({ error: { code: 'UNAUTHORIZED', requestId: req.requestId } })
try {
req.user = jwt.verify(token, process.env.JWT_SECRET!)
next()
} catch {
return res.status(401).json({ error: { code: 'UNAUTHORIZED', requestId: req.requestId } })
}
}Regras: Access token 15min. Refresh token 7 dias com rotação. Nunca localStorage — usar httpOnly cookie. Secret mínimo 256 bits.
Autorização — RBAC
const requireRole = (...roles: string[]) => (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: { code: 'FORBIDDEN', requestId: req.requestId } })
}
next()
}
// Uso
router.delete('/users/:id', verifyToken, requireRole('admin'), deleteUser)Row Level Security (RLS — Supabase/Postgres)
-- Habilitar RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- Usuário vê apenas seus dados
CREATE POLICY "user_own_orders" ON orders
FOR ALL USING (auth.uid() = user_id);
-- Admin vê tudo — papel vem de app_metadata (só o servidor escreve)
CREATE POLICY "admin_all_orders" ON orders
FOR ALL USING ((auth.jwt() -> 'app_metadata' ->> 'role') = 'admin');RLS é a última linha de defesa — aplicar em todas as tabelas com dados de usuário.
auth.jwt() ->> 'role'(claim de topo) é o papel do Postgres (authenticated/anon), não o papel da aplicação — política escrita assim nunca casa.- Nunca autorizar por
user_metadata(raw_user_meta_data): o próprio usuário edita esse campo pela API de auth e se dá qualquer papel. Papel de aplicação mora emapp_metadataou numa tabela de papéis. - "RLS ativo" não prova nada: RLS ligado sem policy nega tudo; RLS com
USING (true)libera tudo. Leia a policy. - Bucket do Storage é tabela também (
storage.objects) —USING (true)ali expõe todos os arquivos.
Validação de Input
const createUserSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100).trim(),
})
// Middleware
const validate = (schema) => (req, res, next) => {
const result = schema.safeParse(req.body)
if (!result.success) {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
details: result.error.errors,
requestId: req.requestId, // sempre incluir requestId
}
})
}
req.body = result.data // dados sanitizados
next()
}Nunca confiar em input do cliente. Validar e sanitizar tudo que vem do exterior.
OWASP Top 10 — Checklist
| Risco | Mitigação |
| SQL Injection | ORM/query builder, nunca concatenar queries |
| Broken Auth | JWT curto, refresh rotation, httpOnly cookies |
| Sensitive Data Exposure | HTTPS, nunca logar dados sensíveis |
| Broken Access Control | RBAC + RLS obrigatórios |
| Security Misconfiguration | Env vars para secrets, nada hardcoded |
| XSS | Sanitizar output, Content-Security-Policy |
| Vulnerable Dependencies | npm audit em CI |
| Insufficient Logging | Logar autenticações e acessos negados com requestId |
Secrets Management
# ✅ Variáveis de ambiente
DATABASE_URL=postgresql://...
JWT_SECRET=super-secret-256-bits
# ❌ Nunca hardcoded
const JWT_SECRET = "minha-chave"Regras:
.envno.gitignore— nunca commitar.env.examplecom chaves sem valores — commitar- Em produção: secret manager (AWS Secrets Manager, Vercel env vars)
- Prefixo público (
NEXT_PUBLIC_,VITE_,PUBLIC_,EXPO_PUBLIC_) vai para o navegador por definição — só chave publicável usa esse prefixo - Segredo que vazou já está queimado: apagar do código não basta. A correção só termina com a chave rotacionada no provedor
Varredura de código gerado por IA
Assistentes de código otimizam para a demo rodar. Os cinco erros abaixo são os que mais escapam em projetos Next.js + Supabase + LLM. Rode a varredura na raiz do projeto, leia cada ocorrência e classifique. Busca é triagem, não veredicto — toda ocorrência é conferida no arquivo antes de virar achado.
# Funciona em bash e zsh. Rode da raiz do projeto.
# 1. Segredo atrás de prefixo público
grep -rnE "(NEXT_PUBLIC|VITE|PUBLIC|EXPO_PUBLIC)_[A-Z0-9_]*(SECRET|SERVICE_ROLE|PRIVATE|TOKEN|PASSWORD|API_KEY)" \
--include='*.[jt]s' --include='*.[jt]sx' --include='*.mjs' --include='*.astro' --include='*.vue' --include='*.svelte' --include='.env*' \
--exclude-dir={node_modules,.next,.git,dist,build} .
# 2. Chave literal no código (OpenAI/Anthropic, Stripe live, JWT, AWS, chave privada)
grep -rnE "sk-(proj-|ant-)?[A-Za-z0-9_-]{20,}|sk_live_[A-Za-z0-9]{10,}|eyJhbGciOi[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY" \
--exclude-dir={node_modules,.next,.git,dist,build} .
# 3. service_role fora do servidor
grep -rnE "service_role|SERVICE_ROLE" \
--include='*.[jt]s' --include='*.[jt]sx' --include='*.mjs' --include='*.astro' --include='*.vue' --include='*.svelte' \
--exclude-dir={node_modules,.next,.git,dist,build} .
# 4. RLS aberta e autorização por user_metadata
grep -rniE "using[[:space:]]*\([[:space:]]*true[[:space:]]*\)|with check[[:space:]]*\([[:space:]]*true[[:space:]]*\)|user_metadata|raw_user_meta_data" \
--include='*.sql' --include='*.[jt]s' --include='*.[jt]sx' \
--exclude-dir={node_modules,.next,.git,dist,build} .
# 4. Tabelas criadas sem "enable row level security" (a saída é a lista de suspeitas)
comm -23 \
<(grep -rhoiE "create table( if not exists)? [a-z_.\"]+" --include='*.sql' --exclude-dir=node_modules --exclude-dir=.git . | tr A-Z a-z | awk '{print $NF}' | sort -u) \
<(grep -rhoiE "alter table( only)? [a-z_.\"]+ enable row level security" --include='*.sql' --exclude-dir=node_modules --exclude-dir=.git . | tr A-Z a-z | awk '{print $(NF-4)}' | sort -u)
# 5. Prompt de sistema montado com dado da requisição
grep -rnE "role:[[:space:]]*[\"']system[\"']|system:[[:space:]]*\`" \
--include='*.[jt]s' --include='*.[jt]sx' --include='*.mjs' \
--exclude-dir={node_modules,.next,.git,dist,build} .Sem migrations SQL no repositório (schema só no painel do Supabase)? A busca 4 não enxerga nada — rode no SQL Editor: select tablename, rowsecurity from pg_tables where schemaname = 'public'; e select * from pg_policies where schemaname in ('public','storage');. Sem esse acesso, o item vira NÃO VERIFICÁVEL, nunca "ok".
| # | Achado | Quando é achado | Severidade |
| 1 | Segredo com prefixo público | Valor não publicável (chave de API privada, service_role, token) | CRITICAL |
| 2 | Chave literal no código | Qualquer chave privada no repositório. JWT literal: decodifique o payload — "role":"service_role" é CRITICAL; "role":"anon" não é achado | CRITICAL |
| 3 | service_role alcançável pelo cliente | Arquivo com "use client", componente, hook ou módulo importado por eles | CRITICAL |
| 4a | Tabela com dado de usuário sem RLS, ou com USING (true) / WITH CHECK (true) | Dado não é público por desenho. Sem RLS, ou UPDATE/DELETE/ALL aberto: CRITICAL. SELECT aberto (qualquer um lê): HIGH. INSERT aberto fora de formulário público: HIGH | ver coluna ao lado |
| 4b | Autorização por user_metadata | Policy, middleware ou checagem de papel lê user_metadata | HIGH |
| 4c | Bucket do Storage aberto | Policy USING (true) em storage.objects de bucket com arquivo privado | HIGH |
| 5a | Entrada da requisição no prompt de sistema e chamada com tools | req.body/query/form chega ao system numa chamada com tools | HIGH |
| 5b | Entrada da requisição no prompt de sistema, sem tools | Idem, sem tools — heurístico, conferir à mão | MEDIUM |
Não é achado (não acusar): anon key do Supabase e NEXT_PUBLIC_SUPABASE_ANON_KEY (a RLS é o portão); chave publicável do Stripe (pk_); chave de projeto de analytics (PostHog, GA); USING (true) só para SELECT em tabela pública por desenho (catálogo, posts publicados) e documentada como tal; INSERT ... WITH CHECK (true) em tabela de formulário público (lead, contato, newsletter) sem policy de SELECT/UPDATE/DELETE para anon — é o desenho normal de formulário (no máximo uma recomendação: limitar formato e tamanho no WITH CHECK ou inserir por rota de servidor com rate limit); entrada do usuário numa mensagem role: "user" separada, sem tools. Acusar o padrão seguro ensina o time a ignorar o QA.
Como reportar cada achado: linha (arquivo:linha) → o risco em uma frase ("vai para o navegador de todo visitante") → a correção em um commit. Para segredo: tipo e local, nunca o valor (mostre no máximo os 4 primeiros caracteres), e a correção sempre inclui rotacionar no provedor.
Correção só vale com nova varredura. Depois do fix, rode a varredura de novo e compare: resolvido, ainda presente, novo. Segredo só sai da lista com a rotação confirmada pelo usuário — remover do código não basta.
Rate Limiting
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10, // 10 tentativas em 15 min
message: { error: { code: 'RATE_LIMITED', requestId: 'see-x-request-id-header' } },
})
app.use('/auth', authLimiter)
app.use('/api', rateLimit({ windowMs: 60000, max: 100 }))Password Hashing
const hash = await bcrypt.hash(password, 12) // custo 12 mínimo
const valid = await bcrypt.compare(input, hash)Nunca MD5 ou SHA1 para passwords. Nunca plain text.
Logging Estruturado Seguro
O requestId gerado no middleware de entrada (ver dev-api-design) deve estar presente em todos os logs — é o fio que conecta request → serviços → banco → resposta no debugging.
// ✅ Correto — contexto rico com requestId, dado sensível ausente
logger.info({
requestId: req.requestId, // sempre — rastreabilidade end-to-end
userId: user.id,
action: 'login_attempt',
ip: req.ip,
success: true,
}, 'User authenticated')
logger.error({
requestId: req.requestId,
userId: user.id,
action: 'payment_process',
orderId: order.id,
err: error,
}, 'Payment processing failed')
// ❌ Errado — sem requestId, sem contexto, pode vazar dados
console.error('Error:', error)
logger.info('Login', { password: body.password, token: jwt })Nunca logar: passwords, tokens JWT, dados de cartão, CPF/SSN, qualquer PII desnecessário. Sempre logar: requestId, userId (não email), action, resultado (success/fail), IP em auth events.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

