package web import ( "crypto/sha256" "encoding/base64" "strings" "testing" ) func hashOf(body string) string { sum := sha256.Sum256([]byte(body)) return "'sha256-" + base64.StdEncoding.EncodeToString(sum[:]) + "'" } func TestContentSecurityPolicy_HashesInlineScriptsOnly(t *testing.T) { theme := "(function () { document.documentElement.dataset.theme = 'dark'; })();" brand := `window.__MINSTREL__ = { appName: "Minstrel" };` html := "
" + `` + "" csp := contentSecurityPolicy([]byte(html)) var scriptSrc string for _, d := range strings.Split(csp, "; ") { if strings.HasPrefix(d, "script-src ") { scriptSrc = d } } if scriptSrc == "" { t.Fatalf("no script-src in %q", csp) } for _, want := range []string{"'self'", hashOf(theme), hashOf(brand)} { if !strings.Contains(scriptSrc, want) { t.Errorf("script-src %q missing %s", scriptSrc, want) } } if strings.Count(scriptSrc, "'sha256-") != 2 { t.Errorf("script-src %q: want exactly the two inline scripts hashed, not the src= one", scriptSrc) } if strings.Contains(scriptSrc, "unsafe-inline") || strings.Contains(scriptSrc, "unsafe-eval") { t.Errorf("script-src must not fall back to unsafe-*: %q", scriptSrc) } for _, want := range []string{"frame-ancestors 'none'", "object-src 'none'", "connect-src 'self'"} { if !strings.Contains(csp, want) { t.Errorf("csp missing %q", want) } } } // The hash must be of the page as served, after the branding template has // substituted the operator's app name, or a renamed instance would refuse // its own bootstrap script. func TestContentSecurityPolicy_TracksTemplatedContent(t *testing.T) { a := contentSecurityPolicy([]byte(``)) b := contentSecurityPolicy([]byte(``)) if a == b { t.Error("different script bodies produced the same policy") } }