Skip to content

Commit 1872f8d

Browse files
committed
refactor(extension): remove lease key session backdoor
1 parent 587750c commit 1872f8d

7 files changed

Lines changed: 94 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010

1111
* **browser** — replace the `--session <name>` flag with a `<session>` positional argument that immediately follows `browser`. `opencli browser work click 12` instead of `opencli browser --session work click 12`; `opencli browser work bind` instead of `opencli browser bind --session work`. Required-flag semantics are now encoded structurally as a positional, matching the Docker/git convention for required operation-target identifiers. The internal `--session` flag is preserved for the daemon protocol and for direct `program.parseAsync` callers but is no longer part of the user-facing surface.
1212
* **env** — remove `OPENCLI_KEEP_TAB`. The flag was a debugging shortcut, not a config dimension: `--keep-tab true|false` on the command line is the single source of truth, and adapter `siteSession: 'persistent'` already pins persistent site tabs as a hard constraint. Removing the env eliminates a globally-leaking process state that overrode every browser command in the shell.
13+
* **extension** — remove the internal `surface\\0session` command-session backdoor. Browser Bridge commands now route only through structured `session` + `surface` fields; lease-key strings remain an extension-internal registry detail.
14+
15+
### Internal
16+
17+
* **extension 1.0.13** — remove the internal command-session lease-key backdoor.
1318

1419
## [1.7.18](https://github.com/jackwener/opencli/compare/v1.7.17...v1.7.18) (2026-05-12)
1520

