Summary
Crabbox documents GitHub browser login as a supported coordinator authentication boundary: signed cbxu_ user tokens carry the caller's owner, org, and GitHub login, and normal user tokens are scoped to their own owner/org resources. That boundary depends on the owner claim being anchored to a verified GitHub identity.
At commit 0ec69d6427646c7bddffbac2c33576f211c203b1, githubIdentity in worker/src/oauth.ts prefers verified email addresses from /user/emails, but it initializes owner from user.email returned by /user and keeps that value whenever /user/emails returns no verified address. GitHub documents /user.email as the publicly visible profile email, while /user/emails is the endpoint that reports verified state. As a result, a successful GitHub OAuth login can produce a signed Crabbox user token whose trusted owner is a public profile value rather than a verified email.
This is in scope under Crabbox's security policy because it affects coordinator authentication, owner scoping, and optional admin-owner promotion for the supported trusted-team model. It does not depend on hostile tenants sharing one broker or on a trusted operator attacking their own leased machine.
Affected Components
worker/src/oauth.ts:197-201: the OAuth authorization URL requests read:user user:email read:org.
worker/src/oauth.ts:231-251: the callback exchanges the GitHub code, resolves identity.owner, checks allowed org/team membership by identity.login, and signs a Crabbox user token.
worker/src/oauth.ts:428-456: githubIdentity starts with owner = user.email || "", overwrites it only if /user/emails contains a verified address, and otherwise keeps the public email fallback.
worker/src/auth.ts:166-192: issueUserToken signs the selected owner into the cbxu_ token payload.
worker/src/auth.ts:90-101: authenticateRequest trusts the signed payload's owner, org, and login for GitHub-authenticated requests.
worker/src/auth.ts:107-117: githubUserIsAdmin promotes a signed user token to admin if payload.owner matches CRABBOX_GITHUB_ADMIN_OWNERS or payload.login matches CRABBOX_GITHUB_ADMIN_LOGINS.
worker/src/http.ts:29 and worker/src/fleet.ts: coordinator handlers use the trusted x-crabbox-owner/x-crabbox-org request context for lease, run, usage, bridge, portal, and sharing authorization decisions.
Attack Path
Attacker role: a GitHub user who passes the deployment's configured GitHub org/team allowlist and can complete crabbox login --url <broker-url>.
Prerequisites:
- GitHub browser login is enabled.
- The attacker is an active member of an allowed org/team.
- The GitHub API response for
/user/emails contains no verified email for the attacker, while /user.email contains a public profile email value.
- For admin impact, the deployment also grants admin by
CRABBOX_GITHUB_ADMIN_OWNERS and that owner list contains the public-email value selected by the attacker. CRABBOX_GITHUB_ADMIN_LOGINS is not affected by this email fallback because it compares the GitHub login, not owner.
Steps:
- The attacker configures their GitHub public profile email to the owner value they want Crabbox to trust.
- The attacker completes the normal GitHub OAuth flow through
crabbox login --url <broker-url>.
githubAuthCallback calls githubIdentity(accessToken).
githubIdentity reads /user, sets owner from user.email, then calls /user/emails.
- If
/user/emails has no verified: true entry, githubIdentity keeps the public profile email instead of failing closed or falling back to a non-email login-derived identity.
issueUserToken signs that value into the cbxu_ token as payload.owner.
- Later requests authenticate as
auth: "github" with owner: payload.owner; requestWithAuthContext injects it as x-crabbox-owner.
- Fleet, portal, usage, run, bridge, and sharing handlers use the trusted owner/org context for visibility and management checks. If the forged owner matches a real owner in the same org, the attacker can be treated as that owner. If the forged owner also matches
CRABBOX_GITHUB_ADMIN_OWNERS, the same signed user token receives admin scope.
The closest missing control is in worker/src/oauth.ts:445-456: Crabbox does not require the owner email to come from a verified /user/emails entry before signing it into a long-lived user token.
Impact
The supported coordinator identity boundary can bind a Crabbox user token to an unverified GitHub public profile email. That breaks the documented expectation in docs/security.md and docs/features/auth-admin.md that GitHub user tokens are trusted owner/org credentials for the authenticated GitHub user.
Practical outcomes include:
- Cross-owner impersonation inside the same allowed org if an attacker can make Crabbox sign an
owner value that matches another user's Crabbox owner identity.
- Unauthorized visibility or management of owner-scoped leases, runs, logs, usage, portal state, bridge attachments, and sharing state where handlers compare the trusted owner/org context.
- Optional admin escalation when a deployment uses
CRABBOX_GITHUB_ADMIN_OWNERS and the selected public email matches an admin-owner value.
- Audit and attribution confusion because repeated logins can produce different owner identities for the same GitHub login when the public email fallback changes.
The issue is bounded by the GitHub org/team allowlist: the attacker must still be accepted by the configured GitHub membership checks. It is nevertheless a reportable Crabbox boundary issue because the flawed value is persisted in a signed coordinator credential and consumed by authorization code.
Severity Assessment
CVSS Assessment
| Metric |
v3.1 |
v4.0 |
| Score |
7.6 / 10.0 |
7.2 / 10.0 |
| Severity |
High |
High |
| Vector |
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L |
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:L/SC:N/SI:N/SA:N |
| Calculator |
CVSS v3.1 Calculator |
CVSS v4.0 Calculator |
The primary score models cross-owner confidentiality impact plus limited integrity and availability impact from owner-scoped management operations. Deployments that use CRABBOX_GITHUB_ADMIN_OWNERS with collidable owner values can have higher integrity and availability impact, but the base score avoids assuming that optional configuration.
Recommended Remediation
Require the signed owner to come from a verified GitHub email source, or use a non-email stable GitHub identity when no verified email is available.
Suggested changes:
- In
githubIdentity, do not initialize or preserve owner from /user.email.
- Select
owner only from /user/emails entries with verified: true, preferring the primary verified email.
- If no verified email exists, either reject the OAuth callback with
GitHubAuthorizationError or deliberately use a stable login/id-derived owner such as <login>@users.noreply.github.com with documentation that it is not an email-verified owner.
- Keep admin-owner grants tied only to verified owner values. Continue using
CRABBOX_GITHUB_ADMIN_LOGINS for login-based grants where that is the intended deployment policy.
- Add regression tests covering the no-verified-email case and the admin-owner promotion case.
Validation
Validation method: static source review against the isolated verification checkout plus official GitHub REST API documentation.
Evidence:
worker/src/oauth.ts:197-201 requests read:user user:email read:org, so Crabbox has the API scope needed to query verified email state.
worker/src/oauth.ts:439-456 fetches /user, sets owner to user.email, then only overwrites it when /user/emails contains a verified primary or verified secondary email.
worker/src/oauth.ts:231-251 passes identity.owner directly into issueUserToken.
worker/src/auth.ts:166-192 signs input.owner into the cbxu_ token payload without recording whether it came from a verified GitHub email.
worker/src/auth.ts:90-101 trusts the signed owner for every GitHub-token request.
worker/src/auth.ts:107-117 checks that same signed owner against CRABBOX_GITHUB_ADMIN_OWNERS.
docs/security.md describes signed user tokens as carrying owner, org, and GitHub login, and says admin scope can come from a signed GitHub user token whose verified email or login matches the configured admin lists.
docs/features/auth-admin.md documents that GitHub user tokens get their trusted owner/org/login from the signed token and that normal user tokens are scoped to their own resources.
- GitHub's
/user documentation identifies email as the publicly visible profile email, while /user/emails is the endpoint that returns email entries with a verified field.
Counterevidence and limits:
- GitHub org/team membership is still checked by login before the token is issued.
CRABBOX_GITHUB_ADMIN_LOGINS is not affected by the email fallback because it compares the signed login.
- Admin escalation through
CRABBOX_GITHUB_ADMIN_OWNERS depends on deployment configuration. The owner-identity binding bug and cross-owner authorization risk do not require that optional admin list.
- Runtime reproduction was not attempted because the issue is fully visible in the OAuth/auth source path and no source edits or external GitHub test account should be created for verification.
Suggested regression tests:
- Mock
/user to return { "login": "attacker", "email": "victim@example.test" } and /user/emails to return only unverified entries or an empty list. The OAuth callback should fail closed, or should issue a token with the documented non-email fallback rather than victim@example.test.
- Mock a token issued from the no-verified-email path and assert that
authenticateRequest does not promote it through CRABBOX_GITHUB_ADMIN_OWNERS.
Summary
Crabbox documents GitHub browser login as a supported coordinator authentication boundary: signed
cbxu_user tokens carry the caller'sowner,org, and GitHublogin, and normal user tokens are scoped to their own owner/org resources. That boundary depends on theownerclaim being anchored to a verified GitHub identity.At commit
0ec69d6427646c7bddffbac2c33576f211c203b1,githubIdentityinworker/src/oauth.tsprefers verified email addresses from/user/emails, but it initializesownerfromuser.emailreturned by/userand keeps that value whenever/user/emailsreturns no verified address. GitHub documents/user.emailas the publicly visible profile email, while/user/emailsis the endpoint that reportsverifiedstate. As a result, a successful GitHub OAuth login can produce a signed Crabbox user token whose trustedowneris a public profile value rather than a verified email.This is in scope under Crabbox's security policy because it affects coordinator authentication, owner scoping, and optional admin-owner promotion for the supported trusted-team model. It does not depend on hostile tenants sharing one broker or on a trusted operator attacking their own leased machine.
Affected Components
worker/src/oauth.ts:197-201: the OAuth authorization URL requestsread:user user:email read:org.worker/src/oauth.ts:231-251: the callback exchanges the GitHub code, resolvesidentity.owner, checks allowed org/team membership byidentity.login, and signs a Crabbox user token.worker/src/oauth.ts:428-456:githubIdentitystarts withowner = user.email || "", overwrites it only if/user/emailscontains a verified address, and otherwise keeps the public email fallback.worker/src/auth.ts:166-192:issueUserTokensigns the selectedownerinto thecbxu_token payload.worker/src/auth.ts:90-101:authenticateRequesttrusts the signed payload'sowner,org, andloginfor GitHub-authenticated requests.worker/src/auth.ts:107-117:githubUserIsAdminpromotes a signed user token to admin ifpayload.ownermatchesCRABBOX_GITHUB_ADMIN_OWNERSorpayload.loginmatchesCRABBOX_GITHUB_ADMIN_LOGINS.worker/src/http.ts:29andworker/src/fleet.ts: coordinator handlers use the trustedx-crabbox-owner/x-crabbox-orgrequest context for lease, run, usage, bridge, portal, and sharing authorization decisions.Attack Path
Attacker role: a GitHub user who passes the deployment's configured GitHub org/team allowlist and can complete
crabbox login --url <broker-url>.Prerequisites:
/user/emailscontains no verified email for the attacker, while/user.emailcontains a public profile email value.CRABBOX_GITHUB_ADMIN_OWNERSand that owner list contains the public-email value selected by the attacker.CRABBOX_GITHUB_ADMIN_LOGINSis not affected by this email fallback because it compares the GitHub login, notowner.Steps:
crabbox login --url <broker-url>.githubAuthCallbackcallsgithubIdentity(accessToken).githubIdentityreads/user, setsownerfromuser.email, then calls/user/emails./user/emailshas noverified: trueentry,githubIdentitykeeps the public profile email instead of failing closed or falling back to a non-email login-derived identity.issueUserTokensigns that value into thecbxu_token aspayload.owner.auth: "github"withowner: payload.owner;requestWithAuthContextinjects it asx-crabbox-owner.CRABBOX_GITHUB_ADMIN_OWNERS, the same signed user token receives admin scope.The closest missing control is in
worker/src/oauth.ts:445-456: Crabbox does not require the owner email to come from a verified/user/emailsentry before signing it into a long-lived user token.Impact
The supported coordinator identity boundary can bind a Crabbox user token to an unverified GitHub public profile email. That breaks the documented expectation in
docs/security.mdanddocs/features/auth-admin.mdthat GitHub user tokens are trusted owner/org credentials for the authenticated GitHub user.Practical outcomes include:
ownervalue that matches another user's Crabbox owner identity.CRABBOX_GITHUB_ADMIN_OWNERSand the selected public email matches an admin-owner value.The issue is bounded by the GitHub org/team allowlist: the attacker must still be accepted by the configured GitHub membership checks. It is nevertheless a reportable Crabbox boundary issue because the flawed value is persisted in a signed coordinator credential and consumed by authorization code.
Severity Assessment
CVSS Assessment
The primary score models cross-owner confidentiality impact plus limited integrity and availability impact from owner-scoped management operations. Deployments that use
CRABBOX_GITHUB_ADMIN_OWNERSwith collidable owner values can have higher integrity and availability impact, but the base score avoids assuming that optional configuration.Recommended Remediation
Require the signed
ownerto come from a verified GitHub email source, or use a non-email stable GitHub identity when no verified email is available.Suggested changes:
githubIdentity, do not initialize or preserveownerfrom/user.email.owneronly from/user/emailsentries withverified: true, preferring the primary verified email.GitHubAuthorizationErroror deliberately use a stable login/id-derived owner such as<login>@users.noreply.github.comwith documentation that it is not an email-verified owner.CRABBOX_GITHUB_ADMIN_LOGINSfor login-based grants where that is the intended deployment policy.Validation
Validation method: static source review against the isolated verification checkout plus official GitHub REST API documentation.
Evidence:
worker/src/oauth.ts:197-201requestsread:user user:email read:org, so Crabbox has the API scope needed to query verified email state.worker/src/oauth.ts:439-456fetches/user, setsownertouser.email, then only overwrites it when/user/emailscontains a verified primary or verified secondary email.worker/src/oauth.ts:231-251passesidentity.ownerdirectly intoissueUserToken.worker/src/auth.ts:166-192signsinput.ownerinto thecbxu_token payload without recording whether it came from a verified GitHub email.worker/src/auth.ts:90-101trusts the signed owner for every GitHub-token request.worker/src/auth.ts:107-117checks that same signed owner againstCRABBOX_GITHUB_ADMIN_OWNERS.docs/security.mddescribes signed user tokens as carryingowner,org, and GitHublogin, and says admin scope can come from a signed GitHub user token whose verified email or login matches the configured admin lists.docs/features/auth-admin.mddocuments that GitHub user tokens get their trustedowner/org/loginfrom the signed token and that normal user tokens are scoped to their own resources./userdocumentation identifiesemailas the publicly visible profile email, while/user/emailsis the endpoint that returns email entries with averifiedfield.Counterevidence and limits:
CRABBOX_GITHUB_ADMIN_LOGINSis not affected by the email fallback because it compares the signedlogin.CRABBOX_GITHUB_ADMIN_OWNERSdepends on deployment configuration. The owner-identity binding bug and cross-owner authorization risk do not require that optional admin list.Suggested regression tests:
/userto return{ "login": "attacker", "email": "victim@example.test" }and/user/emailsto return only unverified entries or an empty list. The OAuth callback should fail closed, or should issue a token with the documented non-email fallback rather thanvictim@example.test.authenticateRequestdoes not promote it throughCRABBOX_GITHUB_ADMIN_OWNERS.