package route import ( "net/url" "strings" "github.com/tamakyi/TamaBox/internal/conf" ) // cspMode 归一化 [security] csp 的取值。默认 enforce。 // // 为什么默认 enforce 而不是 report:本项没有配置 report-uri 时,report-only 只是 // 「发个头、什么都不拦」,等于没装 —— 装个不生效的开关反而给人「已防护」的错觉。 // 已用真浏览器巡检 20 个页面(匿名 11 + 登录 6 + 单题/投票/分享图)零违规,见 // outputs/sec-audit-2026-10-25/csp_sweep.py。 // // 两个方向的安全性都保留了:写错值(如 "enforce " 以外的拼写错误)退到 report, // 宁可少拦也不让整站样式与交互崩掉;真要临时关掉写 off。 func cspMode() string { switch strings.ToLower(strings.TrimSpace(conf.Security.CSP)) { case "off", "none", "false", "0": return "off" case "report", "report-only", "reportonly": return "report" case "enforce", "on", "true", "1", "": return "enforce" default: return "report" } } // cspOrigin 把配置里的「域名」或「完整 URL」统一成 scheme://host 形态。 // 配置项经常是两种写法混用(如 image_bucket_cdn_host 常写成 host/path), // 直接拼进 CSP 会变成非法源而被浏览器整条忽略。 func cspOrigin(raw string) string { raw = strings.TrimSpace(raw) if raw == "" { return "" } if !strings.Contains(raw, "://") { raw = "https://" + raw } u, err := url.Parse(raw) if err != nil || u.Host == "" { return "" } return u.Scheme + "://" + u.Host } // cspPolicy 构造 Content-Security-Policy 头。 // // 目标是补一道纵深防御,而不是把页面卡死:XSS 过滤器是一套手写白名单, // 任何一次改动都可能开口子;CSP 让「即使注入了