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ça

    HSTS 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