package middleware import ( "os" "strings" "github.com/gin-gonic/gin" ) // SecurityHeaders ajoute des headers de sécurité HTTP recommandés // BE-SEC-011: Enhanced security headers implementation // MOD-P2-005: Headers sécurité manquants (HSTS, CSP, X-Frame-Options, etc.) func SecurityHeaders() gin.HandlerFunc { return func(c *gin.Context) { // INT-DOC-001: Check if this is a Swagger documentation route // Allow embedding for Swagger UI so it can be displayed in iframe isSwaggerRoute := strings.HasPrefix(c.Request.URL.Path, "/swagger/") || strings.HasPrefix(c.Request.URL.Path, "/docs/") || c.Request.URL.Path == "/swagger" || c.Request.URL.Path == "/docs" // BE-SEC-011: Strict-Transport-Security (HSTS) - Force HTTPS // Only set HSTS in production (not in development) // Max-Age: 31536000 = 1 an // IncludeSubDomains: inclut tous les sous-domaines // Preload: permet d'être inclus dans les listes de préchargement HSTS des navigateurs if isProduction() { c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") } // BE-SEC-011: X-Content-Type-Options - Empêche le MIME type sniffing // Force le navigateur à respecter le Content-Type déclaré c.Header("X-Content-Type-Options", "nosniff") // BE-SEC-011: X-Frame-Options - Empêche le clickjacking // DENY: empêche le site d'être affiché dans une frame (iframe, embed, object) // INT-DOC-001: Allow SAMEORIGIN for Swagger routes to enable iframe embedding // Note: X-Frame-Options SAMEORIGIN only allows same-origin, but CSP frame-ancestors // handles cross-origin embedding in development if isSwaggerRoute { // In development, we rely on CSP frame-ancestors for cross-origin embedding // X-Frame-Options SAMEORIGIN is still set for same-origin compatibility c.Header("X-Frame-Options", "SAMEORIGIN") } else { c.Header("X-Frame-Options", "DENY") } // BE-SEC-011: X-XSS-Protection - Protection XSS (déprécié mais encore supporté par certains navigateurs) // mode=block: bloque la page si une attaque XSS est détectée c.Header("X-XSS-Protection", "1; mode=block") // BE-SEC-011: Referrer-Policy - Contrôle les informations de referrer envoyées // strict-origin-when-cross-origin: envoie l'origine complète pour les requêtes same-origin, // seulement l'origine pour les requêtes cross-origin HTTPS->HTTPS, rien pour HTTPS->HTTP c.Header("Referrer-Policy", "strict-origin-when-cross-origin") // BE-SEC-011: Permissions-Policy (formerly Feature-Policy) - Contrôle les fonctionnalités du navigateur // Désactive les fonctionnalités non nécessaires pour une API REST c.Header("Permissions-Policy", "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()") // BE-SEC-011: Content-Security-Policy (CSP) - Contrôle les ressources chargées // Pour une API REST, on peut être strict car on ne sert pas de HTML // INT-DOC-001: For Swagger routes, allow embedding and necessary resources if isSwaggerRoute { // Swagger UI needs scripts, styles, images, and to be embeddable // In development, allow localhost origins for iframe embedding (frontend on different port) var frameAncestors string // Check if we're in production mode - if not, allow localhost origins for development env := strings.ToLower(strings.TrimSpace(os.Getenv("APP_ENV"))) if env == "production" || env == "prod" { // Production: only allow same-origin embedding frameAncestors = "'self'" } else { // Development/Staging/Test: allow common localhost origins to match CORS configuration // These match the default CORS origins: localhost:3000, localhost:5173, etc. frameAncestors = "'self' http://localhost:3000 http://127.0.0.1:3000 http://localhost:5173 http://127.0.0.1:5173" } csp := "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' http: https:; font-src 'self' data: https:; frame-ancestors " + frameAncestors c.Header("Content-Security-Policy", csp) } else { // default-src 'none': bloque tout par défaut // script-src 'none': bloque les scripts // style-src 'none': bloque les styles // img-src 'none': bloque les images // connect-src 'self': permet les requêtes vers la même origine (pour les appels API) // frame-ancestors 'none': empêche l'embedding (complément de X-Frame-Options) c.Header("Content-Security-Policy", "default-src 'none'; script-src 'none'; style-src 'none'; img-src 'none'; connect-src 'self'; frame-ancestors 'none'") } // BE-SEC-011: X-Permitted-Cross-Domain-Policies - Contrôle les politiques cross-domain pour Flash/PDF // none: empêche les politiques cross-domain c.Header("X-Permitted-Cross-Domain-Policies", "none") // BE-SEC-011: Cross-Origin-Embedder-Policy - Empêche l'embedding cross-origin // INT-DOC-001: Relax for Swagger routes to allow embedding if isSwaggerRoute { // Allow embedding for Swagger UI c.Header("Cross-Origin-Embedder-Policy", "unsafe-none") c.Header("Cross-Origin-Opener-Policy", "unsafe-none") c.Header("Cross-Origin-Resource-Policy", "cross-origin") } else { // require-corp: nécessite que les ressources soient marquées comme cross-origin c.Header("Cross-Origin-Embedder-Policy", "require-corp") // same-origin: isole les contextes de navigation à la même origine c.Header("Cross-Origin-Opener-Policy", "same-origin") // same-origin: seules les requêtes de la même origine peuvent charger les ressources c.Header("Cross-Origin-Resource-Policy", "same-origin") } c.Next() } } // isProduction vérifie si l'application est en mode production func isProduction() bool { env := strings.ToLower(os.Getenv("APP_ENV")) return env == "production" || env == "prod" }