Quando falamos sobre segurança em APIs, normalmente pensamos em autenticação, autorização e validação de dados.
Mas existe outra camada de proteção que muitas vezes passa despercebida: os headers de segurança HTTP.
Um deles é o Content Security Policy (CSP).
Neste tutorial, vamos entender o que é CSP e como configurá-lo em uma aplicação NestJS utilizando o Helmet.
O que é CSP?
O Content Security Policy é um mecanismo de segurança que permite definir quais recursos o navegador está autorizado a carregar e executar.
Por exemplo, podemos restringir:
- Scripts JavaScript.
- Arquivos CSS.
- Imagens.
- Recursos de domínios externos.
Isso ajuda a reduzir os riscos de ataques como Cross-Site Scripting (XSS), limitando a execução de conteúdos não autorizados.
Vale destacar que o CSP é uma camada adicional de proteção, não um substituto para validação de entradas e tratamento seguro de dados.
1. Instalando o Helmet
O NestJS permite utilizar o Helmet como middleware para configurar diversos headers de segurança.
Primeiro, instale a dependência:
npm install helmet
Depois, importe o Helmet no arquivo main.ts:
import helmet from 'helmet';
2. Configurando o CSP
Vamos adicionar a configuração ao bootstrap da aplicação.
import { NestFactory } from '@nestjs/core';
import helmet from 'helmet';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
},
},
crossOriginEmbedderPolicy: false,
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
}),
);
await app.listen(3000);
}
bootstrap();
Com isso, o Helmet passa a enviar headers de segurança nas respostas HTTP da aplicação.
Importante: o CSP é aplicado pelo navegador aos documentos que ele carrega. Em uma API que retorna apenas JSON, seu efeito direto costuma ser limitado.
Ele se torna especialmente relevante quando o NestJS também entrega páginas HTML, documentação Swagger ou outros recursos consumidos pelo navegador.
3. Entendendo as diretivas
Vamos analisar as configurações que utilizamos.
defaultSrc: ["'self'"]
Define a política padrão para recursos que não possuem uma diretiva específica.
O valor 'self' permite carregar recursos da mesma origem da página.
scriptSrc: ["'self'"]
Restringe o carregamento de scripts à própria origem.
Isso ajuda a impedir a execução de scripts externos não autorizados.
styleSrc: ["'self'", "'unsafe-inline'"]
Permite carregar estilos da própria origem e utilizar estilos inline.
O unsafe-inline facilita a compatibilidade com aplicações que dependem desse comportamento, mas reduz a proteção oferecida pelo CSP.
Sempre que possível, prefira estilos externos ou mecanismos como nonces e hashes.
imgSrc: ["'self'", "data:", "https:"]
Permite carregar imagens de três tipos de origem:
-
'self': mesma origem da aplicação. -
data:: imagens incorporadas utilizando Data URLs. -
https:: imagens provenientes de qualquer origem HTTPS.
Essa configuração oferece flexibilidade, mas pode ser restringida a domínios específicos quando necessário.
4. E as outras configurações do Helmet?
Além do CSP, adicionamos duas configurações importantes.
crossOriginEmbedderPolicy: false
Desativa o envio do header Cross-Origin-Embedder-Policy pelo Helmet.
Isso pode evitar problemas de compatibilidade com recursos externos que não estão preparados para as exigências dessa política.
Por outro lado, desativá-lo significa abrir mão dessa camada de isolamento.
hsts
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
}
O HTTP Strict Transport Security (HSTS) instrui navegadores a utilizar HTTPS para futuras conexões com o domínio.
Nessa configuração:
-
maxAge: mantém a política por aproximadamente um ano. -
includeSubDomains: estende a política aos subdomínios. -
preload: declara a intenção de utilizar a lista de pré-carregamento HSTS dos navegadores.
Atenção: configurar preload: true não adiciona automaticamente seu domínio à lista de preload.
Antes de utilizar essa opção em produção, é importante garantir que todos os subdomínios envolvidos suportem HTTPS corretamente.
5. Como verificar se está funcionando?
Inicie a aplicação:
npm run start:dev
Depois, faça uma requisição para um endpoint existente:
curl -i http://localhost:3000
Você deverá encontrar um header semelhante a este:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:
O Helmet também pode adicionar outras diretivas padrão à política, mesmo que não estejam explicitamente declaradas no código.
Outra opção é abrir o DevTools do navegador, acessar a aba Network e inspecionar os Response Headers.
Para verificar se uma política realmente bloqueia recursos, é necessário testá-la em uma página HTML que tente carregá-los.
6. Quais são os trade-offs?
Apesar dos benefícios, configurar CSP exige alguns cuidados.
Benefícios
- Redução da superfície de ataque: ajuda a mitigar determinadas formas de XSS.
- Controle de recursos: permite definir quais origens são autorizadas.
- Segurança adicional: adiciona uma camada de proteção no navegador.
- Centralização: facilita a configuração de headers de segurança.
Trade-offs
- Compatibilidade: políticas restritivas podem bloquear recursos legítimos.
- Estilos e scripts inline: algumas bibliotecas podem exigir ajustes.
- Integrações externas: domínios utilizados pela aplicação precisam ser considerados.
- Manutenção: novas dependências podem exigir alterações na política.
Uma boa estratégia é começar utilizando Content-Security-Policy-Report-Only para identificar possíveis violações antes de aplicar bloqueios em produção.
Conclusão
Implementar CSP com Helmet no NestJS é relativamente simples.
O maior desafio não está em adicionar o middleware, mas em definir uma política que equilibre segurança e compatibilidade.
Também é importante lembrar que o Helmet oferece outras proteções além do CSP, e que headers de segurança não substituem boas práticas de desenvolvimento.
Segurança não depende de uma única configuração. Ela é construída em camadas.
Top comments (0)