diff --git a/CHANGELOG.md b/CHANGELOG.md index ff50bf055..cdc6f7cbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ * **browser** — replace the `--session ` flag with a `` 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. * **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. +* **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. + +### Internal + +* **extension 1.0.13** — remove the internal command-session lease-key backdoor. ## [1.7.18](https://github.com/jackwener/opencli/compare/v1.7.17...v1.7.18) (2026-05-12) diff --git a/extension/dist/background.js b/extension/dist/background.js index 15293f523..c6b41288f 100644 --- a/extension/dist/background.js +++ b/extension/dist/background.js @@ -785,14 +785,11 @@ function getSessionName(session) { if (!raw) throw new CommandFailure( "session_required", "Browser session is required.", - "Pass --session with opencli browser commands." + "Pass a browser session name, e.g. opencli browser ." ); - return raw.includes(LEASE_KEY_SEPARATOR) ? getSessionFromKey(raw) : raw; + return raw; } function getCommandSurface(cmd) { - if (typeof cmd.session === "string" && cmd.session.includes(LEASE_KEY_SEPARATOR)) { - return getSurfaceFromKey(cmd.session); - } return cmd.surface === "adapter" ? "adapter" : "browser"; } function getSurfaceFromKey(key) { diff --git a/extension/manifest.json b/extension/manifest.json index 2136e5559..af558d00c 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "OpenCLI", - "version": "1.0.12", + "version": "1.0.13", "description": "Browser automation bridge for the OpenCLI CLI tool. Executes commands in Chrome tab leases via a local daemon.", "permissions": [ "debugger", diff --git a/extension/package-lock.json b/extension/package-lock.json index 477d0fc90..ce0a8409b 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "opencli-extension", - "version": "1.0.11", + "version": "1.0.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "opencli-extension", - "version": "1.0.11", + "version": "1.0.13", "devDependencies": { "@types/chrome": "^0.0.287", "typescript": "^5.7.0", diff --git a/extension/package.json b/extension/package.json index da65b4166..9dbe732f6 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "opencli-extension", - "version": "1.0.12", + "version": "1.0.13", "private": true, "opencli": { "compatRange": ">=1.7.0" diff --git a/extension/src/background.test.ts b/extension/src/background.test.ts index 9a542e19c..155b69e78 100644 --- a/extension/src/background.test.ts +++ b/extension/src/background.test.ts @@ -269,6 +269,69 @@ describe('background tab isolation', () => { ]); }); + it('does not parse lease-key separators from command session fields', async () => { + const { chrome } = createChromeMock(); + vi.stubGlobal('chrome', chrome); + + const mod = await import('./background'); + + expect(mod.__test__.getSessionName(adapterKey('twitter'))).toBe(adapterKey('twitter')); + expect(mod.__test__.getCommandSurface({ session: adapterKey('twitter') })).toBe('browser'); + expect(mod.__test__.getCommandSurface({ session: browserKey('work'), surface: 'adapter' })).toBe('adapter'); + }); + + it('routes structured command session and surface fields without encoded lease keys', async () => { + const { chrome } = createChromeMock(); + chrome.debugger.sendCommand = vi.fn(async () => ({})); + vi.stubGlobal('chrome', chrome); + + const mod = await import('./background'); + mod.__test__.setAutomationWindowId(adapterKey('twitter'), 1); + + const result = await mod.__test__.handleCommand({ + id: 'structured-cdp', + action: 'cdp', + session: 'twitter', + surface: 'adapter', + cdpMethod: 'Accessibility.enable', + cdpParams: {}, + }); + + expect(result).toEqual(expect.objectContaining({ ok: true })); + expect(chrome.debugger.sendCommand).toHaveBeenCalledWith( + { tabId: 1 }, + 'Accessibility.enable', + {}, + ); + }); + + it('does not route encoded adapter lease keys through the command session backdoor', async () => { + const { chrome } = createChromeMock(); + chrome.debugger.sendCommand = vi.fn(async () => ({})); + vi.stubGlobal('chrome', chrome); + + const mod = await import('./background'); + mod.__test__.setAutomationWindowId(adapterKey('twitter'), 1); + + const result = await mod.__test__.handleCommand({ + id: 'encoded-session', + action: 'cdp', + session: adapterKey('twitter'), + cdpMethod: 'Accessibility.enable', + cdpParams: {}, + }); + + expect(result).toEqual(expect.objectContaining({ ok: true })); + expect(mod.__test__.getSession(adapterKey('twitter'))).toEqual(expect.objectContaining({ + surface: 'adapter', + session: 'twitter', + })); + expect(mod.__test__.getSession(browserKey(adapterKey('twitter')))).toEqual(expect.objectContaining({ + surface: 'browser', + session: adapterKey('twitter'), + })); + }); + it('allows Accessibility.enable through the guarded CDP passthrough', async () => { const { chrome } = createChromeMock(); chrome.debugger.sendCommand = vi.fn(async () => ({})); @@ -943,7 +1006,8 @@ describe('background tab isolation', () => { id: 'new-foreground', action: 'tabs', op: 'new', - session: adapterKey('twitter'), + session: 'twitter', + surface: 'adapter', url: 'https://x.com', windowMode: 'foreground', }); @@ -1259,7 +1323,8 @@ describe('background tab isolation', () => { const result = await mod.__test__.handleCommand({ id: 'close-1', action: 'close-window', - session: browserKey('default'), + session: 'default', + surface: 'browser', }); expect(result.ok).toBe(true); @@ -1280,7 +1345,8 @@ describe('background tab isolation', () => { await mod.__test__.handleCommand({ id: 'custom-1', action: 'cookies', - session: browserKey('default'), + session: 'default', + surface: 'browser', domain: 'example.com', idleTimeout: 120, }); @@ -1409,7 +1475,8 @@ describe('background tab isolation', () => { const result = await mod.__test__.handleCommand({ id: 'bound-close', action: 'close-window', - session: browserKey('default'), + session: 'default', + surface: 'browser', }); expect(result).toEqual(expect.objectContaining({ ok: true })); @@ -1443,7 +1510,8 @@ describe('background tab isolation', () => { const result = await mod.__test__.handleCommand({ id: 'bound-exec-gone', action: 'exec', - session: browserKey('default'), + session: 'default', + surface: 'browser', code: 'document.title', }); @@ -1465,7 +1533,8 @@ describe('background tab isolation', () => { const result = await mod.__test__.handleCommand({ id: 'bound-exec-undebuggable', action: 'exec', - session: browserKey('default'), + session: 'default', + surface: 'browser', code: 'document.title', }); @@ -1492,13 +1561,15 @@ describe('background tab isolation', () => { const nav = await mod.__test__.handleCommand({ id: 'bound-nav', action: 'navigate', - session: browserKey('default'), + session: 'default', + surface: 'browser', url: 'https://other.example', }); const tabNew = await mod.__test__.handleCommand({ id: 'bound-tab-new', action: 'tabs', - session: browserKey('default'), + session: 'default', + surface: 'browser', op: 'new', url: 'https://other.example', }); diff --git a/extension/src/background.ts b/extension/src/background.ts index abdf0ed45..7971bc121 100644 --- a/extension/src/background.ts +++ b/extension/src/background.ts @@ -249,15 +249,12 @@ function getSessionName(session?: string): string { if (!raw) throw new CommandFailure( 'session_required', 'Browser session is required.', - 'Pass --session with opencli browser commands.', + 'Pass a browser session name, e.g. opencli browser .', ); - return raw.includes(LEASE_KEY_SEPARATOR) ? getSessionFromKey(raw) : raw; + return raw; } function getCommandSurface(cmd: Pick): BrowserSurface { - if (typeof cmd.session === 'string' && cmd.session.includes(LEASE_KEY_SEPARATOR)) { - return getSurfaceFromKey(cmd.session); - } return cmd.surface === 'adapter' ? 'adapter' : 'browser'; } @@ -1715,6 +1712,8 @@ export const __test__ = { resolveTabId, resetWindowIdleTimer, handleCommand, + getSessionName, + getCommandSurface, getIdleTimeout, getLeaseKey, sessionTimeoutOverrides,