Rewrites the web app's comments to rule 32. Task, milestone, rule and spec ids are gone; regression stories are restated as the trap they guard (public auth routes, the menu's stopPropagation, universal loads under adapter-static, metric deltas with their margin). Flutter and Tauri mentions now name the Android app or a native shell, operator quotes and dates are dropped, and one library's counts no longer appear outside test fixtures. Verified with a TypeScript-scanner token comparison (template literals and regex literals re-scanned): only comment text changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.0 KiB
Go
61 lines
2.0 KiB
Go
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 := "<html><head><script>" + theme + "</script>" +
|
|
`<script type="module" src="/_app/start.js"></script>` +
|
|
"<script>" + brand + "</script></head></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 app's name, or a renamed instance would refuse
|
|
// its own bootstrap script.
|
|
func TestContentSecurityPolicy_TracksTemplatedContent(t *testing.T) {
|
|
a := contentSecurityPolicy([]byte(`<script>window.__MINSTREL__ = { appName: "A" };</script>`))
|
|
b := contentSecurityPolicy([]byte(`<script>window.__MINSTREL__ = { appName: "B" };</script>`))
|
|
if a == b {
|
|
t.Error("different script bodies produced the same policy")
|
|
}
|
|
}
|