Security headers automatizados: middleware e proxies
1. Por que Automatizar Security Headers?
Manter security headers manualmente em cada rota ou endpoint é uma receita para falhas de segurança. Um estudo do Google em 2023 mostrou que 72% dos sites que implementaram Content Security Policy (CSP) o fizeram de forma incompleta, deixando brechas para XSS. A automação resolve três problemas críticos:
- Esquecimento humano: desenvolvedores podem pular headers em rotas novas
- Inconsistência: diferentes equipes aplicam políticas diferentes
- Manutenção: atualizar um header exige alterar dezenas de arquivos
Os headers essenciais que devem ser automatizados incluem:
Content-Security-Policy: default-src 'self'
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=()
2. Implementação via Middleware em Frameworks Web
Express.js com Helmet
O pacote helmet é o padrão ouro para Node.js. Ele configura 15 headers de segurança automaticamente:
npm install helmet
const express = require('express');
const helmet = require('helmet');
const app = express();
// Configuração padrão segura
app.use(helmet());
// Personalização avançada
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://analytics.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https://images.example.com"],
},
},
crossOriginEmbedderPolicy: false, // Desabilitar se usar CDN
})
);
app.get('/', (req, res) => {
res.send('Headers seguros aplicados');
});
Django com middleware customizado
Django permite criar middlewares que interceptam todas as respostas:
# middleware.py
from django.utils.deprecation import MiddlewareMixin
class SecurityHeadersMiddleware(MiddlewareMixin):
def process_response(self, request, response):
response['X-Content-Type-Options'] = 'nosniff'
response['X-Frame-Options'] = 'DENY'
response['Referrer-Policy'] = 'strict-origin-when-cross-origin'
if request.is_secure():
response['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response
# settings.py
MIDDLEWARE = [
'app.middleware.SecurityHeadersMiddleware',
# ... outros middlewares
]
# Para CSP, use django-csp
INSTALLED_APPS = [
'csp',
# ...
]
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "https://cdn.example.com")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
FastAPI/Starlette com SecureHeaders
pip install secure
from fastapi import FastAPI
from secure import SecureHeaders
app = FastAPI()
secure_headers = SecureHeaders()
@app.middleware("http")
async def add_security_headers(request, call_next):
response = await call_next(request)
secure_headers.framework.fastapi(response)
return response
@app.get("/")
async def root():
return {"message": "Headers seguros via middleware"}
3. Proxies Reversos como Camada Centralizada
Nginx
O Nginx permite aplicar headers globalmente ou por localização:
server {
listen 443 ssl;
server_name exemplo.com;
# Headers globais
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# HSTS condicional
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# CSP dinâmico com map
map $uri $csp_policy {
default "default-src 'self'";
~^/api/ "default-src 'self'; script-src 'none'";
}
add_header Content-Security-Policy $csp_policy always;
location / {
proxy_pass http://localhost:3000;
}
}
Apache com mod_headers
<VirtualHost *:443>
ServerName exemplo.com
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
<LocationMatch "^/api/">
Header unset X-Frame-Options
Header set Content-Security-Policy "default-src 'self'; script-src 'none'"
</LocationMatch>
</VirtualHost>
Traefik em Docker Compose
version: '3.8'
services:
traefik:
image: traefik:v3.0
command:
- "--providers.docker=true"
- "--entrypoints.websecure.address=:443"
labels:
- "traefik.http.middlewares.security-headers.headers.customframeoptionsvalue=DENY"
- "traefik.http.middlewares.security-headers.headers.contenttypenosniff=true"
- "traefik.http.middlewares.security-headers.headers.stsseconds=31536000"
- "traefik.http.middlewares.security-headers.headers.stsincludesubdomains=true"
- "traefik.http.middlewares.security-headers.headers.customresponseheaders.Content-Security-Policy=default-src 'self'"
app:
image: myapp:latest
labels:
- "traefik.http.routers.app.middlewares=security-headers@docker"
4. Headers Dinâmicos com Base na Rota ou Ambiente
Diferenciando produção vs. desenvolvimento
# Node.js com dotenv
const helmet = require('helmet');
require('dotenv').config();
const cspDirectives = {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
};
if (process.env.NODE_ENV === 'development') {
cspDirectives.scriptSrc.push("'unsafe-eval'"); // Para hot reload
cspDirectives.styleSrc.push("'unsafe-inline'");
}
app.use(helmet({
contentSecurityPolicy: { directives: cspDirectives }
}));
Headers específicos para APIs
// Express middleware condicional
function apiSecurityHeaders(req, res, next) {
if (req.path.startsWith('/api/')) {
res.setHeader('X-Frame-Options', 'SAMEORIGIN'); // APIs podem ser embutidas
res.setHeader('Content-Security-Policy', "default-src 'none'"); // APIs não renderizam HTML
} else {
res.setHeader('X-Frame-Options', 'DENY');
}
next();
}
app.use(apiSecurityHeaders);
5. Testando e Validando Headers Automaticamente
Testes com curl
# Verificar headers de uma URL
curl -I https://exemplo.com
# Script de validação
#!/bin/bash
URL="https://exemplo.com"
HEADERS=$(curl -sI $URL)
echo "$HEADERS" | grep -q "Strict-Transport-Security" && echo "HSTS presente" || echo "HSTS ausente!"
echo "$HEADERS" | grep -q "X-Frame-Options: DENY" && echo "X-Frame-Options OK" || echo "X-Frame-Options ausente!"
Testes unitários com supertest (Node.js)
const request = require('supertest');
const app = require('./app');
describe('Security Headers', () => {
it('deve incluir X-Frame-Options', async () => {
const res = await request(app).get('/');
expect(res.headers['x-frame-options']).toBe('DENY');
});
it('deve incluir HSTS em HTTPS', async () => {
const res = await request(app)
.get('/')
.set('X-Forwarded-Proto', 'https');
expect(res.headers['strict-transport-security']).toBeDefined();
});
it('CSP deve bloquear scripts inline', async () => {
const res = await request(app).get('/');
expect(res.headers['content-security-policy']).toContain("script-src 'self'");
});
});
Testes de integração com pytest (Python)
import requests
from django.test import TestCase
class SecurityHeadersTest(TestCase):
def test_headers_presentes(self):
response = self.client.get('/')
self.assertEqual(response['X-Frame-Options'], 'DENY')
self.assertEqual(response['X-Content-Type-Options'], 'nosniff')
self.assertIn('Strict-Transport-Security', response)
Verificação em CI/CD com GitHub Actions
name: Security Headers Check
on: [push, pull_request]
jobs:
check-headers:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Start application
run: docker-compose up -d
- name: Wait for app
run: sleep 10
- name: Check security headers
run: |
HEADERS=$(curl -sI http://localhost:3000)
echo "$HEADERS" | grep -q "X-Frame-Options: DENY" || exit 1
echo "$HEADERS" | grep -q "Content-Security-Policy" || exit 1
6. Monitoramento e Alertas de Desvios
Logging de headers ausentes
// Express middleware de monitoramento
app.use((req, res, next) => {
const originalSend = res.send;
res.send = function(body) {
const requiredHeaders = [
'x-frame-options',
'x-content-type-options',
'strict-transport-security'
];
requiredHeaders.forEach(header => {
if (!res.getHeader(header)) {
console.warn(`Header ausente: ${header} na rota ${req.path}`);
// Enviar métrica para Prometheus
metrics.securityHeadersMissing.inc({ header, route: req.path });
}
});
originalSend.call(this, body);
};
next();
});
Métricas com Prometheus
# Prometheus metrics endpoint
const prometheus = require('prom-client');
const headersPresentGauge = new prometheus.Gauge({
name: 'security_headers_present',
help: 'Security headers present in responses',
labelNames: ['header', 'route']
});
// Atualizar métricas a cada requisição
app.use((req, res, next) => {
res.on('finish', () => {
const headers = ['x-frame-options', 'content-security-policy', 'strict-transport-security'];
headers.forEach(header => {
headersPresentGauge.set(
{ header, route: req.path },
res.getHeader(header) ? 1 : 0
);
});
});
next();
});
7. Armadilhas Comuns e Boas Práticas
Cuidado com CDNs e balanceadores
CDNs como Cloudflare podem sobrescrever headers. Configure explicitamente:
# Nginx atrás de CDN
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy $csp always;
# Cloudflare: desabilitar "Auto Minify" que remove headers
# Verificar se "Security Headers" não está conflitando com sua configuração
Headers conflitantes
X-Frame-Options e CSP frame-ancestors podem conflitar. Prefira CSP moderno:
# Erro comum: ambos configurados
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'self' # Conflito!
# Correto: usar apenas CSP
Content-Security-Policy: frame-ancestors 'none'
# Remove X-Frame-Options se CSP cobrir
Versionamento de políticas
Mantenha políticas versionadas para rollback seguro:
# Configuração versionada
const CSP_POLICIES = {
v1: "default-src 'self'",
v2: "default-src 'self'; script-src 'self' https://cdn.example.com",
v3: "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'unsafe-inline'"
};
const activePolicy = process.env.CSP_VERSION || 'v3';
app.use(helmet({
contentSecurityPolicy: {
directives: parsePolicy(CSP_POLICIES[activePolicy])
}
}));
Boas práticas finais
- Teste em staging primeiro: headers restritivos podem quebrar funcionalidades
- Use report-uri/report-to no CSP: capture violações sem bloquear
- Documente exceções: cada header personalizado deve ter justificativa
- Automatize a rotação de HSTS: nunca remova HSTS sem planejamento
- Monitore no Kibana/Grafana: crie dashboards de conformidade de headers
Referências
- Helmet.js - Documentação Oficial — Middleware de segurança para Express.js, com 15 headers configuráveis e exemplos de personalização
- MDN Web Docs: Content-Security-Policy — Documentação completa sobre CSP, diretivas e exemplos práticos
- Nginx: add_header directive — Guia oficial do Nginx para adicionar headers HTTP, incluindo condicionais com map
- Traefik: Middlewares - Headers — Documentação oficial do Traefik para configuração de security headers via Docker Compose e Kubernetes
- securityheaders.com - Ferramenta de Teste — Scanner online que avalia a implementação de security headers e fornece nota de segurança
- Django CSP - django-csp — Biblioteca para implementar Content Security Policy em projetos Django, com suporte a relatórios de violação
- OWASP: HTTP Security Headers Cheat Sheet — Guia de referência rápida da OWASP sobre todos os security headers HTTP e suas configurações recomendadas