fix(daemon): reject unknown startup commands (#3934)

* fix(daemon): reject unknown startup commands

* fix: make agent browser cleanup mac compatible

* fix: reject flag-shaped startup option values

---------

Co-authored-by: audit <a@b.c>
This commit is contained in:
lefarcen
2026-06-08 15:44:38 +00:00
committed by GitHub
co-authored by audit
parent 96915fd05c
commit c47312b206
4 changed files with 142 additions and 16 deletions
+63 -15
View File
@@ -17,6 +17,61 @@ type DaemonRuntimeOptions = Omit<StartServerOptions, 'returnServer'> & {
logListening?: boolean;
};
export type DaemonCliStartupConfig = {
host: string;
open: boolean;
port: number;
};
export type DaemonCliStartupParseResult =
| { ok: true; config: DaemonCliStartupConfig }
| { ok: false; kind: 'help' }
| { ok: false; kind: 'error'; message: string };
function requiredOptionValue(flag: string, value: string | undefined, label: string): string | DaemonCliStartupParseResult {
if (value == null || value.startsWith('-')) {
return { ok: false, kind: 'error', message: `${flag} requires ${label}` };
}
return value;
}
export function parseDaemonCliStartupArgs(
argv: string[],
env: NodeJS.ProcessEnv = process.env,
): DaemonCliStartupParseResult {
let port = Number(env.OD_PORT) || 7456;
let host = env.OD_BIND_HOST || '127.0.0.1';
let open = true;
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a == null) continue;
if (a === '-p' || a === '--port') {
const next = requiredOptionValue(a, argv[++i], 'a port');
if (typeof next !== 'string') return next;
const parsedPort = Number(next);
if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
return { ok: false, kind: 'error', message: `invalid port: ${next}` };
}
port = parsedPort;
} else if (a === '--host') {
const next = requiredOptionValue(a, argv[++i], 'an address');
if (typeof next !== 'string') return next;
host = next;
} else if (a === '--no-open') {
open = false;
} else if (a === '-h' || a === '--help') {
return { ok: false, kind: 'help' };
} else if (a.startsWith('-')) {
return { ok: false, kind: 'error', message: `unknown option: ${a}` };
} else {
return { ok: false, kind: 'error', message: `unknown command: od ${a}` };
}
}
return { ok: true, config: { host, open, port } };
}
export async function closeHttpServer(
server: Server,
{ closeTimeoutMs = 5_000, idleCloseMs = 1_000 } = {},
@@ -87,24 +142,17 @@ export async function startDaemonRuntime(options: DaemonRuntimeOptions = {}): Pr
}
export async function runDaemonCliStartup(argv: string[], options: { printHelp?: () => void } = {}): Promise<void> {
let port = Number(process.env.OD_PORT) || 7456;
let host = process.env.OD_BIND_HOST || '127.0.0.1';
let open = true;
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === '-p' || a === '--port') {
port = Number(argv[++i]);
} else if (a === '--host') {
const next = argv[++i];
if (next != null) host = next;
} else if (a === '--no-open') {
open = false;
} else if (a === '-h' || a === '--help') {
const parsed = parseDaemonCliStartupArgs(argv);
if (!parsed.ok) {
if (parsed.kind === 'error') {
console.error(parsed.message);
options.printHelp?.();
return;
process.exit(2);
}
options.printHelp?.();
return;
}
const { host, open, port } = parsed.config;
const runtime = await startDaemonRuntime({
host,
+56
View File
@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest';
import { parseDaemonCliStartupArgs } from '../src/daemon-startup.js';
describe('daemon startup CLI parsing', () => {
it('parses the documented daemon startup flags', () => {
expect(parseDaemonCliStartupArgs(['--host', '0.0.0.0', '--port', '8123', '--no-open'], {})).toEqual({
ok: true,
config: {
host: '0.0.0.0',
open: false,
port: 8123,
},
});
});
it('uses environment defaults when startup flags are omitted', () => {
expect(parseDaemonCliStartupArgs([], { OD_BIND_HOST: '127.0.0.2', OD_PORT: '7345' })).toEqual({
ok: true,
config: {
host: '127.0.0.2',
open: true,
port: 7345,
},
});
});
it('rejects browser snapshot instead of treating it as daemon startup', () => {
expect(parseDaemonCliStartupArgs(['browser', 'snapshot', '--url', 'https://example.test/'], {})).toEqual({
ok: false,
kind: 'error',
message: 'unknown command: od browser',
});
});
it('rejects unknown daemon startup options', () => {
expect(parseDaemonCliStartupArgs(['--url', 'https://example.test/'], {})).toEqual({
ok: false,
kind: 'error',
message: 'unknown option: --url',
});
});
it('rejects flag-shaped values for required daemon startup options', () => {
expect(parseDaemonCliStartupArgs(['--host', '--no-open'], {})).toEqual({
ok: false,
kind: 'error',
message: '--host requires an address',
});
expect(parseDaemonCliStartupArgs(['--port', '--no-open'], {})).toEqual({
ok: false,
kind: 'error',
message: '--port requires a port',
});
});
});
+4
View File
@@ -176,6 +176,10 @@ describe('listSkills', () => {
expect(skill.body).toContain('Chrome crashed before CDP became available');
expect(skill.body).toContain('command -v agent-browser');
expect(skill.body).toContain('Open Design Smoke Path');
expect(skill.body).toContain('`daemon-cli.mjs browser snapshot`');
expect(skill.body).toContain('misinterpreted as daemon startup');
expect(skill.body).toContain('trap cleanup_agent_browser EXIT INT TERM');
expect(skill.body).toContain('pkill -f -- "--user-data-dir=${CHROME_USER_DATA_DIR}"');
});
it('includes the DCF valuation, X research, and Last30Days research skills', async () => {
+19 -1
View File
@@ -115,6 +115,13 @@ screenshots.
`agent-browser open` before `agent-browser connect`; doing so can make the CLI
auto-launch Chrome and re-enter the crash path.
Do not run Open Design's own daemon CLI as a browser automation tool. Commands
such as `od browser snapshot`, `daemon-cli.mjs browser snapshot`, or
`$OD_NODE_BIN $OD_BIN browser snapshot` are not valid browser tools; they can be
misinterpreted as daemon startup and open an internal `127.0.0.1:<port>` service
in the system browser. Use the external `agent-browser` CLI attached to CDP
instead.
Use this sequence:
```bash
@@ -164,13 +171,24 @@ export HOME=/tmp/agent-browser-home
export AGENT_BROWSER_SESSION=od-local-preview
```
When you start a temporary Chrome profile for this smoke path, close it before
finishing the task. Prefer a shell trap around the whole smoke script:
```bash
CHROME_USER_DATA_DIR=/tmp/od-agent-browser-chrome
cleanup_agent_browser() {
pkill -f -- "--user-data-dir=${CHROME_USER_DATA_DIR}" 2>/dev/null || true
}
trap cleanup_agent_browser EXIT INT TERM
```
With the Open Design preview at `http://127.0.0.1:17573/`, run:
```bash
if ! curl -fsS http://127.0.0.1:9223/json/version | rg -q webSocketDebuggerUrl; then
open -na "Google Chrome" --args \
--remote-debugging-port=9223 \
--user-data-dir=/tmp/od-agent-browser-chrome \
--user-data-dir="$CHROME_USER_DATA_DIR" \
--no-first-run \
--no-default-browser-check