Pular para o conteúdo
Fernando Valler

Checklist de segurança para aplicações Backend com NestJS

Um checklist prático com 30 verificações de segurança para aplicações backend construídas com NestJS, cobrindo autenticação, autorização, validação de dados, rate limiting, secrets, CORS, CSRF, uploads, logs, dependências e infraestrutura.

Checklist de segurança para aplicações Backend com NestJS

Construir uma API com NestJS é relativamente simples. Construir uma API que continue segura quando estiver exposta à internet, recebendo dados não confiáveis e lidando com autenticação, arquivos, integrações externas e informações sensíveis exige uma camada adicional de cuidado.

O NestJS fornece diversos mecanismos para implementar segurança, como Guards, Pipes, interceptors, autenticação, autorização, rate limiting e headers de segurança. Mas o framework não consegue decidir quais regras de negócio devem ser protegidas em cada aplicação.

Este checklist reúne verificações que podem ser utilizadas antes de colocar uma aplicação NestJS em produção.


1. Autenticação está implementada corretamente?

Toda aplicação que possui usuários ou dados privados precisa definir claramente como a identidade do cliente será validada.

Verifique:

  • Os endpoints privados exigem autenticação.
  • Tokens inválidos são rejeitados.
  • Tokens expirados são rejeitados.
  • O algoritmo utilizado para validar JWT é explicitamente configurado.
  • A aplicação não aceita tokens com algoritmos inesperados.
  • Chaves e secrets não estão dentro do código-fonte.
  • O mecanismo de refresh token possui proteção contra reutilização indevida.
  • Tokens têm tempo de expiração adequado ao nível de risco da aplicação.

O NestJS documenta a utilização de Guards para proteger rotas e também permite registrar um Guard de autenticação global, deixando explicitamente marcadas apenas as rotas públicas. Essa abordagem reduz o risco de criar acidentalmente um endpoint privado sem proteção.

Uma estratégia interessante é adotar o princípio:

Por padrão, a rota é privada. A exceção precisa ser declarada explicitamente.


2. Autenticação não é autorização

Um usuário autenticado não significa que ele pode acessar qualquer recurso.

Considere uma API:

http
GET /users/123/orders

O fato de o usuário estar autenticado não significa que ele possa consultar os pedidos do usuário 123.

Esse tipo de problema está diretamente relacionado ao Broken Object Level Authorization (BOLA), listado como API1 no OWASP API Security Top 10.

Verifique:

  • Cada recurso verifica quem pode acessá-lo.
  • IDs enviados pelo cliente não são considerados autorização.
  • Um usuário não consegue acessar objetos de outro usuário alterando um ID.
  • Operações administrativas possuem autorização adicional.
  • Alterar, excluir ou exportar dados exige as permissões apropriadas.

No NestJS, Guards podem ser utilizados para estruturar mecanismos como RBAC, mas a autorização de cada recurso ainda precisa refletir as regras reais da aplicação.


3. Evite confiar em dados enviados pelo cliente

Nunca considere seguro algo simplesmente porque existe um DTO ou porque o TypeScript declarou um tipo.

O cliente controla a requisição.

Ele pode enviar:

json
{
  "name": "Fernando",
  "role": "admin",
  "isActive": true,
  "balance": 999999
}

Se a aplicação simplesmente persistir tudo que recebeu, o problema pode virar uma vulnerabilidade de mass assignment ou de autorização em nível de propriedade.

A API deve definir explicitamente quais campos podem ser recebidos em cada operação.

Por isso, prefira DTOs específicos:

typescript
export class CreateUserDto {
  name: string;
  email: string;
}

Em vez de reutilizar diretamente uma entidade de banco de dados como entrada da API.


4. Use ValidationPipe globalmente

A validação dos dados recebidos deve acontecer antes que eles cheguem à lógica de negócio.

Uma configuração inicial comum é:

typescript
app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }),
);

O NestJS documenta whitelist para remover propriedades que não possuem decorators de validação e forbidNonWhitelisted para rejeitar requisições contendo propriedades não permitidas. transform permite converter os valores recebidos para os tipos definidos no DTO.

Checklist:

  • ValidationPipe global.
  • DTOs específicos para entrada.
  • Campos obrigatórios explicitamente validados.
  • Enumerações validadas.
  • Tamanhos máximos definidos.
  • Strings com regras apropriadas.
  • IDs validados.
  • Paginação limitada.
  • Propriedades inesperadas rejeitadas ou removidas.

5. Nunca confie no TypeScript como mecanismo de segurança

Isto:

