Segurança em Node.js: Fundamentos e Práticas Essenciais
A segurança em aplicações Node.js não é um apêndice — é arquitetura. Diferentemente de outras linguagens, Node.js frequentemente depende de centenas de pacotes terceirizados, ampliando a superfície de ataque. Nesta aula, você aprenderá a construir defesas em três camadas: gestão de dependências, identificação de vulnerabilidades e endurecimento da aplicação.
Entendendo a Importância
A maioria dos ataques em Node.js explora não o código que você escreve, mas pacotes que você importa. Um único pacote comprometido pode expor dados sensíveis, permitir execução de código remoto ou interromper toda a aplicação. O desenvolvimento seguro é sinônimo de desenvolvimento consciente das dependências.
Auditoria de Dependências
npm audit: Sua Primeira Linha de Defesa
O npm audit analisa seu package-lock.json e identifica vulnerabilidades conhecidas no banco de dados do National Vulnerability Database (NVD). Execute regularmente:
npm audit
npm audit fix
npm audit fix --force # Use com cuidado, pode quebrar compatibilidade
A saída mostra nível de severidade (critical, high, moderate, low) e caminho exato da vulnerabilidade. Critical e High devem ser tratadas imediatamente.
Exemplo Prático: Auditoria em Pipeline CI/CD
Integre a auditoria no seu fluxo de desenvolvimento:
// scripts/audit-check.js
const { execSync } = require('child_process');
try {
execSync('npm audit --audit-level=moderate', { stdio: 'inherit' });
console.log('✓ Auditoria passou');
process.exit(0);
} catch (error) {
console.error('✗ Vulnerabilidades encontradas');
process.exit(1);
}
Adicione no package.json:
{
"scripts": {
"audit": "node scripts/audit-check.js",
"precommit": "npm audit --audit-level=moderate"
}
}
Ferramentas Complementares
npm audit é limitado. Use Snyk para análise mais profunda, monitoramento contínuo e recomendações de remediação. Snyk identifica vulnerabilidades zero-day e oferece patches automáticos:
npm install -g snyk
snyk auth
snyk test
snyk monitor # Monitora continuamente
Para organizações, Dependabot (GitHub) ou Renovate (GitLab) automatizam atualizações de dependências com testes automáticos.
Hardening da Aplicação
Gestão Segura de Segredos
Nunca commite credenciais. Use variáveis de ambiente com validação:
// config/environment.js
const dotenv = require('dotenv');
dotenv.config();
const requiredEnvVars = ['DATABASE_URL', 'JWT_SECRET', 'API_KEY'];
const missing = requiredEnvVars.filter(env => !process.env[env]);
if (missing.length > 0) {
throw new Error(`Variáveis de ambiente obrigatórias ausentes: ${missing.join(', ')}`);
}
module.exports = {
databaseUrl: process.env.DATABASE_URL,
jwtSecret: process.env.JWT_SECRET,
apiKey: process.env.API_KEY,
nodeEnv: process.env.NODE_ENV || 'development',
};
Use .env.example para documentar qual variáveis são necessárias, mas nunca commita .env.
Proteção contra Vulnerabilidades Comuns
Helmet.js mitiga headers HTTP inseguros em uma linha:
// server.js
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const app = express();
// Aplica headers de segurança
app.use(helmet());
// Rate limiting contra brute force
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100, // máximo 100 requisições por IP
message: 'Muitas requisições deste IP, tente novamente mais tarde.',
});
app.use('/api/', limiter);
// Validação de entrada
const express_validator = require('express-validator');
app.post('/user', [
express_validator.body('email').isEmail(),
express_validator.body('password').isLength({ min: 8 }),
], (req, res) => {
const errors = express_validator.validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// lógica segura continua
});
app.listen(3000, () => console.log('Servidor seguro iniciado'));
Tratamento Seguro de Erros
Nunca exponha detalhes de erro ao cliente. Implemente middleware centralizado:
// middleware/errorHandler.js
const errorHandler = (err, req, res, next) => {
const isDev = process.env.NODE_ENV === 'development';
console.error(err); // Log sempre no servidor
// Resposta ao cliente
const response = {
status: err.status || 500,
message: isDev ? err.message : 'Erro interno do servidor',
};
if (isDev) {
response.stack = err.stack;
}
res.status(response.status).json(response);
};
module.exports = errorHandler;
Princípio do Menor Privilégio
Execue Node.js com usuário sem privilégios em produção. No Docker:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Criar usuário sem privilégios
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
CMD ["node", "server.js"]
Boas Práticas em Produção
Monitoramento Contínuo
Configure alertas para vulnerabilidades detectadas em tempo real. Use ferramentas como Wiz, Lacework ou Snyk, que monitoram seu ambiente em produção e notificam sobre novas CVEs que afetam suas dependências específicas.
Atualizações Regulares
Mantenha dependências atualizadas. A maioria das vulnerabilidades não é zero-day, mas pacotes desatualizados. Configure Renovate ou Dependabot para criar pull requests automaticamente:
# renovate.json
{
"extends": ["config:base"],
"automerge": true,
"major": {
"automerge": false
},
"vulnerabilityAlerts": {
"labels": ["security"],
"automerge": true
}
}
Testes de Segurança Automatizados
Integre SAST (Static Application Security Testing) no seu pipeline:
# package.json
{
"scripts": {
"security": "npm audit && snyk test && eslint . --ext .js"
},
"devDependencies": {
"eslint-plugin-security": "^1.7.1"
}
}
Conclusão
Segurança em Node.js repousa em três pilares: auditoria constante de dependências com npm audit e ferramentas especializadas como Snyk; hardening arquitetural usando Helmet, rate limiting e validação de entrada; e disciplina operacional com variáveis de ambiente, tratamento seguro de erros e princípio do menor privilégio. A segurança não é um projeto, é um processo contínuo que deve ser integrado em todo o ciclo de desenvolvimento.