const express = require('express');
const helmet = require('helmet');
const app = express();
// Configuration simple
app.use(helmet());
// Configuration détaillée
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"]
}
},
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: true,
crossOriginResourcePolicy: { policy: "cross-origin" },
dnsPrefetchControl: true,
expectCt: {
maxAge: 30,
enforce: true
},
frameguard: {
action: "deny"
},
hidePoweredBy: true,
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
ieNoOpen: true,
noSniff: true,
originAgentCluster: true,
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
xssFilter: true
}));
// Configuration CSP détaillée
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
// Scripts
scriptSrc: [
"'self'",
"'unsafe-inline'",
'https://cdn.jsdelivr.net',
'https://ajax.googleapis.com'
],
// Styles
styleSrc: [
"'self'",
"'unsafe-inline'",
'https://fonts.googleapis.com'
],
// Images
imgSrc: [
"'self'",
'data:',
'https:',
'blob:'
],
// Fonts
fontSrc: [
"'self'",
'https://fonts.gstatic.com'
],
// API et WebSocket
connectSrc: [
"'self'",
'https://api.example.com',
'wss://websocket.example.com'
],
// Frames
frameSrc: ["'none'"],
// Media
mediaSrc: ["'self'"],
// Object/Embed
objectSrc: ["'none'"],
// Manifests
manifestSrc: ["'self'"],
// Forms
formAction: ["'self'"],
// Base URI
baseUri: ["'self'"],
// Reporting
reportUri: '/csp-report'
}
}));
// Protection contre les attaques
const rateLimit = require('express-rate-limit');
const slowDown = require('express-slow-down');
const hpp = require('hpp');
const mongoSanitize = require('express-mongo-sanitize');
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limite par IP
});
// Ralentissement progressif
const speedLimiter = slowDown({
windowMs: 15 * 60 * 1000, // 15 minutes
delayAfter: 100, // commence après 100 requêtes
delayMs: 500 // ajoute 500ms par requête
});
// Protection des paramètres HTTP
app.use(hpp());
// Protection contre l'injection NoSQL
app.use(mongoSanitize());
// Sécurisation des cookies
app.use(session({
name: 'sessionId',
secret: process.env.SESSION_SECRET,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 1000 * 60 * 60 * 24 // 24 heures
},
resave: false,
saveUninitialized: false
}));
// Middleware de gestion des erreurs
app.use((err, req, res, next) => {
// Ne pas exposer les détails d'erreur en prod
if (process.env.NODE_ENV === 'production') {
console.error(err.stack);
res.status(500).json({
error: 'Internal Server Error'
});
} else {
res.status(500).json({
error: err.message,
stack: err.stack
});
}
});
// Gestion des routes non trouvées
app.use((req, res) => {
res.status(404).json({
error: 'Not Found'
});
});
// Logging sécurisé
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
})
]
});
const request = require('supertest');
const app = require('../app');
describe('Security Headers', () => {
test('should set security headers', async () => {
const response = await request(app)
.get('/');
// CSP Header
expect(response.headers['content-security-policy'])
.toBeDefined();
// HSTS
expect(response.headers['strict-transport-security'])
.toContain('max-age=31536000');
// X-Frame-Options
expect(response.headers['x-frame-options'])
.toBe('DENY');
// XSS Protection
expect(response.headers['x-xss-protection'])
.toBe('1; mode=block');
// No Sniff
expect(response.headers['x-content-type-options'])
.toBe('nosniff');
});
});
// Test de rate limiting
describe('Rate Limiting', () => {
test('should limit requests', async () => {
// Faire 101 requêtes
for (let i = 0; i < 100; i++) {
await request(app).get('/');
}
const response = await request(app).get('/');
expect(response.status).toBe(429);
});
});
// Test de validation des entrées
describe('Input Validation', () => {
test('should sanitize query parameters', async () => {
const response = await request(app)
.get('/?q=$gt:100');
expect(response.status).toBe(400);
});
});