typescript
createUser(dto: CreateUserDto)

não impede que uma requisição HTTP envie qualquer JSON.

TypeScript fornece segurança durante o desenvolvimento, mas a entrada da aplicação vem da rede e precisa ser validada em runtime.

Em outras palavras:

text
TypeScript ≠ validação de entrada

A validação precisa acontecer na fronteira entre a aplicação e o mundo externo.


6. Configure CORS explicitamente

CORS não deve ser tratado como:

typescript
app.enableCors();

sem avaliar o cenário da aplicação.

Defina explicitamente quais origens podem consumir sua API.

Por exemplo:

typescript
app.enableCors({
  origin: [
    'https://app.exemplo.com',
  ],
  credentials: true,
});

O NestJS permite configurar CORS diretamente na aplicação e utiliza as opções oferecidas pelo adaptador HTTP escolhido.

Evite liberar origens indiscriminadamente em ambientes que trabalham com autenticação por cookies.

Também é importante lembrar:

CORS não substitui autenticação ou autorização.

Ele controla quais navegadores podem realizar determinadas requisições cross-origin; não impede que outro cliente HTTP envie requisições diretamente à API.


7. Proteja aplicações que utilizam cookies contra CSRF

Se sua autenticação utiliza cookies enviados automaticamente pelo navegador, avalie a necessidade de proteção contra Cross-Site Request Forgery (CSRF).

O NestJS documenta o uso de mecanismos específicos de proteção contra CSRF, incluindo o pacote csrf-csrf para aplicações Express.

Além disso, cookies de autenticação devem ser avaliados quanto a atributos como:

text
HttpOnly
Secure
SameSite

A estratégia precisa considerar como frontend, backend e domínio de autenticação estão estruturados.

Uma API que utiliza exclusivamente:

http
Authorization: Bearer <token>

possui um modelo diferente de uma aplicação que depende de sessão autenticada por cookie.


8. Limite tentativas de login e operações sensíveis

Endpoints como:

text
POST /auth/login
POST /auth/forgot-password
POST /auth/reset-password
POST /auth/verify-code
POST /auth/send-code

não devem aceitar tentativas ilimitadas.

O NestJS disponibiliza o @nestjs/throttler para implementar rate limiting e proteção contra ataques como brute force.

Exemplo:

typescript
ThrottlerModule.forRoot({
  throttlers: [
    {
      ttl: 60_000,
      limit: 10,
    },
  ],
});

Mas não use necessariamente a mesma política para toda a aplicação.

Uma requisição de:

http
GET /products

pode ter uma política completamente diferente de:

http
POST /auth/login

Também considere que, em ambientes com múltiplas instâncias, a estratégia de armazenamento do rate limiter precisa ser compatível com a arquitetura distribuída.


9. Não armazene senhas em texto puro

Senhas não devem ser armazenadas como:

text
senha123

nem criptografadas de maneira reversível.

Elas devem ser armazenadas utilizando um algoritmo apropriado de password hashing.

A própria documentação do NestJS recomenda bibliotecas como bcrypt ou argon2 para hashing de senhas.

Exemplo:

typescript
const hash = await bcrypt.hash(password, 12);

E para verificar:

typescript
const valid = await bcrypt.compare(password, hash);

Nunca crie seu próprio algoritmo de hashing.


10. Secrets não devem estar no Git

Procure pelo repositório:

text
JWT_SECRET=
DATABASE_PASSWORD=
AWS_SECRET_ACCESS_KEY=
API_KEY=
PRIVATE_KEY=

Secrets não devem ser commitados no código.

O OWASP recomenda uma abordagem de gerenciamento de secrets que considere armazenamento, provisionamento, auditoria, controle de acesso e rotação.

Use mecanismos apropriados para cada ambiente:

text
Development → .env local
Staging     → secret manager / environment
Production  → secret manager / environment

E não esqueça de revisar:

text
.gitignore
Docker images
CI/CD logs
build artifacts
backups
logs

Remover um secret do arquivo atual não resolve necessariamente um vazamento que já entrou no histórico do Git.


11. Valide também as variáveis de ambiente

Um erro comum é iniciar a aplicação com uma configuração incompleta:

text
DATABASE_URL=
JWT_SECRET=
REDIS_HOST=

e descobrir o problema apenas depois que determinadas rotas forem utilizadas.

O NestJS suporta validação de configuração durante o bootstrap da aplicação. A documentação atual também apresenta opções baseadas em schemas como Zod e outras implementações compatíveis.

A aplicação deveria falhar rapidamente quando uma variável obrigatória está ausente ou inválida.

