/** * Content Security Policy (CSP) utilities * Gère les nonces et la configuration CSP pour la sécurité */ // Nonce généré côté serveur pour les scripts inline let cspNonce: string | null = null /** * Définit le nonce CSP pour la session courante */ export function setCSPNonce(nonce: string): void { cspNonce = nonce } /** * Récupère le nonce CSP actuel */ export function getCSPNonce(): string | null { return cspNonce } /** * Génère un nonce CSP sécurisé */ export function generateCSPNonce(): string { const array = new Uint8Array(16) crypto.getRandomValues(array) return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('') } /** * Configuration CSP pour la production */ export const CSP_POLICY = { 'default-src': ["'self'"], 'script-src': [ "'self'", "'nonce-__CSP_NONCE__'", // Nonce pour scripts inline 'https://cdn.jsdelivr.net', // Pour les CDN si nécessaire ], 'style-src': [ "'self'", "'unsafe-inline'", // Nécessaire pour Tailwind CSS 'https://fonts.googleapis.com', ], 'img-src': [ "'self'", 'data:', 'https:', 'blob:', ], 'connect-src': [ "'self'", 'ws:', 'wss:', 'http:', 'https:', ], 'font-src': [ "'self'", 'data:', 'https://fonts.gstatic.com', ], 'object-src': ["'none'"], 'base-uri': ["'self'"], 'form-action': ["'self'"], 'frame-ancestors': ["'none'"], 'upgrade-insecure-requests': [], } as const /** * Construit la chaîne CSP à partir de la configuration */ export function buildCSPHeader(nonce?: string): string { const policy = { ...CSP_POLICY } if (nonce) { policy['script-src'] = policy['script-src'].map(src => src === "'nonce-__CSP_NONCE__'" ? `'nonce-${nonce}'` : src ) } return Object.entries(policy) .map(([directive, sources]) => { if (sources.length === 0) { return directive } return `${directive} ${sources.join(' ')}` }) .join('; ') } /** * Valide qu'un script peut être exécuté selon la CSP */ export function validateScriptExecution(scriptContent: string): boolean { // Vérifications de base pour les scripts inline const dangerousPatterns = [ /eval\s*\(/, /Function\s*\(/, /setTimeout\s*\(\s*["']/, /setInterval\s*\(\s*["']/, /document\.write/, /innerHTML\s*=/, /outerHTML\s*=/, ] return !dangerousPatterns.some(pattern => pattern.test(scriptContent)) } /** * Sanitise le contenu HTML pour éviter les violations CSP */ export function sanitizeForCSP(content: string): string { return content .replace(/javascript:/gi, '') .replace(/on\w+\s*=/gi, '') // Supprimer les event handlers inline .replace(/]*>[\s\S]*?<\/script>/gi, '') // Supprimer les scripts .replace(/]*>[\s\S]*?<\/iframe>/gi, '') // Supprimer les iframes } /** * Configuration CSP pour le développement (plus permissive) */ export const CSP_POLICY_DEV = { 'default-src': ["'self'"], 'script-src': [ "'self'", "'unsafe-inline'", // Nécessaire pour Vite HMR "'unsafe-eval'", // Nécessaire pour Vite en dev ], 'style-src': [ "'self'", "'unsafe-inline'", ], 'img-src': [ "'self'", 'data:', 'https:', 'blob:', ], 'connect-src': [ "'self'", 'ws:', 'wss:', 'http:', 'https:', ], 'font-src': [ "'self'", 'data:', 'https:', ], 'object-src': ["'none'"], 'base-uri': ["'self'"], 'form-action': ["'self'"], 'frame-ancestors': ["'none'"], } as const /** * Construit la CSP pour le développement */ export function buildCSPHeaderDev(): string { return Object.entries(CSP_POLICY_DEV) .map(([directive, sources]) => { if (sources.length === 0) { return directive } return `${directive} ${sources.join(' ')}` }) .join('; ') } /** * Hook pour utiliser le nonce CSP dans les composants React */ export function useCSPNonce(): string | null { return getCSPNonce() } /** * Middleware pour injecter le nonce CSP dans les réponses */ export function createCSPMiddleware() { return (req: any, res: any, next: any) => { const nonce = generateCSPNonce() setCSPNonce(nonce) const cspHeader = process.env.NODE_ENV === 'production' ? buildCSPHeader(nonce) : buildCSPHeaderDev() res.setHeader('Content-Security-Policy', cspHeader) res.setHeader('X-Content-Type-Options', 'nosniff') res.setHeader('X-Frame-Options', 'DENY') res.setHeader('X-XSS-Protection', '1; mode=block') res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin') next() } }