Cheatsheet e decision tree de segurança

TL;DR

Referência consolidada de decisões de segurança para Node.js senior. O galho 8 cobre 9 áreas críticas: supply chain (npm audit + socket.dev), segredos (vault, env validation com Zod), validação de entrada (Zod vs Joi), JWT (jsonwebtoken, refresh token rotation, Redis blacklist), OAuth 2.0/OIDC (openid-client, PKCE), autorização (casl ABAC vs casbin policies), rate limiting (express-rate-limit + Redis), HTTP headers (Helmet.js) e OWASP Top 10 (A01–A10 com exemplos Node). Use este cheatsheet como ponto de entrada rápido e decision tree para escolher a ferramenta certa para cada categoria de segurança.

O que é

Este cheatsheet consolida os padrões, ferramentas e decisões de segurança das 9 notas do galho 8 da trilha Node Senior. Não é um resumo superficial — é um mapa de decisão para situações reais: qual biblioteca escolher, qual padrão aplicar, o que checar em um PR de segurança.

Quando usar este cheatsheet:

  • Início de projeto: escolher o stack de segurança correto
  • Code review: validar que as decisões de segurança estão corretas
  • Entrevista: responder perguntas transversais sobre segurança Node.js senior
  • Incidente: identificar rapidamente qual área pode ter falhado

Como funciona

Decision tree — escolha a ferramenta certa

flowchart TD
    A[Precisa de segurança em Node.js] --> B{Qual área?}

    B --> C[Validação de entrada]
    B --> D[Autenticação]
    B --> E[Autorização]
    B --> F[Proteção de API]
    B --> G[Supply chain]
    B --> H[Segredos]
    B --> I[HTTP Headers]

    C --> C1{TypeScript?}
    C1 -->|Sim| C2[Zod v3\nsafeParse + infer]
    C1 -->|Legado JS| C3[Joi v17\nschema object-based]

    D --> D1{OAuth/OIDC externo?}
    D1 -->|Sim| D2[openid-client v5\nIssuer.discover + PKCE]
    D1 -->|JWT próprio| D3[jsonwebtoken v9\nHS256 ou RS256]
    D3 --> D4[Access + Refresh tokens\nBlacklist no Redis]

    E --> E1{Regras baseadas em atributos?}
    E1 -->|Sim, TypeScript| E2[casl v6\ndefineAbility + can/cannot]
    E1 -->|Políticas externas| E3[casbin\npolicy files + enforce]
    E1 -->|RBAC simples| E4[Claims no JWT\ncheckRole middleware]

    F --> F1[express-rate-limit v7\n+ rate-limit-redis\nstore Redis para múltiplas instâncias]

    G --> G1[npm audit\n+ npm ci no CI\n+ socket.dev ou snyk]

    H --> H1{Produção?}
    H1 -->|Sim| H2[AWS Secrets Manager\nHashiCorp Vault\nDoppler]
    H1 -->|Dev| H3[.env local\nnode --env-file=.env\nValidar com Zod na inicialização]

    I --> I1["Helmet v7\napp.use(helmet())\nCSP com nonce para scripts inline"]

Tabela comparativa por categoria

CategoriaBiblioteca principalQuando usarAlternativa
Validação de entradaZod v3TypeScript, schema-first, infer de tiposJoi v17 (legado JS)
Autenticação JWTjsonwebtoken v9JWT próprio, stateless, microservices@fastify/jwt para Fastify
OAuth 2.0 / OIDCopenid-client v5Login social, SSO enterprise, Authorization Code + PKCEpassport-oauth2 (flows simples)
Autorizaçãocasl v6ABAC TypeScript, permissões condicionais, NestJScasbin (policies em arquivo)
HTTP HeadersHelmet v7Qualquer app Express — padrão obrigatório@fastify/helmet para Fastify
Rate Limitingexpress-rate-limit v7Auth endpoints, APIs públicas@fastify/rate-limit para Fastify
Supply chainnpm audit + socket.devCI/CD pipeline, pre-merge checkSnyk, Dependabot (GitHub)
Segredos em prodAWS Secrets Manager / VaultRotação automática, auditoria de acessoGCP Secret Manager, Doppler

Checklist de segurança para PR review

Use este checklist em code reviews que tocam lógica de negócio ou infra:

## Security Review