Isso é muito melhor do que iniciar parcialmente configurada.


12. Use HTTPS

Nunca trate HTTPS como responsabilidade exclusiva do frontend.

Toda comunicação envolvendo:

  • credenciais;
  • tokens;
  • dados pessoais;
  • informações financeiras;
  • informações administrativas;

deve utilizar transporte protegido.

Em produção, verifique também:

text
HTTP → HTTPS
HSTS
TLS configuration
Secure cookies
Proxy configuration

Se houver um reverse proxy, confirme que a aplicação conhece corretamente o esquema e o IP original quando isso impactar autenticação, logs ou rate limiting.


13. Configure headers de segurança

Aplicações HTTP podem utilizar headers para ativar mecanismos de proteção no navegador.

O NestJS atualmente possui app.useSecurityHeaders() para definir headers de segurança com os defaults da implementação baseada no Helmet 8. Também é possível utilizar Helmet diretamente.

Exemplo:

typescript
const app = await NestFactory.create(AppModule);

app.useSecurityHeaders();

Para aplicações compatíveis com versões anteriores do NestJS, Helmet continua sendo uma alternativa documentada:

typescript
import helmet from 'helmet';

app.use(helmet());

A configuração precisa ser testada com recursos como Swagger, GraphQL IDEs e outros componentes que possam ser afetados por CSP.


14. Não exponha informações sensíveis nas mensagens de erro

Evite respostas como:

json
{
  "error": "SQLSTATE[HY000]: Access denied for user..."
}

ou:

json
{
  "error": "/var/www/app/src/modules/users/users.service.ts:42"
}

Em produção, mensagens de erro precisam ser úteis para o cliente sem revelar:

  • stack trace;
  • caminhos internos;
  • credenciais;
  • queries;
  • nomes internos de serviços;
  • detalhes da infraestrutura.

O detalhe completo deve estar disponível para observabilidade interna, e não necessariamente para o usuário.


15. Não registre secrets nos logs

Tenha atenção especial aos logs de:

text
Authorization
Cookie
password
refreshToken
accessToken
apiKey
clientSecret

Não faça:

typescript
logger.debug(request.body);
logger.debug(request.headers);

sem entender o conteúdo desses objetos.

Logs são extremamente úteis para investigação de incidentes, mas precisam ser tratados como dados potencialmente sensíveis. O OWASP recomenda que eventos de segurança sejam registrados de forma consistente e utilizável para investigação e correlação.


16. Não confie apenas na validação do frontend

Este código:

typescript
if (password.length < 8) {
  // ...
}

no React, Vue ou Angular não protege a API.

Um atacante pode ignorar completamente a interface e executar:

bash
curl

ou criar seu próprio cliente HTTP.

Toda regra de segurança relevante precisa ser validada no backend.


17. Cuidado com SQL e NoSQL Injection

Nunca construa queries concatenando diretamente dados recebidos do usuário.

Evite:

typescript
const query = `
  SELECT *
  FROM users
  WHERE email = '${email}'
`;

Prefira mecanismos parametrizados fornecidos pelo ORM ou pelo driver.

A mesma preocupação vale para:

  • SQL;
  • MongoDB;
  • Redis;
  • mecanismos de busca;
  • filtros dinâmicos;
  • comandos externos.

O uso de um ORM reduz alguns riscos, mas não transforma automaticamente todo código em seguro.


18. Limite paginação e quantidade de resultados

Uma rota como:

http
GET /users

não deveria permitir:

http
GET /users?limit=999999999

Defina limites:

text
page >= 1
limit >= 1
limit <= 100

Isso ajuda a reduzir:

  • consumo excessivo de memória;
  • consultas pesadas;
  • respostas gigantes;
  • abuso automatizado.

Esse cuidado também está relacionado ao conceito de Unrestricted Resource Consumption, listado pelo OWASP como API4.


19. Cuidado com uploads de arquivos

Upload é uma superfície de ataque por si só.

Não confie apenas na extensão:

text
arquivo.jpg

Verifique pelo menos:

  • tamanho máximo;
  • MIME type;
  • extensão permitida;
  • conteúdo real quando necessário;
  • quantidade de arquivos;
  • nome do arquivo;
  • local de armazenamento;
  • necessidade de antivírus ou sandbox;
  • possibilidade de execução do arquivo.

O NestJS disponibiliza mecanismos para upload de arquivos, mas esses recursos precisam ser configurados de acordo com os requisitos da aplicação.

Nunca salve uploads diretamente em um diretório onde o servidor possa executá-los.


20. Proteja endpoints de download

