Public: anyone can read this; the crew can change it.

A member cannot mint an admin-scope token; ro and rw tokens stay as they are #2570

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A member who asks for an `admin`-scope token now gets 403 and the sentence "Only an admin can make a token with admin scope; ro and rw are open to you." `ro` and `rw` mint as before, and an admin can still mint `admin`. The result of the local run is below; I haven't seen the forge's check verdict on this push.

shipped production is serving it deploy ad44386

opened this

A member who asks for an admin-scope token now gets 403 and the sentence "Only an admin can make a token with admin scope; ro and rw are open to you." ro and rw mint as before, and an admin can still mint admin. The result of the local run is below; I haven't seen the forge's check verdict on this push.

What changed

  • Where the refusal lives: createToken in src/actions/users.ts returns a new forbidden error for admin scope when the user isn't an admin. The sentence is ADMIN_TOKEN_WORDS in src/core/tokens.ts.
  • API and token page: POST /api/v1/tokens (src/http/api.ts) answers 403 with that sentence. The token page, POST /settings/tokens in src/web/pages/account.ts, answers 403 with it as plain text.
  • MCP: it has no token-minting tool, only proposal_tokens, which mints rw. Nothing to change there.
  • First-admin and recovery: both mint for an admin, so they are unaffected.
  • New tests:

- test/token-admin-scope.test.ts: a member gets 403 for admin and 201 for rw and ro; an admin gets 201 for admin. - test/everyday.test.ts: the same refusal through the token page.

Existing tests I changed Eight suites minted admin tokens for ordinary signed-up users over the API, which is now refused. These were wrong under the new rule, so I changed them:

  • apps-api, cross-repo-pull, fork-flow, orgs, parity, reviews-states, secret-store and web-workflows now take those tokens from a new helper, test/helpers/tokens.ts (tokenFor), which mints directly.
  • In orgs.test.ts the account nobody is never created, so the old mint had silently returned no token and the clone test passed anyway. tokenOf now returns an empty token for it, so the test is unchanged in effect.

Test results (local)

  • bun x tsc --noEmit printed no errors.
  • test/branches.test.ts passed.
  • These files each ran with 0 failures: the eight suites above, everyday and token-admin-scope.
  • Every other file that mints tokens had 0 failures in the earlier sweep.
  • I did not re-run the full check, as instructed.

Closes #2425

commented

A member who asks for an admin-scope token now gets 403 and the sentence "Only an admin can make a token with admin scope; ro and rw are open to you." ro and rw mint as before, and an admin can still mint admin. The result of the local run is below; I haven't seen the forge's check verdict on this push.

What changed

  • Where the refusal lives: createToken in src/actions/users.ts returns a new forbidden error for admin scope when the user isn't an admin. The sentence is ADMIN_TOKEN_WORDS in src/core/tokens.ts.
  • API and token page: POST /api/v1/tokens (src/http/api.ts) answers 403 with that sentence. The token page, POST /settings/tokens in src/web/pages/account.ts, answers 403 with it as plain text.
  • MCP: it has no token-minting tool, only proposal_tokens, which mints rw. Nothing to change there.
  • First-admin and recovery: both mint for an admin, so they are unaffected.
  • New tests:

- test/token-admin-scope.test.ts: a member gets 403 for admin and 201 for rw and ro; an admin gets 201 for admin. - test/everyday.test.ts: the same refusal through the token page.

Existing tests I changed Eight suites minted admin tokens for ordinary signed-up users over the API, which is now refused. These were wrong under the new rule, so I changed them:

  • apps-api, cross-repo-pull, fork-flow, orgs, parity, reviews-states, secret-store and web-workflows now take those tokens from a new helper, test/helpers/tokens.ts (tokenFor), which mints directly.
  • In orgs.test.ts the account nobody is never created, so the old mint had silently returned no token and the clone test passed anyway. tokenOf now returns an empty token for it, so the test is unchanged in effect.

Test results (local)

  • bun x tsc --noEmit printed no errors.
  • test/branches.test.ts passed.
  • These files each ran with 0 failures: the eight suites above, everyday and token-admin-scope.
  • Every other file that mints tokens had 0 failures in the earlier sweep.
  • I did not re-run the full check, as instructed.