### Validação e sanitização
- [ ] Input externo validado com Zod/Joi na borda do sistema (controller/route handler)
- [ ] Nenhum `req.body` ou `req.query` passado diretamente para query de banco ou sistema de arquivos
- [ ] Tamanho máximo de strings e arrays definido no schema (previne ReDoS e DoS)

### Segredos
- [ ] Nenhum segredo hardcoded no código (API keys, passwords, tokens)
- [ ] Nenhum `.env` de produção no repositório
- [ ] Env vars validadas com Zod na inicialização — app falha rápido se variável estiver ausente

### Autenticação e sessão
- [ ] `jwt.verify()` chamado com `{ algorithms: ['HS256'] }` explícito (previne algorithm confusion)
- [ ] Refresh tokens com rotação (revoga o anterior ao emitir o novo)
- [ ] Mensagens de erro de auth genéricas (não vazar se email existe ou não)

### Autorização
- [ ] Verificação de ownership feita server-side, não apenas no frontend
- [ ] Usuário pode acessar apenas seus próprios recursos (BOLA/IDOR check)
- [ ] Logs de decisões de autorização negadas registrados (auditoria)

### HTTP e headers
- [ ] `helmet()` aplicado globalmente antes das rotas
- [ ] CSP configurada — sem `'unsafe-inline'` nos scripts (exceto se usando nonces)
- [ ] CORS allowlist explícita, não `*` com `credentials: true`

### Rate limiting
- [ ] Rate limiting aplicado em endpoints de autenticação (login, reset de senha)
- [ ] Store Redis configurado (não memória) em ambientes com múltiplas instâncias

### Supply chain
- [ ] `npm audit` sem vulnerabilidades high/critical
- [ ] `package-lock.json` ou `pnpm-lock.yaml` commitado e usado com `npm ci` no CI

### Logging
- [ ] Dados sensíveis redactados nos logs (senha, token, CPF, cartão)
- [ ] Stack traces não expostos em respostas de API em produção
- [ ] Eventos de autenticação (login, logout, falhas) logados com user_id e IP

Padrões de resposta de erro segura

Erros mal formatados são um vetor de information disclosure. O padrão correto é genérico para o cliente e detalhado internamente.

import express, { Request, Response, NextFunction } from 'express'
 
interface AppError extends Error {
  statusCode?: number
  code?: string
  isOperational?: boolean
}
 
// Formato de erro público — sem detalhes internos
interface ErrorResponse {
  error: {
    code: string
    message: string
  }
}
 
function errorHandler(
  err: AppError,
  req: Request,
  res: Response,
  _next: NextFunction,
): void {
  // Log completo interno (stack trace, contexto)
  console.error({
    message: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
    ip: req.ip,
  })
 
  // Resposta pública: sem stack trace, mensagem genérica para erros não operacionais
  const statusCode = err.statusCode ?? 500
  const isOperational = err.isOperational ?? false
 
  const response: ErrorResponse = {
    error: {
      code: err.code ?? 'INTERNAL_ERROR',
      // Erros não operacionais (bugs) → mensagem genérica
      // Erros operacionais (validação, negócio) → mensagem da aplicação
      message: isOperational ? err.message : 'An unexpected error occurred',
    },
  }
 
  res.status(statusCode).json(response)
}
 
// Erros de autenticação: sempre genérico — não vazar se email existe
function authErrorResponse(res: Response): void {
  // 401 correto para auth failure (não 400 — isso vaza informação de que o email existe)
  res.status(401).json({
    error: {
      code: 'AUTH_FAILED',
      message: 'Invalid credentials',
    },
  })
}
 
// 401 vs 403: regra clara
// 401 Unauthorized → não autenticado (sem token ou token inválido)
// 403 Forbidden     → autenticado mas sem permissão para o recurso
function authorizationErrorResponse(res: Response, isAuthenticated: boolean): void {
  if (!isAuthenticated) {
    res.status(401).json({ error: { code: 'UNAUTHENTICATED', message: 'Authentication required' } })
  } else {
    res.status(403).json({ error: { code: 'FORBIDDEN', message: 'You do not have permission to access this resource' } })
  }
}
 
export { errorHandler, authErrorResponse, authorizationErrorResponse }

Bootstrap seguro da aplicação

Configuração mínima de segurança para uma aplicação Express em produção:

import express from 'express'
import helmet from 'helmet'
import rateLimit from 'express-rate-limit'
import { z } from 'zod'
import crypto from 'node:crypto'
 
