v0.126.3: storage wizard CSRF on claimed boxes — wizard pages rendered via raw render() shipped an EMPTY csrf-meta token, every /api/storage/init|attach POST died with 'token mismatch'; latent until the claim arc (unclaimed boxes skip CsrfProtect); fix = executeTemplate injection + session-backed regression test (red-proven)
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
## Changelog
|
||||
|
||||
### v0.126.3 — storage wizard on a CLAIMED box: the init/attach POST no longer dies on CSRF (2026-07-13)
|
||||
|
||||
First live hit during the agent-0.87.0 drill wizard leg: /api/storage/init → "CSRF token missing
|
||||
or invalid" (log: token mismatch). Root cause: `storageWizardPageHandler` rendered via raw
|
||||
`render()` instead of `executeTemplate()`, so /storage/init + /storage/attach shipped an EMPTY
|
||||
csrf-meta token — and the wizard's fetch() posts that token. LATENT until the claim arc: an
|
||||
unclaimed box skips CsrfProtect entirely, so the wizard had never run against a password-gated
|
||||
box before. Fix: executeTemplate (CSRF auto-injection); regression test renders both wizard
|
||||
pages with a real session and asserts the meta carries the SESSION token (red-proven: swap back
|
||||
to render() → both cases fail on the empty meta).
|
||||
|
||||
### v0.126.2 — stylesheet cache-bust (2026-07-13)
|
||||
|
||||
0.126.1 live QA: Cloudflare edge-caches `/static/style.css` for 4h (`Cf-Cache-Status: HIT`), so
|
||||
|
||||
@@ -261,7 +261,11 @@ func (s *Server) storageWizardPageHandler(w http.ResponseWriter, r *http.Request
|
||||
title = "Meglévő meghajtó csatolása"
|
||||
}
|
||||
data := s.baseData(tmpl, title)
|
||||
s.render(w, tmpl, data)
|
||||
// executeTemplate, NOT render(): the wizard POSTs /api/storage/* with the csrf-meta token,
|
||||
// and render() leaves {{.CSRFToken}} empty. Latent until the claim arc — on an unclaimed
|
||||
// (auth-off) box CsrfProtect is skipped entirely, so the wizard had never run against a
|
||||
// CLAIMED box before; first hit = the agent-0.87.0 drill wizard leg ("token mismatch").
|
||||
s.executeTemplate(w, r, tmpl, data)
|
||||
}
|
||||
|
||||
// ServeStorageAPI dispatches /api/storage/* (guided init/attach/eject orchestration).
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// v0.126.3 — the storage wizard pages must render the session's CSRF token into the meta tag
|
||||
// (the wizard's fetch() POSTs /api/storage/init|attach with csrfHeaders()). The pre-fix
|
||||
// storageWizardPageHandler used raw render() (no CSRF injection) → empty meta → every init
|
||||
// POST on a CLAIMED box died with "token mismatch". Latent until the claim arc because an
|
||||
// unclaimed box skips CsrfProtect entirely; first live hit was the agent-0.87.0 drill wizard
|
||||
// leg (2026-07-13).
|
||||
// COMPANION red-proof: swap executeTemplate back to render() → both cases fail on the empty
|
||||
// meta token.
|
||||
func TestStorageWizardPages_CarrySessionCSRFToken(t *testing.T) {
|
||||
s := testServer(t)
|
||||
s.loadTemplates()
|
||||
if s.sessions == nil {
|
||||
s.sessions = map[string]*session{} // testServer skips the login wiring
|
||||
}
|
||||
|
||||
// A real session, exactly as login creates it.
|
||||
tok := s.createSession()
|
||||
want := s.csrfTokenForSession(tok)
|
||||
if want == "" {
|
||||
t.Fatal("test session has no csrf token")
|
||||
}
|
||||
|
||||
for _, page := range []string{"storage_init", "storage_attach"} {
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/storage/init", nil)
|
||||
req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: tok})
|
||||
s.storageWizardPageHandler(rr, req, page)
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, `name="csrf-token" content="`+want+`"`) {
|
||||
t.Errorf("%s: the csrf meta must carry the SESSION token (got empty or wrong token)", page)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user