mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-21 12:18:24 +08:00
refactor: split handleCommand into handleCommandInternal + HTTP wrapper
Chain subcommands now route through handleCommandInternal for full security enforcement (scope, domain, tab ownership, rate limiting, content wrapping). Adds recursion guard for nested chains, rate-limit exemption for chain subcommands, and activity event suppression (1 event per chain, not per sub). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,10 +44,13 @@ describe('Server auth security', () => {
|
||||
});
|
||||
|
||||
// Test 1c: newtab must check domain restrictions (CSO finding #5)
|
||||
// Domain check for newtab is now unified with goto in the scope check section:
|
||||
// (command === 'goto' || command === 'newtab') && args[0] → checkDomain
|
||||
test('newtab enforces domain restrictions', () => {
|
||||
const newtabBlock = sliceBetween(SERVER_SRC, "newtab with ownership for scoped tokens", "Block mutation commands while watching");
|
||||
expect(newtabBlock).toContain('checkDomain');
|
||||
expect(newtabBlock).toContain('Domain not allowed');
|
||||
const scopeBlock = sliceBetween(SERVER_SRC, "Scope check (for scoped tokens)", "Pin to a specific tab");
|
||||
expect(scopeBlock).toContain("command === 'newtab'");
|
||||
expect(scopeBlock).toContain('checkDomain');
|
||||
expect(scopeBlock).toContain('Domain not allowed');
|
||||
});
|
||||
|
||||
// Test 2: /refs endpoint requires auth via validateAuth
|
||||
@@ -168,7 +171,7 @@ describe('Server auth security', () => {
|
||||
|
||||
// Test 10d: server passes tokenInfo to handleMetaCommand
|
||||
test('server passes tokenInfo to handleMetaCommand', () => {
|
||||
expect(SERVER_SRC).toContain('handleMetaCommand(command, args, browserManager, shutdown, tokenInfo)');
|
||||
expect(SERVER_SRC).toContain('handleMetaCommand(command, args, browserManager, shutdown, tokenInfo,');
|
||||
});
|
||||
|
||||
// Test 10e: activity attribution includes clientId
|
||||
|
||||
@@ -502,12 +502,12 @@ describe('BROWSE_TAB tab pinning (cross-tab isolation)', () => {
|
||||
expect(cliSrc).toContain('tabId: parseInt(browseTab');
|
||||
});
|
||||
|
||||
test('handleCommand accepts tabId from request body', () => {
|
||||
test('handleCommandInternal accepts tabId from request body', () => {
|
||||
const handleFn = serverSrc.slice(
|
||||
serverSrc.indexOf('async function handleCommand('),
|
||||
serverSrc.indexOf('\nasync function ', serverSrc.indexOf('async function handleCommand(') + 1) > 0
|
||||
? serverSrc.indexOf('\nasync function ', serverSrc.indexOf('async function handleCommand(') + 1)
|
||||
: serverSrc.indexOf('\n// ', serverSrc.indexOf('async function handleCommand(') + 200),
|
||||
serverSrc.indexOf('async function handleCommandInternal('),
|
||||
serverSrc.indexOf('\n/** HTTP wrapper', serverSrc.indexOf('async function handleCommandInternal(') + 1) > 0
|
||||
? serverSrc.indexOf('\n/** HTTP wrapper', serverSrc.indexOf('async function handleCommandInternal(') + 1)
|
||||
: serverSrc.indexOf('\nasync function ', serverSrc.indexOf('async function handleCommandInternal(') + 200),
|
||||
);
|
||||
// Should destructure tabId from body
|
||||
expect(handleFn).toContain('tabId');
|
||||
@@ -516,10 +516,10 @@ describe('BROWSE_TAB tab pinning (cross-tab isolation)', () => {
|
||||
expect(handleFn).toContain('switchTab(tabId');
|
||||
});
|
||||
|
||||
test('handleCommand restores active tab after command (success path)', () => {
|
||||
test('handleCommandInternal restores active tab after command (success path)', () => {
|
||||
// On success, should restore savedTabId without stealing focus
|
||||
const handleFn = serverSrc.slice(
|
||||
serverSrc.indexOf('async function handleCommand('),
|
||||
serverSrc.indexOf('async function handleCommandInternal('),
|
||||
serverSrc.length,
|
||||
);
|
||||
// Count restore calls — should appear in both success and error paths
|
||||
@@ -527,18 +527,18 @@ describe('BROWSE_TAB tab pinning (cross-tab isolation)', () => {
|
||||
expect(restoreCount).toBeGreaterThanOrEqual(2); // success + error paths
|
||||
});
|
||||
|
||||
test('handleCommand restores active tab on error path', () => {
|
||||
test('handleCommandInternal restores active tab on error path', () => {
|
||||
// The catch block should also restore
|
||||
const catchBlock = serverSrc.slice(
|
||||
serverSrc.indexOf('} catch (err: any) {', serverSrc.indexOf('async function handleCommand(')),
|
||||
serverSrc.indexOf('} catch (err: any) {', serverSrc.indexOf('async function handleCommandInternal(')),
|
||||
);
|
||||
expect(catchBlock).toContain('switchTab(savedTabId');
|
||||
});
|
||||
|
||||
test('tab pinning only activates when tabId is provided', () => {
|
||||
const handleFn = serverSrc.slice(
|
||||
serverSrc.indexOf('async function handleCommand('),
|
||||
serverSrc.indexOf('try {', serverSrc.indexOf('async function handleCommand(') + 1),
|
||||
serverSrc.indexOf('async function handleCommandInternal('),
|
||||
serverSrc.indexOf('try {', serverSrc.indexOf('async function handleCommandInternal(') + 1),
|
||||
);
|
||||
// Should check tabId is not undefined/null before switching
|
||||
expect(handleFn).toContain('tabId !== undefined');
|
||||
|
||||
Reference in New Issue
Block a user