Não basta esconder o link de um arquivo.

Evite:

http
GET /files/123

retornar o arquivo simplesmente porque o ID existe.

Verifique:

text
usuário autenticado
recurso pertence ao usuário
usuário possui permissão
arquivo ainda está disponível

Um endpoint de download precisa aplicar autorização exatamente como qualquer outro recurso da API.


21. Proteja integrações externas contra SSRF

Cuidado com endpoints como:

http
POST /fetch-url

recebendo:

json
{
  "url": "https://exemplo.com"
}

Se o backend realiza requisições arbitrárias para URLs fornecidas pelo usuário, pode surgir uma vulnerabilidade de Server-Side Request Forgery (SSRF).

O OWASP inclui SSRF como API7 no API Security Top 10.

Quando possível:

  • utilize allowlists de hosts;
  • bloqueie redes internas;
  • bloqueie localhost;
  • valide redirects;
  • limite tempo de conexão;
  • limite tamanho da resposta;
  • evite permitir protocolos desnecessários.

22. Valide webhooks recebidos

Integrações como:

text
Stripe
GitHub
Mercado Pago
PagSeguro
sistemas internos
provedores de autenticação

podem enviar webhooks.

Não confie apenas no IP de origem.

Sempre que o provedor disponibilizar esse mecanismo, valide:

text
assinatura
timestamp
payload
idempotência

Também armazene identificadores de eventos processados para evitar que o mesmo evento seja executado várias vezes.


23. Implemente idempotência onde necessário

Operações como:

text
criar pagamento
emitir documento
criar pedido
enviar cobrança
processar webhook

podem receber a mesma requisição mais de uma vez.

Uma aplicação segura não deve transformar um retry em múltiplas operações financeiras ou administrativas.

Quando apropriado, utilize uma chave de idempotência:

http
Idempotency-Key: 9c3d...

e garanta que a operação seja processada uma única vez.


24. Controle acesso a funções administrativas

Rotas como:

http
POST /admin/users
DELETE /admin/users/:id
GET /admin/audit
POST /admin/settings

merecem uma camada adicional de autorização.

Não dependa somente de:

typescript
if (user.role === 'admin')

espalhado pela aplicação.

Centralize as regras utilizando Guards, policies ou outro mecanismo consistente.

O NestJS oferece exemplos de RBAC e também suporta outras estratégias de autorização.


25. Não exponha endpoints internos desnecessariamente

Revise a API em busca de rotas como:

text
/debug
/test
/internal
/admin
/metrics
/swagger
/api-json

Nem todo endpoint existente durante o desenvolvimento precisa estar publicamente acessível.

A documentação Swagger também merece atenção. O NestJS expõe tanto a interface quanto o documento OpenAPI configurado pela aplicação.

Em produção, avalie:

text
Swagger protegido por autenticação
Swagger somente em rede interna
Swagger desabilitado

O importante é tomar essa decisão conscientemente.


26. Faça inventário dos endpoints

Uma API grande pode possuir:

text
/users
/users/:id
/orders
/orders/:id
/admin
/internal
/webhooks
/debug

sem que todos tenham a mesma política de segurança.

Faça uma revisão periódica da superfície exposta.

Para cada endpoint, documente:

text
Método
URL
Autenticação
Autorização
Dados recebidos
Dados retornados
Rate limit
Sensibilidade

Isso ajuda a encontrar endpoints esquecidos ou inconsistentes.

O OWASP API Security Top 10 também destaca Improper Inventory Management como API9.


27. Retorne somente os campos necessários

Imagine:

typescript
return user;

e a entidade contém:

text
id
name
email
passwordHash
refreshToken
resetToken
internalNotes

Mesmo que o framework serialize automaticamente o objeto, isso pode gerar vazamento de informações.

Prefira DTOs de resposta:

typescript
export class UserResponseDto {
  id: string;
  name: string;
  email: string;
}

A resposta da API deve ser uma decisão explícita.


28. Atualize dependências e faça auditoria

Uma aplicação NestJS não é composta apenas pelo código que você escreveu.

Ela depende de:

text
NestJS
Express/Fastify
ORM
JWT
bcrypt/argon2
Swagger
Redis
RabbitMQ
bibliotecas auxiliares

Inclua verificações no CI/CD:

bash
npm audit

e mantenha as dependências atualizadas de maneira controlada.

Também monitore vulnerabilidades relevantes em dependências transitivas.


29. Faça testes de segurança automatizados

Antes do deploy, teste pelo menos:

