From 87a3e6256979f8a8448d843fb496446f4a81bf70 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 5 Apr 2026 00:06:52 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20scoped=20tokens=20rejected=20on=20/comma?= =?UTF-8?q?nd=20=E2=80=94=20auth=20gate=20ordering=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The blanket validateAuth() gate (root-only) sat above the /command endpoint, rejecting all scoped tokens with 401 before they reached getTokenInfo(). Moved /command above the gate so both root and scoped tokens are accepted. This was the bug Wintermute hit. Co-Authored-By: Claude Opus 4.6 (1M context) --- browse/src/server.ts | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/browse/src/server.ts b/browse/src/server.ts index 097326ed..01a54bc2 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -1792,7 +1792,23 @@ async function start() { return new Response(JSON.stringify({ ok: true }), { status: 200, headers: { 'Content-Type': 'application/json' } }); } - // ─── Auth-required endpoints ────────────────────────────────── + // ─── Command endpoint (accepts both root AND scoped tokens) ──── + // Must be checked BEFORE the blanket root-only auth gate below, + // because scoped tokens from /connect are valid for /command. + if (url.pathname === '/command' && req.method === 'POST') { + const tokenInfo = getTokenInfo(req); + if (!tokenInfo) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' }, + }); + } + resetIdleTimer(); + const body = await req.json(); + return handleCommand(body, tokenInfo); + } + + // ─── Auth-required endpoints (root token only) ───────────────── if (!validateAuth(req)) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { @@ -1952,22 +1968,6 @@ async function start() { }); } - // ─── Command endpoint ────────────────────────────────────────── - - if (url.pathname === '/command' && req.method === 'POST') { - // Accept both root token and scoped tokens - const tokenInfo = getTokenInfo(req); - if (!tokenInfo) { - return new Response(JSON.stringify({ error: 'Unauthorized' }), { - status: 401, - headers: { 'Content-Type': 'application/json' }, - }); - } - resetIdleTimer(); // Only commands reset idle timer - const body = await req.json(); - return handleCommand(body, tokenInfo); - } - return new Response('Not found', { status: 404 }); }, });