Skip to content

fix(dashboard): restrict admin_token query-param to GET only (PILOT-238)#87

Closed
matthew-pilot wants to merge 1 commit into
mainfrom
openclaw/pilot-238-20260629-172046
Closed

fix(dashboard): restrict admin_token query-param to GET only (PILOT-238)#87
matthew-pilot wants to merge 1 commit into
mainfrom
openclaw/pilot-238-20260629-172046

Conversation

@matthew-pilot

Copy link
Copy Markdown
Collaborator

What changed

The requireAdminToken wrapper accepted admin_token= via query parameter for all HTTP methods, not just GET. This meant a leaked dashboard URL containing ?admin_token=... (e.g. pasted into Slack, email, or browser history) could be used for cross-site mutation attacks on any admin endpoint wrapped by it.

The /api/banner and /api/breakers/ handlers already had inline restrictions (header-only for mutations), but requireAdminToken itself — used by /api/admin/runtime/profile-rates, /api/admin/runtime/log-level, /api/admin/whitelist, /api/admin/runtime/gc, and others — still accepted query-param tokens for writes.

Why

Query parameters are a "simple" part of an HTTP request (like URL paths or body content). An attacker-controlled page can submit a form or script that sends a POST/PUT/DELETE with ?admin_token=... appended — the browser sends the token without triggering a CORS preflight. By contrast, the X-Admin-Token header is a non-simple header that triggers preflight, blocking cross-origin requests from attacker pages.

Verification

  • go build ./...
  • go vet ./...
  • go test ./... — all packages green ✅
  • TestRequireAdminToken_RejectsQueryParamOnWrite — new test that confirms query-param token is accepted for GET but rejected with 401 for POST/PUT/DELETE ✅

Files changed

  • dashboard/dashboard.go (+7/-4) — restrict query-param fallback to GET in requireAdminToken
  • dashboard/zz_stats_auth_test.go (+56/-0) — new test for the restriction

Closes https://vulturelabs.atlassian.net/browse/PILOT-238

The requireAdminToken wrapper accepted admin_token= via query parameter
for ALL HTTP methods, not just GET. A leaked dashboard URL containing
?admin_token=... could be used for cross-site mutation attacks against
any admin endpoint wrapped with requireAdminToken — the browser would
send the token as a query parameter on POST/PUT/DELETE requests with
no CORS preflight barrier (query params are a simple request part).

Fix: restrict query-param fallback to GET only. POST/PUT/DELETE must
use the X-Admin-Token header, which triggers CORS preflight and blocks
cross-origin requests from attacker-controlled pages. This is consistent
with the existing patterns in the /api/banner and /api/breakers/ handlers
that already had this restriction inlined.

Add TestRequireAdminToken_RejectsQueryParamOnWrite to pin the behavior:
query-param token is accepted for GET (read-only convenience) but
rejected with 401 for POST, PUT, and DELETE.

Closes PILOT-238
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@matthew-pilot

Copy link
Copy Markdown
Collaborator Author

Superseded by PR #89 — broader fix at requireAdminToken level

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant