Some checks failed
Veza CI / Backend (Go) (push) Waiting to run
Veza CI / Frontend (Web) (push) Waiting to run
Veza CI / Notify on failure (push) Blocked by required conditions
Security Scan / Secret Scanning (gitleaks) (push) Failing after 3m4s
Veza CI / Rust (Stream Server) (push) Has been cancelled
Backend API CI / test-integration (push) Failing after 11m59s
Backend API CI / test-unit (push) Failing after 12m1s
backend-ci.yml's `test -z "$(gofmt -l .)"` strict gate (added in
c96edd692) failed on a backlog of unformatted files. None of the
85 files in this commit had been edited since the gate was added
because no push touched veza-backend-api/** in between, so the
gate never fired until today's CI fixes triggered it.
The diff is exclusively whitespace alignment in struct literals
and trailing-space comments. `go build ./...` and the full test
suite (with VEZA_SKIP_INTEGRATION=1 -short) pass identically.
120 lines
5.9 KiB
Go
120 lines
5.9 KiB
Go
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"
|
|
}
|
|
// SECURITY(MEDIUM-006): Removed 'unsafe-eval' from script-src. Swagger UI works without it.
|
|
// Keep 'unsafe-inline' only for style-src (required by Swagger UI's inline styles).
|
|
csp := "default-src 'self'; script-src 'self' 'unsafe-inline'; 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"
|
|
}
|