// 1. Validar env vars na inicialização — falha rápido se algo estiver ausente
const EnvSchema = z.object({
  NODE_ENV: z.enum(['development', 'test', 'production']),
  JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 characters'),
  DATABASE_URL: z.string().url(),
  REDIS_URL: z.string().url(),
  PORT: z.coerce.number().default(3000),
  ALLOWED_ORIGINS: z.string().transform((val) => val.split(',')),
})
 
const env = EnvSchema.parse(process.env)
 
const app = express()
 
// 2. Helmet — headers de segurança HTTP (antes de qualquer rota)
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", (_req, res) => `'nonce-${(res as any).locals.nonce}'`],
        styleSrc: ["'self'", "'unsafe-inline'"],
        imgSrc: ["'self'", 'data:', 'https:'],
      },
    },
  }),
)
 
// Gerar nonce por request (para CSP com scripts inline)
app.use((_req, res, next) => {
  res.locals.nonce = crypto.randomBytes(16).toString('base64')
  next()
})
 
// 3. CORS com allowlist explícita
app.use((_req, res, next) => {
  const origin = _req.headers.origin
  if (origin && env.ALLOWED_ORIGINS.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin)
    res.setHeader('Access-Control-Allow-Credentials', 'true')
    res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS')
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization')
  }
  if (_req.method === 'OPTIONS') {
    res.sendStatus(204)
    return
  }
  next()
})
 
// 4. Rate limiting global
const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  standardHeaders: 'draft-7',
  legacyHeaders: false,
})
app.use(globalLimiter)
 
// 5. Rate limiting restrito para auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 10,
  message: { error: { code: 'RATE_LIMIT_EXCEEDED', message: 'Too many authentication attempts' } },
  standardHeaders: 'draft-7',
  legacyHeaders: false,
})
app.use('/auth', authLimiter)
 
// 6. Body parsing com limite de tamanho
app.use(express.json({ limit: '10kb' }))
 
export { app, env }

JWT verify com proteção contra algorithm confusion

import jwt from 'jsonwebtoken'
import { z } from 'zod'
 
const JwtPayloadSchema = z.object({
  sub: z.string(),
  email: z.string().email(),
  role: z.enum(['admin', 'user', 'moderator']),
  iat: z.number(),
  exp: z.number(),
})
 
type JwtPayload = z.infer<typeof JwtPayloadSchema>
 
function verifyAccessToken(token: string, secret: string): JwtPayload {
  // Sempre passar algorithms explicitamente — previne algorithm confusion attack
  // Se omitir, um atacante pode forjar token com alg: "none" ou trocar HS256 por RS256
  const raw = jwt.verify(token, secret, { algorithms: ['HS256'] })
 
  // Validar estrutura do payload com Zod — jwt.verify não valida claims customizados
  const result = JwtPayloadSchema.safeParse(raw)
  if (!result.success) {
    throw new Error('Invalid token payload structure')
  }
 
  return result.data
}
 
export { verifyAccessToken, JwtPayload }

Armadilhas comuns

CORS com * e credentials: true é inválido e perigoso

Configurar Access-Control-Allow-Origin: * junto com Access-Control-Allow-Credentials: true viola a spec CORS e browsers modernos bloqueiam a requisição. Mais grave: alguns proxies e configurações incorretas de gateway aceitam e repassam ambos os headers, expondo APIs autenticadas a qualquer origem. A configuração correta é sempre uma allowlist explícita de origens: res.setHeader('Access-Control-Allow-Origin', allowedOrigin) onde allowedOrigin é validado contra uma lista de origens aprovadas.

Erros de auth com mensagem específica vazam informação

Retornar "User not found" vs "Wrong password" permite a um atacante confirmar quais emails estão cadastrados (user enumeration attack). A mitigação é sempre retornar a mesma mensagem genérica: "Invalid credentials" — e tomar o mesmo tempo para processar ambos os casos (comparação de hash mesmo quando o usuário não existe, usando bcrypt.compare com um hash fake para evitar timing attack).

jwt.verify sem algorithms abre brecha de algorithm confusion

jwt.verify(token, secret) sem { algorithms: ['HS256'] } aceita qualquer algoritmo declarado no header do token, incluindo "none". Um atacante pode criar um token sem assinatura com alg: "none" e bypassar a verificação. Sempre passe o array de algoritmos aceitos explicitamente.

