This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

168 lines
4.9 KiB
Go

package middleware
import (
"os"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
// SecurityHeadersConfig holds configuration for security headers.
type SecurityHeadersConfig struct {
// X-Content-Type-Options
ContentTypeOptions string
// X-Frame-Options
FrameOptions string
// X-XSS-Protection (legacy but useful for older browsers)
XSSProtection string
// Strict-Transport-Security
HSTSEnabled bool
HSTSMaxAge int
HSTSIncludeSubdomains bool
HSTSPreload bool
// Content-Security-Policy
CSPEnabled bool
CSPPolicy string
// Referrer-Policy
ReferrerPolicy string
// Permissions-Policy
PermissionsPolicy string
// Cross-Origin headers
CrossOriginOpenerPolicy string
CrossOriginResourcePolicy string
// Development mode (relaxes some restrictions)
DevelopmentMode bool
// Excluded paths (e.g., health checks)
ExcludedPaths []string
}
// DefaultSecurityHeadersConfig returns sensible default configuration.
func DefaultSecurityHeadersConfig() SecurityHeadersConfig {
env := os.Getenv("ENVIRONMENT")
isDev := env == "" || strings.ToLower(env) == "development" || strings.ToLower(env) == "dev"
return SecurityHeadersConfig{
ContentTypeOptions: "nosniff",
FrameOptions: "DENY",
XSSProtection: "1; mode=block",
HSTSEnabled: true,
HSTSMaxAge: 31536000, // 1 year
HSTSIncludeSubdomains: true,
HSTSPreload: false,
CSPEnabled: true,
CSPPolicy: getDefaultCSP(isDev),
ReferrerPolicy: "strict-origin-when-cross-origin",
PermissionsPolicy: "geolocation=(), microphone=(), camera=()",
CrossOriginOpenerPolicy: "same-origin",
CrossOriginResourcePolicy: "same-origin",
DevelopmentMode: isDev,
ExcludedPaths: []string{"/health", "/metrics", "/api/v1/health"},
}
}
// getDefaultCSP returns a sensible default CSP for the environment.
func getDefaultCSP(isDevelopment bool) string {
if isDevelopment {
return "default-src 'self' localhost:* ws://localhost:*; " +
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https: blob:; " +
"font-src 'self' data:; " +
"connect-src 'self' localhost:* ws://localhost:* https:; " +
"frame-ancestors 'self'"
}
return "default-src 'self'; " +
"script-src 'self' 'unsafe-inline'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:; " +
"font-src 'self' data:; " +
"connect-src 'self' https://breakpilot.app https://*.breakpilot.app; " +
"frame-ancestors 'none'"
}
// buildHSTSHeader builds the Strict-Transport-Security header value.
func (c *SecurityHeadersConfig) buildHSTSHeader() string {
parts := []string{"max-age=" + strconv.Itoa(c.HSTSMaxAge)}
if c.HSTSIncludeSubdomains {
parts = append(parts, "includeSubDomains")
}
if c.HSTSPreload {
parts = append(parts, "preload")
}
return strings.Join(parts, "; ")
}
// isExcludedPath checks if the path should be excluded from security headers.
func (c *SecurityHeadersConfig) isExcludedPath(path string) bool {
for _, excluded := range c.ExcludedPaths {
if path == excluded {
return true
}
}
return false
}
// SecurityHeaders returns a middleware that adds security headers to all responses.
//
// Usage:
//
// r.Use(middleware.SecurityHeaders())
//
// // Or with custom config:
// config := middleware.DefaultSecurityHeadersConfig()
// config.CSPPolicy = "default-src 'self'"
// r.Use(middleware.SecurityHeadersWithConfig(config))
func SecurityHeaders() gin.HandlerFunc {
return SecurityHeadersWithConfig(DefaultSecurityHeadersConfig())
}
// SecurityHeadersWithConfig returns a security headers middleware with custom configuration.
func SecurityHeadersWithConfig(config SecurityHeadersConfig) gin.HandlerFunc {
return func(c *gin.Context) {
// Skip for excluded paths
if config.isExcludedPath(c.Request.URL.Path) {
c.Next()
return
}
// Always add these headers
c.Header("X-Content-Type-Options", config.ContentTypeOptions)
c.Header("X-Frame-Options", config.FrameOptions)
c.Header("X-XSS-Protection", config.XSSProtection)
c.Header("Referrer-Policy", config.ReferrerPolicy)
// HSTS (only in production or if explicitly enabled)
if config.HSTSEnabled && !config.DevelopmentMode {
c.Header("Strict-Transport-Security", config.buildHSTSHeader())
}
// Content-Security-Policy
if config.CSPEnabled && config.CSPPolicy != "" {
c.Header("Content-Security-Policy", config.CSPPolicy)
}
// Permissions-Policy
if config.PermissionsPolicy != "" {
c.Header("Permissions-Policy", config.PermissionsPolicy)
}
// Cross-Origin headers (only in production)
if !config.DevelopmentMode {
c.Header("Cross-Origin-Opener-Policy", config.CrossOriginOpenerPolicy)
c.Header("Cross-Origin-Resource-Policy", config.CrossOriginResourcePolicy)
}
c.Next()
}
}