From 2dfd6edeee455bd7a149b7433e8445f663e97d2e Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 20 Aug 2026 04:00:42 +0000 Subject: [PATCH] fix(browse): 403 hint stops recommending --admin; invariant names both scope defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scope-denied hint told restricted agents to "re-pair with --admin for eval/cookies/storage" — but --admin is a legacy alias for --control, so following it over-granted browser-wide destructive commands on top of the admin scope the default already carries. The hint now matches the CLI's sibling wording: re-pair without --restrict for page access, --control for browser control. Registry invariant #2 claimed "admin scope denied by default" three releases after b73f3644 deliberately made /pair grant admin. It now names BOTH defaults precisely (registry API functions default read+write; the /pair ceremony grants DEFAULT_PAIR_SCOPES) so the header cannot lie one layer down. Co-Authored-By: Claude Fable 5 --- browse/src/server.ts | 2 +- browse/src/token-registry.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/browse/src/server.ts b/browse/src/server.ts index 6efdee65c..40d5b6c62 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -1000,7 +1000,7 @@ async function handleCommandInternalImpl( status: 403, json: true, result: JSON.stringify({ error: `Command "${command}" not allowed by your token scope`, - hint: `Your scopes: ${tokenInfo.scopes.join(', ')}. Ask the user to re-pair with --admin for eval/cookies/storage access.`, + hint: `Your scopes: ${tokenInfo.scopes.join(', ')}. Ask the user to re-pair without --restrict for full page access, or with --control for browser control commands.`, }), }; } diff --git a/browse/src/token-registry.ts b/browse/src/token-registry.ts index 0787aabf0..23c9a388f 100644 --- a/browse/src/token-registry.ts +++ b/browse/src/token-registry.ts @@ -19,7 +19,12 @@ * * Security invariants: * 1. Only root token can mint sub-tokens (POST /token, POST /connect) - * 2. admin scope denied by default — must be explicitly granted + * 2. control scope denied by default — must be explicitly flagged. + * Registry API defaults (createToken/createSetupKey with no scopes) + * stay ['read','write']; the /pair ceremony explicitly grants + * DEFAULT_PAIR_SCOPES (read+write+admin+meta — the pairing ceremony + * is the trust boundary; --restrict narrows, --control must be + * explicit and never rides in via a scopes list) * 3. chain command scope-checks each subcommand individually * 4. Root token never in connection strings or pasted instructions *