Rate limiting em memória não funciona em múltiplas instâncias

O store padrão do express-rate-limit é em memória (por processo). Em um cluster com 4 workers ou 3 pods Kubernetes, cada instância tem seu próprio contador — o limite efetivo é multiplicado pelo número de instâncias. Use rate-limit-redis com um store Redis compartilhado para garantir que o limite seja global.

Em entrevista

Q: How would you secure a Node.js REST API end-to-end?

A: I’d think of security in layers. At the network layer, HTTPS everywhere and a reverse proxy handling TLS termination. At the HTTP layer, Helmet.js for security headers — CSP, HSTS, X-Frame-Options — and rate limiting on all endpoints, stricter on authentication. At the application boundary, all external input validated with Zod before touching any business logic. For authentication, JWTs with short expiry signed with HS256 or RS256 (never alg: none), plus a refresh token rotation pattern with a Redis blacklist. Authorization is checked server-side on every request — ownership verification for resource access to prevent BOLA/IDOR. Secrets never in code, only from environment variables validated at startup, pulled from AWS Secrets Manager or Vault in production. And supply chain: npm ci in CI, npm audit as a pipeline gate, socket.dev for real-time package threat analysis.

Q: What’s the difference between authentication and authorization, and how do you implement both in Node.js?

A: Authentication answers “who are you?” — verifying identity. Authorization answers “what are you allowed to do?” — verifying permissions. They must always happen in that order, and on the server side. In Node.js, authentication typically means validating a JWT with jsonwebtoken’s verify() using an explicit algorithms array, or running an OIDC flow with openid-client. Authorization for simple role-based rules can live in JWT claims with a checkRole middleware, but for anything conditional — like “users can only edit their own posts” — I use casl, which lets me express can('update', 'Post', { authorId: user.id }) and check it against the actual resource. The key mistake is checking authorization only in the frontend, or forgetting to verify ownership when the user provides a resource ID in the URL.

Q: How do you prevent OWASP A01 Broken Access Control in a Node.js API?

A: A01 is consistently the top OWASP risk because it’s easy to miss. The main patterns I protect against are BOLA (Broken Object Level Authorization) — where a user accesses another user’s resource by changing an ID in the URL — and privilege escalation. For BOLA, the rule is: never trust IDs from the request alone. Always verify ownership server-side: WHERE id = ? AND owner_id = ?. For privilege escalation, I never let users set their own role via API, and I check the role from the JWT, not from the database row the user controls. In code reviews I specifically look for routes that accept a resource ID without verifying it belongs to the authenticated user — that pattern appears constantly in CRUD APIs.

Q: How do you handle secrets in a Node.js production environment?

A: The principles are: secrets never in source code, never in environment variables baked into Docker images, and rotatable without downtime. In production I pull secrets from AWS Secrets Manager or HashiCorp Vault at startup, using the SDK — the app crashes fast if a required secret is missing, which is better than running with a wrong configuration. I validate all environment variables with a Zod schema at boot: EnvSchema.parse(process.env). For rotation, the pattern is to support two valid versions of a secret simultaneously during the rotation window — issue new JWTs signed with the new key, keep accepting old ones until they expire. No manual secret rotation that requires a deployment.

Vocabulário

  • Supply chain attack: ataque que compromete uma dependência (pacote npm, build tool, CI pipeline) em vez de atacar diretamente a aplicação
  • BOLA (Broken Object Level Authorization): vulnerabilidade onde um usuário acessa recursos de outro usuário alterando um ID na requisição; sinônimo de IDOR (Insecure Direct Object Reference)
  • Algorithm confusion: ataque JWT onde o atacante altera o campo alg do header para none ou troca RS256 por HS256, bypassando verificação de assinatura quando algorithms não é passado explicitamente ao verify()
  • SSRF (Server-Side Request Forgery): vulnerabilidade onde o servidor realiza requisições HTTP a destinos controlados pelo atacante — pode expor metadados de cloud (169.254.169.254) ou serviços internos
  • CSP (Content Security Policy): header HTTP que instrui o browser sobre quais origens de scripts, estilos e recursos são permitidas — principal defesa contra XSS
  • HSTS (HTTP Strict Transport Security): header que instrui o browser a sempre usar HTTPS para o domínio pelo período definido em maxAge, impedindo downgrade para HTTP
  • Rate limiting: mecanismo que limita o número de requisições de uma origem em uma janela de tempo — protege contra brute force, DoS e abuso de API
  • Input validation: verificação que dados externos (req.body, req.query, req.params) obedecem o schema esperado antes de qualquer processamento — primeira linha de defesa contra injection
  • Least privilege: princípio de segurança onde um usuário, serviço ou processo recebe apenas as permissões mínimas necessárias para sua função
  • Secret rotation: processo de substituição periódica de segredos (API keys, JWT secrets, DB passwords) sem downtime — requer suporte a múltiplas versões válidas durante a transição
  • CVE (Common Vulnerabilities and Exposures): identificador único para vulnerabilidades de segurança conhecidas; reportado por npm audit
  • Timing attack: ataque que mede diferenças de tempo de resposta para inferir informação — e.g., comparar strings com === em vez de crypto.timingSafeEqual vaza se o hash começou a divergir

