[security] fix(auth): reject admin claims in user tokens#64
Merged
steipete merged 3 commits intoMay 9, 2026
Merged
Conversation
1e4d933 to
05bae43
Compare
10 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR hardens the coordinator user-token trust boundary so signed GitHub/browser-login user tokens cannot carry or activate admin privileges.
cbxu_user tokens that contain anadminclaim.Security issues covered
admin: trueCRABBOX_SESSION_SECRETfalls back toCRABBOX_SHARED_TOKENBefore this PR
issueUserToken()did not issue admin user tokens, butverifyUserToken()accepted arbitrary signed payloads that includedadmin: true.authenticateRequest()then mappedpayload.admin === trueintox-crabbox-admin: truefor downstream routes.CRABBOX_SESSION_SECRETuseCRABBOX_SHARED_TOKENas the user-token HMAC key, so a shared-token holder could sign their owncbxu_payload.After this PR
adminclaim is rejected during token verification.UserTokenPayloadschema no longer includesadmin.admin: trueuser token does not authenticate.Why this matters
Crabbox separates normal automation/user access from coordinator admin access. Admin routes can view or manage global coordinator state, including all leases, pool state, image operations, and forced release/delete flows.
If signed user tokens can self-assert admin status, that separation can collapse in deployments where the user-token signing secret falls back to the shared non-admin token. A caller who should only have shared-token automation access could mint a
cbxu_token that passes the admin route gate.How this differs from prior auth hardening
Previous public auth hardening focused on identity/header trust, such as ensuring caller-supplied Access identity headers do not override signed GitHub user-token identity.
This PR addresses a different boundary:
Both boundaries matter. A user token can have the correct owner/org identity and still be unsafe if it can self-assert
admin: true.Attack flow
Affected code
worker/src/auth.ts,worker/test/http.test.tsRoot cause
Signed user-token admin claim acceptance:
verifyUserToken()validated signature, expiry, and identity fields, but did not reject anadminclaim.authenticateRequest()treatedpayload.admin === trueas an admin authentication context even though GitHub/browser-login user tokens are documented as non-admin.sessionSecret()can fall back toCRABBOX_SHARED_TOKEN, making this unsafe when no distinctCRABBOX_SESSION_SECRETis configured.CVSS assessment
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HRationale:
Safe reproduction steps
CRABBOX_SHARED_TOKENand no distinctCRABBOX_SESSION_SECRET.cbxu_token payload containing:typ: "crabbox-user"expadmin: trueAuthorization: Bearer <forged-cbxu-token>to an admin-only route such as/v1/admin/leases.authenticateRequest()rejects the token.The regression test added in this PR performs the safe local version of this flow without contacting any real coordinator.
Expected vulnerable behavior
admin: trueauthenticates asauth: "github"andadmin: truewhen the signer knows the user-token HMAC key.CRABBOX_SHARED_TOKEN, the non-admin shared token becomes enough to mint an admin user token.Changes in this PR
adminfrom the signed user-token payload type.admin: false.adminclaim.admin: trueuser-token payload and expects authentication to fail.Files changed
worker/src/auth.tsworker/test/http.test.tsMaintainer impact
CRABBOX_ADMIN_TOKENthrough the existing admin bearer-token path.Fix rationale
GitHub/browser-login user tokens are documented as non-admin. The most durable boundary is therefore to make the user-token verifier reject admin-bearing payloads and make the authenticated context non-admin by construction.
This avoids depending on every deployment having a distinct session secret before the admin boundary is safe, while still preserving the existing recommendation to keep
CRABBOX_SESSION_SECRETas a separate Worker secret.Type of change
Test plan
Executed with:
npm test --prefix worker -- http.test.tsnpm test --prefix workernpm run check --prefix workernpm run lint --prefix workergit diff --checkToken usage
admin/cbxu_,admin claims/signed user,CRABBOX_SESSION_SECRET/CRABBOX_SHARED_TOKEN,payload.admin,shared token/admin, and repo security advisories.Disclosure notes
CRABBOX_ADMIN_TOKENpath.