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 *