Helmet.js: headers de segurança em APIs Node.js
Helmet.js configura automaticamente dezenas de headers HTTP de segurança recomendados pelo OWASP. Em uma linha de código, sua API ativa proteções contra XSS, clickjacking, MIME sniffing e information disclosure. A configuração padrão é segura — mas entender cada header permite ajustar para seu caso de uso.
Configuração básica do Helmet
Adicionar Helmet com configuração de produção:
typescript
// npm install helmet
import helmet from 'helmet'
import { Express } from 'express'
export function configurarSeguranca(app: Express) {
// Helmet deve ser o PRIMEIRO middleware:
app.use(helmet({
// Content-Security-Policy: controla de onde scripts/styles podem ser carregados
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
// Para APIs puras (sem HTML): pode desabilitar CSP
// contentSecurityPolicy: false,
},
},
// HTTP Strict Transport Security: forçar HTTPS por 1 ano:
hsts: {
maxAge: 31_536_000, // 1 ano em segundos
includeSubDomains: true,
preload: true, // incluir na lista de preload do Chrome
},
// X-Frame-Options: DENY (previne clickjacking):
frameguard: { action: 'deny' },
// X-Content-Type-Options: nosniff (previne MIME sniffing):
noSniff: true,
// X-XSS-Protection: desabilitado (browsers modernos usam CSP)
xssFilter: false,
// Referrer-Policy: controlar o header Referer:
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
// X-Permitted-Cross-Domain-Policies:
permittedCrossDomainPolicies: false,
// Remove X-Powered-By: Express (não revelar tecnologia):
hidePoweredBy: true,
}))
}Content Security Policy para APIs com frontend
CSP ajustado para APIs que servem HTML (docs, admin):
typescript
// Para APIs que servem APENAS JSON: CSP simples ou desabilitado
app.use(helmet({ contentSecurityPolicy: false }))
// Para APIs que servem algum HTML (Swagger UI, painel admin):
// Nonce-based CSP (mais seguro que 'unsafe-inline'):
import crypto from 'crypto'
app.use((req, res, next) => {
// Gerar nonce único por requisição:
res.locals.nonce = crypto.randomBytes(16).toString('base64')
next()
})
app.use((req, res, next) => {
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
(req, res) => `'nonce-${(res as Response).locals.nonce}'`,
],
styleSrc: [
"'self'",
(req, res) => `'nonce-${(res as Response).locals.nonce}'`,
],
// Permitir CDN específica:
scriptSrc: ["'self'", 'https://cdn.jsdelivr.net'],
// Conectar para APIs externas:
connectSrc: ["'self'", 'https://api.seudominio.com.br'],
imgSrc: ["'self'", 'data:', 'https:'],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
})(req, res, next)
})
// No template HTML: usar o nonce
// <script nonce="<%= nonce %>">...</script>
// Relatório de violações CSP (monitorar erros em produção):
// contentSecurityPolicy: {
// directives: {
// reportUri: '/api/csp-report',
// ...
// }
// }Headers de segurança customizados
Adicionar headers além do Helmet:
typescript
// Headers extras não cobertos pelo Helmet:
app.use((req, res, next) => {
// Permissions Policy (antes Feature-Policy): controlar APIs do browser:
res.setHeader('Permissions-Policy',
'camera=(), microphone=(), geolocation=(), payment=()'
)
// Cross-Origin Headers (Isolation — necessário para SharedArrayBuffer):
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin')
// Cache de segurança para dados sensíveis:
if (req.path.startsWith('/api/auth') || req.path.startsWith('/api/admin')) {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate')
res.setHeader('Pragma', 'no-cache')
}
next()
})
// Remover headers que revelam stack:
app.use((req, res, next) => {
res.removeHeader('X-Powered-By')
res.removeHeader('Server') // pode precisar configurar no Nginx também
next()
})
// Verificar headers em produção:
// curl -I https://api.seudominio.com.br/api/health | grep -i "x-|strict|content-security|referrer"
// Ferramentas de teste:
// securityheaders.com — analisa headers da sua API
// observatory.mozilla.org — análise completa de segurançaHSTS e forçar HTTPS no Node.js + Nginx
Redirecionar HTTP para HTTPS e configurar HSTS:
bash
# HSTS (HTTP Strict Transport Security) no Nginx:
# /etc/nginx/sites-enabled/api.conf
# Redirecionar HTTP → HTTPS:
server {
listen 80;
server_name api.seudominio.com.br;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name api.seudominio.com.br;
# Certificado SSL (Let's Encrypt):
ssl_certificate /etc/letsencrypt/live/api.seudominio.com.br/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.seudominio.com.br/privkey.pem;
# TLS moderno:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
# HSTS no Nginx (além do Helmet no Node.js):
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
proxy_pass http://127.0.0.1:3000;
}
# No Node.js: forçar HTTPS também (redundância):
app.use((req, res, next) => {
if (process.env.NODE_ENV === 'production' && !req.secure) {
return res.redirect(301, `https://${req.headers.host}${req.url}`)
}
next()
})Auditoria de headers com teste automatizado
Testar headers de segurança nos testes de integração:
typescript
// tests/seguranca-headers.test.ts
import request from 'supertest'
import app from '../src/app'
describe('Headers de segurança', () => {
let res: Response
beforeAll(async () => {
res = await request(app).get('/api/health')
})
test('X-Powered-By não deve ser exposto', () => {
expect(res.headers['x-powered-by']).toBeUndefined()
})
test('X-Content-Type-Options: nosniff', () => {
expect(res.headers['x-content-type-options']).toBe('nosniff')
})
test('X-Frame-Options: DENY', () => {
expect(res.headers['x-frame-options']).toBe('DENY')
})
test('Strict-Transport-Security presente', () => {
expect(res.headers['strict-transport-security']).toMatch(/max-age=\d+/)
})
test('Content-Security-Policy presente', () => {
expect(res.headers['content-security-policy']).toBeDefined()
})
test('Referrer-Policy configurado', () => {
expect(res.headers['referrer-policy']).toBeDefined()
})
// Testar que endpoints de auth têm Cache-Control no-store:
test('Auth endpoints: Cache-Control: no-store', async () => {
const authRes = await request(app).get('/api/auth/me')
expect(authRes.headers['cache-control']).toMatch(/no-store/)
})
})$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS para APIs em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.
Perguntas frequentes
Conteúdos relacionados