package web import ( "bytes" "crypto/sha256" "encoding/base64" "io/fs" "net/http/httptest" "regexp" "strings" "testing" ) func TestAPageNamesItsInlineScriptsAndNothingElse(t *testing.T) { page := []byte(`` + ``) sum := sha256.Sum256([]byte("alert(1)")) csp := contentSecurityPolicy(page) want := "script-src 'self' 'sha256-" + base64.StdEncoding.EncodeToString(sum[:]) + "';" if !strings.Contains(csp, want) { t.Errorf("policy %q lacks %q", csp, want) } scripts := strings.Split(strings.Split(csp, "script-src")[1], ";")[0] if strings.Contains(scripts, "unsafe") || strings.Count(scripts, "sha256-") != 1 { t.Errorf("scripts are this server's and the one inline script, nothing unsafe: %q", scripts) } } func TestRenderedPagesCarryThePolicy(t *testing.T) { rec := httptest.NewRecorder() renderStatus(rec, nil, 401, "login", loginData{Error: "no"}) if rec.Code != 401 { t.Errorf("status %d", rec.Code) } csp := rec.Header().Get("Content-Security-Policy") if !strings.Contains(csp, "frame-ancestors 'none'") { t.Errorf("policy %q", csp) } // Every inline script on the page is in it. for _, m := range inlineScript.FindAllSubmatch(rec.Body.Bytes(), -1) { if bytes.Contains(m[1], []byte("src=")) { continue } sum := sha256.Sum256(m[2]) if !strings.Contains(csp, base64.StdEncoding.EncodeToString(sum[:])) { t.Errorf("an inline script is not in the policy: %.60s", m[2]) } } } func TestNoTemplateUsesWhatThePolicyRefuses(t *testing.T) { handler := regexp.MustCompile(`\son[a-z]+\s*=\s*["']`) err := fs.WalkDir(files, "templates", func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return err } b, _ := fs.ReadFile(files, path) if loc := handler.FindIndex(b); loc != nil { t.Errorf("%s has an inline event handler: %.60s", path, b[loc[0]:]) } if bytes.Contains(b, []byte("javascript:")) { t.Errorf("%s has a javascript: URL", path) } if bytes.Contains(b, []byte(`hx-trigger="`)) && regexp.MustCompile(`hx-trigger="[^"]*\[`).Match(b) { t.Errorf("%s has an htmx trigger filter, which is evaluated JavaScript", path) } return nil }) if err != nil { t.Fatal(err) } }