Casos práticos

Auditoria de segurança antes de go-live

A equipe tem 48 horas antes do lançamento de uma API em produção e precisa de uma auditoria rápida de segurança. O cheatsheet serve como roteiro: a engenheira de segurança percorre o checklist de PR review item a item, abrindo issues para cada ponto falho encontrado.

Resultado da auditoria em um caso típico: 3 endpoints de autenticação sem rate limiting (critica — OWASP A04), jwt.verify sem { algorithms } em dois middlewares (crítica — A07), CSP com unsafe-inline para scripts (alta — A05), dois segredos hardcoded em arquivo de configuração de staging (alta — A02), e npm install em vez de npm ci no Dockerfile (média — A08).

A priorização é direta pelo critério de impacto: os dois itens críticos bloqueiam o go-live. Os três itens de alta prioridade entram no sprint seguinte. O time não precisa conhecer de cabeça todo o OWASP Top 10 — o cheatsheet opera como externalização de memória especializada, reduzindo o risco de omissão em revisões de tempo curto.

# Sequência de auditoria rápida com CLI
npm audit --audit-level=high             # dependências com CVE high/critical
grep -r "Math.random()" src/             # tokens inseguros
grep -rn "jwt.verify" src/ | grep -v "algorithms"  # jwt sem algorithm fixo
grep -rn "process.env\." src/ | grep -v "env\."    # env vars sem validação centralizada
grep -rn "console.log.*password\|console.log.*token" src/  # logs com dados sensíveis

Onboarding de desenvolvedor junior em segurança Node.js

Um engenheiro júnior entra no time e precisa aprender as práticas de segurança do projeto em uma semana. Em vez de ler as 9 notas do galho sequencialmente (sobrecarga), o tech lead usa esta nota como ponto de entrada:

  1. Dia 1: decision tree — qual ferramenta para cada área de segurança e por quê
  2. Dia 2: checklist de PR — ler o checklist, depois revisar um PR real de auth e identificar o que está correto e o que está faltando
  3. Dia 3: padrões de resposta de erro e bootstrap seguro — entender por que mensagens genéricas e por que zod.parse na inicialização
  4. Dia 4: aprofundar nas notas das áreas mais fracas (ex: se nunca trabalhou com JWT, ir para nota 04)
  5. Dia 5: simular um code review completo usando o checklist como guia

Ao final, o júnior tem o mapa mental correto: “quando vejo um endpoint que aceita ID de recurso, lembro do checklist de BOLA. Quando vejo jwt.verify, verifico se tem algorithms.” O cheatsheet não substitui o entendimento — ele ancora os padrões para o contexto de decisão real.

Escolha entre casl e casbin em um sistema multi-tenant

Um SaaS com múltiplos tenants precisa decidir entre casl e casbin para autorização. A decision tree da nota aponta para a bifurcação correta: casl se as regras são expressas em TypeScript com condicionais de atributos; casbin se as políticas precisam ser gerenciadas externamente (por operadores, sem re-deploy).

O SaaS tem dois perfis de usuário: admins de tenant (gerenciam usuários e dados do próprio tenant) e usuários finais (acessam apenas seus próprios recursos). Há também um super admin da plataforma (acessa qualquer tenant para suporte). Este cenário mistura RBAC (roles fixas: admin, user, super_admin) com ABAC (condições: tenantId, ownerId).