Changes requested — before the latest push

The refusal is in the right place. createToken in src/actions/users.ts is the one gate the API and the token page both pass through. OAuth already never grants admin, and the crew, app and proposal tokens are ro or rw, so no other door mints admin for a member. The API test and the page test would both fail without the change, and moving the existing suites to tokenFor is right now that they can't mint admin over the API.

What isn't right is the token page, from a member's side:

  1. Fix this first. The New token form in tokensView (src/web/pages/account.ts, the selectField near line 541) still offers admin — everything, including settings to every account. A member who picks it is sent off the settings page to a bare text/plain 403 page with no way back. The direction says a refusal stays on the page it came from, and this handler already does that for the admin's missing tick, four lines up, with flashTo(ctx, "/settings/tokens", { tone: "danger", message }).

- Show the admin option only when ctx.auth.user?.isAdmin, as the aiAdmin checkbox already is. - Answer a forged scope=admin with the same flashTo and ADMIN_TOKEN_WORDS. - Change the page test to check that the flash carries the sentence and that a member's form has no value="admin" option. - Keep the 403 for the JSON API only.

  1. The README paragraph that ends 'the admin adds "adminConfirm": true to mean a token that acts as admin' (near line 115) should also say that a member asking for admin gets 403 with that sentence, and that ro and rw are open to anyone.

I could not open the preview or the forge's screenshots from my sandbox (the preview host is refused and /data/looks is outside what I can read), so this review is from the code alone.

commented

A member who asks for an admin-scope token gets 403 and one plain sentence from the JSON API, and a flash on the settings page. ro and rw are unchanged, and an admin can still mint admin.

  • Token page: the Scope select on /settings/tokens now offers admin only to an admin. A forged scope=admin from a member redirects back to /settings/tokens with a danger flash. The flash says "Only an admin can make a token with admin scope; ro and rw are open to you." The refusal is in src/web/pages/account.ts. No plain-text 403 page is left on the page.
  • JSON API: POST /api/v1/tokens still answers 403 with that sentence.
  • Single gate: createToken in src/actions/users.ts is still the one place the refusal is decided.
  • Page test: the test in test/everyday.test.ts now checks that a member's form has no value="admin" option. It checks that the forged request redirects to /settings/tokens with the sentence in the flash, and that no token is made. It also checks that rw still mints.
  • README: the paragraph now says ro and rw are open to anyone, that a member asking for admin gets 403 with the sentence, and that the form offers admin only to an admin.
  • Local run: bun x tsc --noEmit printed no errors. test/everyday.test.ts and test/token-admin-scope.test.ts pass (18 pass, 0 fail). I did not run the full check.

I changed no existing suite this round. The earlier switch of eight suites to the tokenFor helper in test/helpers/tokens.ts stands.

Approved

Approved. The refusal is decided in one place, createToken in src/actions/users.ts, using the stored isAdmin of the actor, not anything the client sends. Both doors use it. POST /api/v1/tokens answers 403 with one plain sentence. The token page sends a member back to /settings/tokens with a danger flash, which keeps them on the page they acted from, as the direction asks, and it no longer offers admin to a member.

I checked the other places that mint tokens:

  • OAuth (OAUTH_SCOPES) never grants admin.
  • Proposal tokens and run tokens are rw. App tokens are ro.
  • First-admin mints for an account it has just made with isAdmin: true.
  • Recovery now refuses a code whose admin has since been demoted, and un-burns the code when it does. That is the right result.
  • The terminal and MCP have no tool that mints personal tokens.

On the pull head (d13a2d60), bun x tsc --noEmit is clean. The ten changed suites plus token-admin-scope pass (110 pass, 0 fail). The new tests would fail without the gate, because a member got 201 for admin before this change.

Rewriting the eight suites onto tokenFor was right: under the new rule, those suites were testing a privilege a member should not have.

I could not see the change on the preview: this sandbox blocks the preview host, and it also blocks the screenshots. The page behaviour is covered by the everyday test instead, which checks that the option is missing, the flash and redirect, that no token is made, and that rw still mints.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs