Sécurité HTTP avec Helmet.js

Guide d'utilisation de Helmet.js pour sécuriser les applications Express.js avec les en-têtes HTTP de sécurité.

Configuration de base

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
}));

Content Security Policy

// 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 avancée

// 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
}));

En-têtes de sécurité :

  • X-Frame-Options : Protection contre le clickjacking
  • X-XSS-Protection : Protection contre les XSS
  • X-Content-Type-Options : Prévention du MIME sniffing
  • Strict-Transport-Security : Force HTTPS
  • Referrer-Policy : Contrôle des informations de référence

Gestion des erreurs

// 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'
    })
  ]
});

Tests de sécurité

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);
  });
});