A escolha final é casl: as regras são estáticas o suficiente para viver no código, TypeScript fornece type safety nas habilidades, e a integração com NestJS via @casl/ability é madura. casbin seria preferível se admins de tenant precisassem configurar regras personalizadas via UI — mas nesse caso o design é fixo.

import { AbilityBuilder, createMongoAbility } from '@casl/ability'
 
type Actions = 'read' | 'create' | 'update' | 'delete' | 'manage'
type Subjects = 'User' | 'Document' | 'Invoice' | 'Tenant' | 'all'
 
function defineAbilityFor(user: { role: string; tenantId: string; id: string }) {
  const { can, cannot, build } = new AbilityBuilder(createMongoAbility)
 
  if (user.role === 'super_admin') {
    // Super admin pode tudo em qualquer tenant
    can('manage', 'all')
  } else if (user.role === 'tenant_admin') {
    // Tenant admin gerencia apenas recursos do próprio tenant
    can('manage', ['User', 'Document', 'Invoice'], { tenantId: user.tenantId })
    // Mas não pode deletar o próprio tenant (proteção extra)
    cannot('delete', 'Tenant')
  } else {
    // Usuário comum vê e edita apenas seus próprios documentos dentro do tenant
    can('read', 'Document', { tenantId: user.tenantId })
    can(['create', 'update'], 'Document', { tenantId: user.tenantId, ownerId: user.id })
    can('read', 'Invoice', { tenantId: user.tenantId, userId: user.id })
  }
 
  return build()
}
 
// Verificação server-side: sempre com o documento já carregado do banco
async function getDocument(req: express.Request, res: express.Response) {
  const doc = await db.document.findUnique({ where: { id: req.params.id } })
  if (!doc) return res.status(404).json({ error: 'Not found' })
 
  const ability = defineAbilityFor(req.user)
  if (ability.cannot('read', subject('Document', doc))) {
    return res.status(403).json({ error: 'Forbidden' })
  }
  return res.json(doc)
}

A decisão casl vs casbin não é técnica apenas — é organizacional. Se o time de produto vai querer no futuro uma interface de configuração de permissões para clientes configurarem suas próprias regras, casbin com adapter de banco é a escolha correta desde o início.

O que vem a seguir

Esta nota encerra o galho 8 de segurança da trilha Node Senior. Para aprofundar cada área:

Fora do galho, as próximas fronteiras naturais são Microservices (segurança inter-serviço: mTLS, service mesh, propagação de contexto) e Observabilidade (correlação de logs de segurança com traces e métricas de anomalia).

O cheatsheet deve ser revisitado a cada nova versão de dependência crítica (helmet, express-rate-limit, jsonwebtoken), pois APIs mudam e novos vetores surgem com o tempo.

Para entrevistas sênior de segurança, o exercício mais valioso é percorrer mentalmente a decision tree e o checklist de PR para um cenário hipotético dado pelo entrevistador — demonstrando não apenas o que fazer, mas o raciocínio sobre por quê cada camada existe e como as categorias OWASP se encadeiam. A profundidade de resposta que separa sênior de pleno nesse tópico é a capacidade de articular os trade-offs (ex: casl vs casbin, npm ci --ignore-scripts e o custo de não ter alguns build scripts) sem depender de memorização de APIs específicas.

Fontes

  • OWASP Top 10:2021 — lista oficial com definições, exemplos e guias de prevenção para cada categoria
  • OWASP Cheat Sheet Series — índice de guias técnicos para todas as categorias, incluindo Authentication, JWT, SSRF e Injection
  • Node.js Security Best Practices — guia oficial Node.js cobrindo DoS, injection, HTTP headers e supply chain
  • Helmet.js Documentation — referência oficial de todos os middlewares Helmet v7 e opções de CSP
  • express-rate-limit — README — documentação completa incluindo Redis store, keyGenerator e options v7
  • casl — Getting Started — guia oficial casl v6 com AbilityBuilder, subject helper e integração com NestJS e React
  • OWASP Testing Guide v4.2 — metodologia completa de teste de segurança, incluindo testes de BOLA, authentication bypass e SSRF
  • Snyk Learn — Node.js Security — trilha de aprendizado com exemplos práticos de cada categoria OWASP em Node.js, com exercícios interativos
  • Socket.dev — análise de ameaças em tempo real para pacotes npm, detectando comportamento suspeito em scripts de instalação antes do npm install

Veja também