Skip to content

Commit 2660f60

Browse files
committed
feat(browser): expose cookies command for HttpOnly-aware reads
Wire the existing IPage.getCookies() path to a user-facing CLI verb so agents can read HttpOnly cookies that document.cookie cannot reach. Refs #1472
1 parent 944ca3a commit 2660f60

2 files changed

Lines changed: 73 additions & 0 deletions

File tree

src/cli.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2194,6 +2194,60 @@ describe('browser console command', () => {
21942194
});
21952195
});
21962196

2197+
describe('browser cookies command', () => {
2198+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
2199+
2200+
beforeEach(() => {
2201+
process.exitCode = undefined;
2202+
consoleLogSpy.mockClear();
2203+
mockBrowserConnect.mockClear();
2204+
mockBrowserClose.mockReset().mockResolvedValue(undefined);
2205+
browserState.page = {
2206+
session: 'test',
2207+
setActivePage: vi.fn(),
2208+
getActivePage: vi.fn().mockReturnValue('tab-1'),
2209+
tabs: vi.fn().mockResolvedValue([{ page: 'tab-1', active: true }]),
2210+
getCookies: vi.fn().mockResolvedValue([
2211+
{ name: 'session', value: 'abc', domain: 'example.com', path: '/', secure: true, httpOnly: true },
2212+
{ name: 'theme', value: 'dark', domain: 'example.com', path: '/', secure: false, httpOnly: false },
2213+
]),
2214+
} as unknown as IPage;
2215+
});
2216+
2217+
function lastJsonLog(): any {
2218+
const calls = consoleLogSpy.mock.calls;
2219+
if (calls.length === 0) throw new Error('Expected at least one console.log call');
2220+
const last = calls[calls.length - 1][0];
2221+
if (typeof last !== 'string') throw new Error(`Expected string arg to console.log, got ${typeof last}`);
2222+
return JSON.parse(last);
2223+
}
2224+
2225+
it('returns cookies including HttpOnly entries for a scoped domain read', async () => {
2226+
const program = createProgram('', '');
2227+
2228+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'cookies', '--domain', 'example.com']);
2229+
2230+
expect(browserState.page!.getCookies).toHaveBeenCalledWith({ domain: 'example.com', url: undefined });
2231+
const out = lastJsonLog();
2232+
expect(out.count).toBe(2);
2233+
expect(out.cookies).toEqual(expect.arrayContaining([
2234+
expect.objectContaining({ name: 'session', httpOnly: true }),
2235+
expect.objectContaining({ name: 'theme', httpOnly: false }),
2236+
]));
2237+
});
2238+
2239+
it('errors with missing_scope when neither --domain nor --url is provided', async () => {
2240+
const program = createProgram('', '');
2241+
2242+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'cookies']);
2243+
2244+
expect(browserState.page!.getCookies).not.toHaveBeenCalled();
2245+
expect(process.exitCode).toBeDefined();
2246+
const out = lastJsonLog();
2247+
expect(out.error.code).toBe('missing_scope');
2248+
});
2249+
});
2250+
21972251
describe('browser get html command', () => {
21982252
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
21992253

src/cli.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,6 +1268,25 @@ Examples:
12681268
}, null, 2));
12691269
}));
12701270

1271+
addBrowserTabOption(browser.command('cookies'))
1272+
.option('--domain <domain>', 'Cookie domain scope (e.g. example.com)')
1273+
.option('--url <url>', 'URL scope (use domain or url, not both)')
1274+
.description('Read scoped cookies (includes HttpOnly)')
1275+
.action(browserAction(async (page, opts) => {
1276+
if (!opts.domain && !opts.url) {
1277+
console.log(JSON.stringify({ error: { code: 'missing_scope', message: 'Provide --domain or --url to scope the cookie read' } }, null, 2));
1278+
process.exitCode = EXIT_CODES.USAGE_ERROR;
1279+
return;
1280+
}
1281+
const cookies = await page.getCookies({ domain: opts.domain, url: opts.url });
1282+
console.log(JSON.stringify({
1283+
session: getPageSession(page),
1284+
captured_at: new Date().toISOString(),
1285+
count: cookies.length,
1286+
cookies,
1287+
}, null, 2));
1288+
}));
1289+
12711290
// ── Analyze (site recon, agent-native) ──
12721291
//
12731292
// Mechanizes the `site-recon.md` decision tree into one CLI call. The agent

0 commit comments

Comments
 (0)