feat(server): honor ?admin=true|false elevation on every endpoint

shared/elevation.js toggles admin mode via the ?admin= URL param, but it's
client-side JS — it only runs on HTML tool pages, where it sets the sticky
zddc-elevate cookie. A raw endpoint (a directory's JSON listing, zip
browsing at /<…>.zip/, the file API) loads no JS, so ?admin=true was inert
there and such requests stayed un-elevated.

ACLMiddleware now reads the same ?admin= toggle directly: true|1|on|yes
elevates the request, false|0|off|no drops it (overriding the cookie for
that request). This is per-request only — the server doesn't set/clear the
cookie; elevation.js still owns sticky persistence on pages. Elevation
grants powers only to a caller who already holds admin authority (every
admin call site re-checks via IsActiveAdmin), so a non-admin's ?admin=true
sets the forensic flag but confers nothing.

Makes e.g. GET /.zddc.zip/?admin=true work for an admin without first
arming the cookie on a page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-06-04 13:13:30 -05:00
parent 9513ea3a07
commit c59bea183e
2 changed files with 109 additions and 6 deletions

View file

@ -46,6 +46,25 @@ const ElevatedKey contextKey = "elevated"
// named in admin lists. // named in admin lists.
const elevationCookieName = "zddc-elevate" const elevationCookieName = "zddc-elevate"
// adminQueryParam reads the ?admin= elevation toggle, returning a pointer to
// the requested state (true = elevate, false = drop) or nil when the param is
// absent or unrecognised. Recognised values mirror shared/elevation.js so the
// URL toggle behaves identically whether elevation.js sets the cookie or the
// server honors the bare param: true/1/on/yes and false/0/off/no
// (case-insensitive).
func adminQueryParam(r *http.Request) *bool {
v := strings.ToLower(r.URL.Query().Get("admin"))
switch v {
case "true", "1", "on", "yes":
t := true
return &t
case "false", "0", "off", "no":
f := false
return &f
}
return nil
}
// ACLMiddleware extracts the user email and stores it (along with the // ACLMiddleware extracts the user email and stores it (along with the
// policy decider) in the request context. It does NOT enforce ACL // policy decider) in the request context. It does NOT enforce ACL
// itself — each handler performs its own ACL check via // itself — each handler performs its own ACL check via
@ -98,6 +117,20 @@ func ACLMiddleware(cfg config.Config, decider policy.Decider, tokens *auth.Store
if c, err := r.Cookie(elevationCookieName); err == nil && c.Value == "1" { if c, err := r.Cookie(elevationCookieName); err == nil && c.Value == "1" {
elevated = true elevated = true
} }
// ?admin=true|1|on|yes elevates this request directly, and
// ?admin=false|0|off|no drops it — mirroring the URL toggle in
// shared/elevation.js, but honored at the server so the param
// works on EVERY endpoint (raw directory listings, zip browsing,
// the file API), not just HTML pages where elevation.js runs to
// set the cookie. elevation.js still sets the cookie for sticky
// persistence across navigation; this just makes the bare param
// effective on a single direct request too. Elevation only grants
// powers to a caller who already holds admin authority (every
// admin call site re-checks the cascade via IsActiveAdmin), so
// honoring the param for a non-admin is a harmless no-op.
if v := adminQueryParam(r); v != nil {
elevated = *v
}
} }
// DEBUG-level header dump for diagnosing proxy / SSO header // DEBUG-level header dump for diagnosing proxy / SSO header
// passthrough. Off by default (LogLevel info); enable with // passthrough. Off by default (LogLevel info); enable with

View file

@ -194,12 +194,12 @@ func TestAccessLog_ChainAdminLevelAttribution(t *testing.T) {
} }
cases := []struct { cases := []struct {
name string name string
email string email string
elevate bool elevate bool
path string path string
wantLevel int wantLevel int
wantActive bool wantActive bool
}{ }{
{"root admin elevated probing root → level 0", "root@example.com", true, "/", 0, true}, {"root admin elevated probing root → level 0", "root@example.com", true, "/", 0, true},
{"root admin elevated probing project → level 0 (walks down chain)", "root@example.com", true, "/Project-1/", 0, true}, {"root admin elevated probing project → level 0 (walks down chain)", "root@example.com", true, "/Project-1/", 0, true},
@ -250,3 +250,73 @@ func TestAccessLog_ChainAdminLevelAttribution(t *testing.T) {
}) })
} }
} }
// TestACLMiddleware_AdminQueryParamElevation verifies the server honors the
// ?admin= URL toggle directly (mirroring shared/elevation.js), so the param
// elevates ANY endpoint — not just HTML pages where elevation.js runs to set
// the cookie. ?admin=true elevates with no cookie; ?admin=false drops even
// when the cookie is present; a non-admin's ?admin=true sets the flag but
// confers no authority.
func TestACLMiddleware_AdminQueryParamElevation(t *testing.T) {
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, ".zddc"),
[]byte("admins:\n - root@example.com\n"), 0o644); err != nil {
t.Fatalf("write root .zddc: %v", err)
}
zddc.InvalidateCache(root)
cfg := config.Config{Root: root, EmailHeader: "X-Auth-Request-Email"}
noop := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
type record struct {
Elevated bool `json:"elevated"`
ActiveAdmin bool `json:"active_admin"`
}
run := func(t *testing.T, path, email string, cookie bool) record {
t.Helper()
var buf bytes.Buffer
auditLogger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}))
chain := ACLMiddleware(cfg, nil, nil, AccessLogMiddleware(cfg, auditLogger, noop))
req := httptest.NewRequest(http.MethodGet, path, nil)
if email != "" {
req.Header.Set("X-Auth-Request-Email", email)
}
if cookie {
req.AddCookie(&http.Cookie{Name: "zddc-elevate", Value: "1"})
}
chain.ServeHTTP(httptest.NewRecorder(), req)
var rec record
if err := json.Unmarshal(buf.Bytes(), &rec); err != nil {
t.Fatalf("audit log not JSON: %v; raw=%s", err, buf.String())
}
return rec
}
t.Run("?admin=true elevates root admin with no cookie", func(t *testing.T) {
rec := run(t, "/?admin=true", "root@example.com", false)
if !rec.Elevated || !rec.ActiveAdmin {
t.Errorf("elevated=%v active=%v, want both true", rec.Elevated, rec.ActiveAdmin)
}
})
t.Run("?admin=false drops despite cookie", func(t *testing.T) {
rec := run(t, "/?admin=false", "root@example.com", true)
if rec.Elevated || rec.ActiveAdmin {
t.Errorf("elevated=%v active=%v, want both false", rec.Elevated, rec.ActiveAdmin)
}
})
t.Run("non-admin ?admin=true sets flag but confers no authority", func(t *testing.T) {
rec := run(t, "/?admin=true", "stranger@example.com", false)
if !rec.Elevated {
t.Errorf("elevated=%v, want true (flag set)", rec.Elevated)
}
if rec.ActiveAdmin {
t.Errorf("active_admin=%v, want false (no admin authority)", rec.ActiveAdmin)
}
})
t.Run("no param, no cookie → not elevated", func(t *testing.T) {
rec := run(t, "/", "root@example.com", false)
if rec.Elevated || rec.ActiveAdmin {
t.Errorf("elevated=%v active=%v, want both false", rec.Elevated, rec.ActiveAdmin)
}
})
}