text
401 → endpoint sem autenticação
403 → usuário sem permissão
404 → recurso inexistente
400 → payload inválido
429 → excesso de requisições
413 → payload grande demais

Teste também cenários como:

text
usuário A acessando recurso de B
usuário comum acessando endpoint administrativo
token expirado
token inválido
refresh token reutilizado
ID manipulado
campo extra no payload
upload inválido
URL externa maliciosa

O objetivo não é apenas testar se o “caminho feliz” funciona.

O objetivo é provar que os caminhos proibidos realmente estão bloqueados.


30. Revise a infraestrutura junto com o código

Uma API segura pode continuar vulnerável por causa da infraestrutura.

Antes de colocar NestJS em produção, revise:

text
HTTPS
Firewall
Reverse proxy
Banco de dados
Redis
Filas
Docker
Volumes
Secrets
Backups
SSH
Logs
Monitoring
Alertas

Nunca exponha diretamente na internet serviços como:

text
PostgreSQL
MySQL
Redis
RabbitMQ
MongoDB

sem necessidade.

A superfície pública idealmente deve ser pequena:

text
Internet
   ↓
Reverse Proxy / Load Balancer
   ↓
NestJS
   ↓
Serviços internos

Um main.ts como ponto de partida

Uma configuração inicial pode concentrar várias dessas proteções:

typescript
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';

import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.setGlobalPrefix('api');

  app.enableCors({
    origin: ['https://app.example.com'],
    credentials: true,
  });

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    }),
  );

  // NestJS atual:
  app.useSecurityHeaders();

  await app.listen(process.env.PORT ?? 3000);
}

bootstrap();

A configuração exata depende do adaptador HTTP, versão do NestJS, estratégia de autenticação e arquitetura da aplicação. Para versões que não possuem useSecurityHeaders(), Helmet continua sendo uma alternativa documentada.


Checklist rápido antes do deploy

Use esta lista como revisão final:

text
[ ] HTTPS obrigatório
[ ] Authentication implementada
[ ] Authorization implementada
[ ] Guards protegendo endpoints privados
[ ] DTOs definidos
[ ] ValidationPipe global
[ ] whitelist habilitado
[ ] Mass assignment revisado
[ ] CORS configurado explicitamente
[ ] CSRF avaliado
[ ] Rate limiting configurado
[ ] Brute force protegido
[ ] Senhas com bcrypt/Argon2
[ ] JWT com expiração adequada
[ ] Secrets fora do Git
[ ] Variáveis de ambiente validadas
[ ] Erros sem informações sensíveis
[ ] Logs sem tokens ou passwords
[ ] SQL/NoSQL injection revisado
[ ] Paginação com limite
[ ] Uploads restritos
[ ] Downloads autorizados
[ ] SSRF revisado
[ ] Webhooks com assinatura validada
[ ] Operações críticas idempotentes
[ ] Endpoints administrativos protegidos
[ ] Swagger/API docs protegidos
[ ] Endpoints antigos/internos removidos
[ ] Dependências auditadas
[ ] Testes de segurança automatizados
[ ] Infraestrutura revisada

Segurança não é um middleware

Uma das maiores armadilhas ao trabalhar com NestJS é acreditar que instalar Helmet, ativar CORS e configurar JWT significa que a aplicação está segura.

Essas ferramentas são importantes, mas representam apenas uma parte do problema.

A segurança de uma API acontece em várias camadas:

text
                 Internet
                    │
                    ▼
              HTTPS / Proxy
                    │
                    ▼
             Rate Limiting
                    │
                    ▼
             Authentication
                    │
                    ▼
             Authorization
                    │
                    ▼
              Validation
                    │
                    ▼
             Business Rules
                    │
                    ▼
              Database

E cada camada precisa assumir que a anterior pode ser insuficiente.

Uma requisição autenticada ainda pode não ser autorizada.

Uma requisição autorizada ainda pode conter dados inválidos.

Um dado válido ainda pode abusar de um recurso.

E uma aplicação corretamente protegida ainda pode expor secrets através de logs ou da infraestrutura.

Por isso, o melhor momento para aplicar esse checklist não é depois de um incidente.

É antes do primeiro deploy.


Referências

  • NestJS — Authentication
  • NestJS — Authorization
  • NestJS — Validation
  • NestJS — Rate Limiting
  • NestJS — Security Headers / Helmet
  • NestJS — CORS
  • NestJS — CSRF Protection
  • NestJS — Encryption and Hashing
  • NestJS — File Upload
  • OWASP API Security Top 10
  • OWASP Secrets Management Cheat Sheet
  • OWASP Logging Cheat Sheet