extension/dist/background.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -785,14 +785,11 @@ function getSessionName(session) {
785785
if (!raw) throw new CommandFailure(
786786
"session_required",
787787
"Browser session is required.",
788-
"Pass --session <name> with opencli browser commands."
788+
"Pass a browser session name, e.g. opencli browser <session> <command>."
789789
);
790-
return raw.includes(LEASE_KEY_SEPARATOR) ? getSessionFromKey(raw) : raw;
790+
return raw;
791791
}
792792
function getCommandSurface(cmd) {
793-
if (typeof cmd.session === "string" && cmd.session.includes(LEASE_KEY_SEPARATOR)) {
794-
return getSurfaceFromKey(cmd.session);
795-
}
796793
return cmd.surface === "adapter" ? "adapter" : "browser";
797794
}
798795
function getSurfaceFromKey(key) {

extension/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "OpenCLI",
4-
"version": "1.0.12",
4+
"version": "1.0.13",
55
"description": "Browser automation bridge for the OpenCLI CLI tool. Executes commands in Chrome tab leases via a local daemon.",
66
"permissions": [
77
"debugger",

extension/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencli-extension",
3-
"version": "1.0.12",
3+
"version": "1.0.13",
44
"private": true,
55
"opencli": {
66
"compatRange": ">=1.7.0"

extension/src/background.test.ts

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,69 @@ describe('background tab isolation', () => {
269269
]);
270270
});
271271

272+
it('does not parse lease-key separators from command session fields', async () => {
273+
const { chrome } = createChromeMock();
274+
vi.stubGlobal('chrome', chrome);
275+
276+
const mod = await import('./background');
277+
278+
expect(mod.__test__.getSessionName(adapterKey('twitter'))).toBe(adapterKey('twitter'));
279+
expect(mod.__test__.getCommandSurface({ session: adapterKey('twitter') })).toBe('browser');
280+
expect(mod.__test__.getCommandSurface({ session: browserKey('work'), surface: 'adapter' })).toBe('adapter');
281+
});
282+
283+
it('routes structured command session and surface fields without encoded lease keys', async () => {
284+
const { chrome } = createChromeMock();
285+
chrome.debugger.sendCommand = vi.fn(async () => ({}));
286+
vi.stubGlobal('chrome', chrome);
287+
288+
const mod = await import('./background');
289+
mod.__test__.setAutomationWindowId(adapterKey('twitter'), 1);
290+
291+
const result = await mod.__test__.handleCommand({
292+
id: 'structured-cdp',
293+
action: 'cdp',
294+
session: 'twitter',
295+
surface: 'adapter',
296+
cdpMethod: 'Accessibility.enable',
297+
cdpParams: {},
298+
});
299+
300+
expect(result).toEqual(expect.objectContaining({ ok: true }));
301+
expect(chrome.debugger.sendCommand).toHaveBeenCalledWith(
302+
{ tabId: 1 },
303+
'Accessibility.enable',
304+
{},
305+
);
306+
});
307+
308+
it('does not route encoded adapter lease keys through the command session backdoor', async () => {
309+
const { chrome } = createChromeMock();
310+
chrome.debugger.sendCommand = vi.fn(async () => ({}));
311+
vi.stubGlobal('chrome', chrome);
312+
313+
const mod = await import('./background');
314+
mod.__test__.setAutomationWindowId(adapterKey('twitter'), 1);
315+
316+
const result = await mod.__test__.handleCommand({
317+
id: 'encoded-session',
318+
action: 'cdp',
319+
session: adapterKey('twitter'),
320+
cdpMethod: 'Accessibility.enable',
321+
cdpParams: {},
322+
});
323+
324+
expect(result).toEqual(expect.objectContaining({ ok: true }));
325+
expect(mod.__test__.getSession(adapterKey('twitter'))).toEqual(expect.objectContaining({
326+
surface: 'adapter',
327+
session: 'twitter',
328+
}));
329+
expect(mod.__test__.getSession(browserKey(adapterKey('twitter')))).toEqual(expect.objectContaining({
330+
surface: 'browser',
331+
session: adapterKey('twitter'),
332+
}));
333+
});
334+
272335
it('allows Accessibility.enable through the guarded CDP passthrough', async () => {
273336
const { chrome } = createChromeMock();
274337
chrome.debugger.sendCommand = vi.fn(async () => ({}));
@@ -943,7 +1006,8 @@ describe('background tab isolation', () => {
9431006
id: 'new-foreground',
9441007
action: 'tabs',
9451008
op: 'new',
946-
session: adapterKey('twitter'),
1009+
session: 'twitter',
1010+
surface: 'adapter',
9471011
url: 'https://x.com',
9481012
windowMode: 'foreground',
9491013
});
@@ -1259,7 +1323,8 @@ describe('background tab isolation', () => {
12591323
const result = await mod.__test__.handleCommand({
12601324
id: 'close-1',
12611325
action: 'close-window',
1262-
session: browserKey('default'),
1326+
session: 'default',
1327+
surface: 'browser',
12631328
});
12641329

12651330
expect(result.ok).toBe(true);
@@ -1280,7 +1345,8 @@ describe('background tab isolation', () => {
12801345
await mod.__test__.handleCommand({
12811346
id: 'custom-1',
12821347
action: 'cookies',
1283-
session: browserKey('default'),
1348+
session: 'default',
1349+
surface: 'browser',
12841350
domain: 'example.com',
12851351
idleTimeout: 120,
12861352
});
@@ -1409,7 +1475,8 @@ describe('background tab isolation', () => {
14091475
const result = await mod.__test__.handleCommand({
14101476
id: 'bound-close',
14111477
action: 'close-window',
1412-
session: browserKey('default'),
1478+
session: 'default',
1479+
surface: 'browser',
14131480
});
14141481

14151482
expect(result).toEqual(expect.objectContaining({ ok: true }));
@@ -1443,7 +1510,8 @@ describe('background tab isolation', () => {
14431510
const result = await mod.__test__.handleCommand({
14441511
id: 'bound-exec-gone',
14451512
action: 'exec',
1446-
session: browserKey('default'),
1513+
session: 'default',
1514+
surface: 'browser',
14471515
code: 'document.title',
14481516
});
14491517

@@ -1465,7 +1533,8 @@ describe('background tab isolation', () => {
14651533
const result = await mod.__test__.handleCommand({
14661534
id: 'bound-exec-undebuggable',
14671535
action: 'exec',
1468-
session: browserKey('default'),
1536+
session: 'default',
1537+
surface: 'browser',
14691538
code: 'document.title',
14701539
});
14711540

@@ -1492,13 +1561,15 @@ describe('background tab isolation', () => {
14921561
const nav = await mod.__test__.handleCommand({
14931562
id: 'bound-nav',
14941563
action: 'navigate',
1495-
session: browserKey('default'),
1564+
session: 'default',
1565+
surface: 'browser',
14961566
url: 'https://other.example',
14971567
});
14981568
const tabNew = await mod.__test__.handleCommand({
14991569
id: 'bound-tab-new',
15001570
action: 'tabs',
1501-
session: browserKey('default'),
1571+
session: 'default',
1572+
surface: 'browser',
15021573
op: 'new',
15031574
url: 'https://other.example',
15041575
});

extension/src/background.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,15 +249,12 @@ function getSessionName(session?: string): string {
249249
if (!raw) throw new CommandFailure(
250250
'session_required',
251251
'Browser session is required.',
252-
'Pass --session <name> with opencli browser commands.',
252+
'Pass a browser session name, e.g. opencli browser <session> <command>.',
253253
);
254-
return raw.includes(LEASE_KEY_SEPARATOR) ? getSessionFromKey(raw) : raw;
254+
return raw;
255255
}
256256

257257
function getCommandSurface(cmd: Pick<Command, 'surface' | 'session'>): BrowserSurface {
258-
if (typeof cmd.session === 'string' && cmd.session.includes(LEASE_KEY_SEPARATOR)) {
259-
return getSurfaceFromKey(cmd.session);
260-
}
261258
return cmd.surface === 'adapter' ? 'adapter' : 'browser';
262259
}
263260

@@ -1715,6 +1712,8 @@ export const __test__ = {
17151712
resolveTabId,
17161713
resetWindowIdleTimer,
17171714
handleCommand,
1715+
getSessionName,
1716+
getCommandSurface,
17181717
getIdleTimeout,
17191718
getLeaseKey,
17201719
sessionTimeoutOverrides,

0 commit comments

Comments
 (0)