package route import ( "strings" "testing" "github.com/stretchr/testify/require" "github.com/tamakyi/TamaBox/internal/conf" ) // withSecurityConf 临时改 conf.Security 并在测试结束后还原,避免污染同包其它用例。 func withSecurityConf(t *testing.T, csp, reportURI string) { t.Helper() oldCSP, oldURI := conf.Security.CSP, conf.Security.CSPReportURI conf.Security.CSP = csp conf.Security.CSPReportURI = reportURI t.Cleanup(func() { conf.Security.CSP = oldCSP conf.Security.CSPReportURI = oldURI }) } func TestCSPMode(t *testing.T) { cases := []struct { in string want string }{ {"", "enforce"}, // 未配置:默认拦截(report-only 没有上报地址等于没装) {"report", "report"}, {"REPORT", "report"}, {"report-only", "report"}, {"off", "off"}, {"none", "off"}, {"enforce", "enforce"}, {" true ", "enforce"}, {"enfroce", "report"}, // 拼错退化为不拦截 } for _, tc := range cases { withSecurityConf(t, tc.in, "") require.Equal(t, tc.want, cspMode(), "输入 %q", tc.in) } } func TestCSPPolicy(t *testing.T) { old := conf.Upload t.Cleanup(func() { conf.Upload = old }) // 生产配置的真实写法:Host 带路径、Domain 是完整 URL。 conf.Upload.ImageBucketCDNHost = "r2.example.net/tamabox" conf.Upload.AliyunBucketCDNHost = "" conf.Recaptcha.Domain = "https://www.recaptcha.net" withSecurityConf(t, "enforce", "") policy := cspPolicy() for _, want := range []string{ "default-src 'self'", "object-src 'none'", "base-uri 'none'", "form-action 'self'", "frame-ancestors 'none'", // 脚本白名单:站内 + 内联 + eval(Alpine 需要)+ 公共 CDN + 验证码 "script-src 'self' 'unsafe-inline' 'unsafe-eval'", "https://cdnjs.snrat.com", // umami 统计脚本是 header.html 里运行时动态注入的,静态 grep 模板看不到; // 少这一条会让 enforce 模式直接拦掉它(已在浏览器实测中复现过)。 "https://mami.shiroko.one", "https://challenges.cloudflare.com", "https://www.recaptcha.net", // 带路径的配置必须被归一化成 origin,否则整条源非法、浏览器静默忽略 "https://r2.example.net", } { require.Contains(t, policy, want) } require.NotContains(t, policy, "r2.example.net/tamabox", "带路径的配置必须裁成 origin") require.NotContains(t, policy, "report-uri", "没配上报地址时不应出现 report-uri") } // TestCSPPolicySentry 覆盖「配了 sentry_dsn 才放行 Sentry CDN」这个条件白名单。 // 两个方向都会静默出错:线上没开 Sentry 时白名单里多挂一个第三方源;开了 Sentry // 时错误上报被 enforce 直接拦掉,而页面上完全看不出异常。 func TestCSPPolicySentry(t *testing.T) { old := conf.App t.Cleanup(func() { conf.App = old }) conf.App.SentryDSN = "" require.NotContains(t, cspPolicy(), "browser.sentry-cdn.com") conf.App.SentryDSN = "https://key@sentry.example.net/1" require.Contains(t, cspPolicy(), "https://browser.sentry-cdn.com") } func TestCSPPolicyReportURI(t *testing.T) { withSecurityConf(t, "report", "https://csp.example.net/collect") require.Contains(t, cspPolicy(), "report-uri https://csp.example.net/collect") } func TestIsNoStorePath(t *testing.T) { for _, p := range []string{"/user", "/user/", "/user/profile", "/_", "/_/someone", "/_/someone/1"} { require.True(t, isNoStorePath(p), p) } // 分享卡是给社交平台抓取的公开图片,且渲染成本高,刻意不套 no-store。 for _, p := range []string{"/", "/login", "/static/css/base.css", "/_foo", "/users", "/explore", "/_/someone/share-card", "/_/someone/1/share-card"} { require.False(t, isNoStorePath(p), p) } } // TestCSPPolicyNoEmptyOrigin 防止配置留空时拼出非法源(如 "https://")。 func TestCSPPolicyNoEmptyOrigin(t *testing.T) { old := conf.Upload t.Cleanup(func() { conf.Upload = old }) conf.Upload.ImageBucketCDNHost = "" conf.Upload.AliyunBucketCDNHost = "" conf.Recaptcha.Domain = "" policy := cspPolicy() require.False(t, strings.Contains(policy, "https:// ") || strings.HasSuffix(policy, "https://"), policy) }