Sandbox artifact rendering via iframe + CSP
Splits /p/{slug} into a trusted wrapper (HTML with a sandboxed iframe)
and /p/{slug}/raw (the artifact itself, served with Content-Security-Policy:
sandbox). Artifact JS now runs in an opaque origin and can't read admin
cookies or make same-origin credentialed requests to /a/* or /api/*.
Password gating is enforced on both routes so /raw can't be used to bypass
the unlock flow.
This commit is contained in:
@@ -75,8 +75,12 @@ func (s *Server) Handler() http.Handler {
|
||||
r.Get("/login", s.getLogin)
|
||||
r.Post("/login", s.postLogin)
|
||||
|
||||
// Artifact serving (public; password gating is per-artifact)
|
||||
// Artifact serving (public; password gating is per-artifact).
|
||||
// /p/{slug} renders a sandbox-iframe wrapper; /p/{slug}/raw serves the
|
||||
// artifact HTML itself with CSP: sandbox so cookies + same-origin XHR
|
||||
// are unavailable to artifact JS whether loaded via iframe or directly.
|
||||
r.Get("/p/{slug}", s.getArtifact)
|
||||
r.Get("/p/{slug}/raw", s.getArtifactRaw)
|
||||
r.Post("/p/{slug}/unlock", s.postUnlock)
|
||||
|
||||
// Admin-only routes
|
||||
@@ -127,8 +131,9 @@ func (s *Server) RollupLoop(stop <-chan struct{}) {
|
||||
|
||||
func securityHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Note: no CSP on /p/{slug} since artifact HTML often uses inline scripts + CDNs.
|
||||
if !strings.HasPrefix(r.URL.Path, "/p/") {
|
||||
// The raw artifact endpoint sets its own CSP: sandbox; skip ours here.
|
||||
isRaw := strings.HasPrefix(r.URL.Path, "/p/") && strings.HasSuffix(r.URL.Path, "/raw")
|
||||
if !isRaw {
|
||||
w.Header().Set("Content-Security-Policy",
|
||||
"default-src 'self'; "+
|
||||
"script-src 'self' https://unpkg.com https://cdn.tailwindcss.com 'unsafe-inline'; "+
|
||||
|
||||
Reference in New Issue
Block a user