Files
minstrel/web/csp.go
T
bvandeusenandClaude Opus 5.5 10d50dccbd docs: web comments say what the app does, not how it came to be (#6277)
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>
2026-10-23 12:42:12 -04:00

58 lines
2.0 KiB
Go

package web
import (
"crypto/sha256"
"encoding/base64"
"regexp"
"strings"
)
// inlineScriptRe matches each <script> element and captures its attributes
// and body. index.html is our own build output, so a regexp is enough: there
// is no hostile markup to out-parse.
var inlineScriptRe = regexp.MustCompile(`(?is)<script\b([^>]*)>(.*?)</script>`)
var srcAttrRe = regexp.MustCompile(`(?i)\bsrc\s*=`)
// contentSecurityPolicy builds the policy served with index.html.
//
// Scripts are allowed from our own origin plus the exact inline scripts the
// page carries: the theme bootstrap in app.html, the branding global the Vite
// plugin injects, and SvelteKit's start-up block. Their hashes are taken from
// the page AFTER the branding template has run, so they match the bytes the
// browser actually receives, whatever the app is named. Any script
// that differs, including one injected through an XSS, is refused.
//
// The rest:
// - style-src allows inline styles: Svelte transitions and style:
// directives write them, and inline style is not a script vector.
// - img-src admits https:/http: because catalogue search art is a remote
// URL. Images cannot run code.
// - media-src/connect-src stay on our own origin: streams, the API and the
// SSE stream are all same-origin. blob: covers Web Audio and object URLs.
func contentSecurityPolicy(indexHTML []byte) string {
scriptSrc := []string{"'self'"}
for _, m := range inlineScriptRe.FindAllSubmatch(indexHTML, -1) {
if srcAttrRe.Match(m[1]) {
continue
}
sum := sha256.Sum256(m[2])
scriptSrc = append(scriptSrc, "'sha256-"+base64.StdEncoding.EncodeToString(sum[:])+"'")
}
return strings.Join([]string{
"default-src 'self'",
"base-uri 'self'",
"object-src 'none'",
"frame-ancestors 'none'",
"form-action 'self'",
"script-src " + strings.Join(scriptSrc, " "),
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob: https: http:",
"font-src 'self' data:",
"media-src 'self' blob:",
"connect-src 'self'",
"worker-src 'self' blob:",
"manifest-src 'self'",
}, "; ")
}