diff --git a/server/__tests__/codex-cli.test.js b/server/__tests__/codex-cli.test.js index 77e8448e..2f60de0d 100644 --- a/server/__tests__/codex-cli.test.js +++ b/server/__tests__/codex-cli.test.js @@ -189,6 +189,20 @@ describe("codex-cli argv construction", () => { } }); + it("accepts an env value starting with '-' — it's the RHS of one KEY=VALUE argv element, never a flag", async () => { + const restore = stubCodex(); + try { + const result = await cli.mcpAdd({ + name: "docs", + command: "node", + env: { OFFSET: "-1" }, + }); + assert.deepEqual(argvOf(result), ["mcp", "add", "docs", "--env", "OFFSET=-1", "--", "node"]); + } finally { + restore(); + } + }); + it("builds mcp list / remove argvs", async () => { const restore = stubCodex(); try { diff --git a/server/lib/codex-cli.js b/server/lib/codex-cli.js index 7236e3bc..49e916a0 100644 --- a/server/lib/codex-cli.js +++ b/server/lib/codex-cli.js @@ -94,6 +94,24 @@ function assertEnvKey(value, label) { return value; } +/** + * An environment variable VALUE. Unlike `assertArg`, a leading "-" is fine: + * the value is the right-hand side of one `KEY=VALUE` argv element (built + * by envFlags below), never its own argv position, so it can never be + * parsed as a flag. Rejecting it anyway (as `assertArg` would) refused + * ordinary values like "-1" or a base64 string that happens to start with + * "-" for no real reason. + */ +function assertEnvValue(value, label) { + if (typeof value !== "string") throw makeError("EBADARG", `${label} is required`); + if (value.length > 512) throw makeError("EBADARG", `${label} is too long`); + // eslint-disable-next-line no-control-regex + if (/[\u0000-\u001f\u007f]/.test(value)) { + throw makeError("EBADARG", `${label} contains control characters`); + } + return value; +} + /** An http(s) URL — `new URL()` both validates and normalizes the parse. */ function assertUrl(value, label) { assertArg(value, label); @@ -219,7 +237,7 @@ async function mcpAdd({ name, url, bearerTokenEnvVar, command, args = [], env }) const cmdArgs = args.map((a, i) => assertArg(a, `args[${i}]`)); const envFlags = Object.entries(env || {}).flatMap(([k, v]) => { const key = assertEnvKey(k, "env key"); - assertArg(String(v), `env.${k}`); + assertEnvValue(String(v), `env.${k}`); return ["--env", `${key}=${v}`]; }); return serialize(() => runCodex(["mcp", "add", n, ...envFlags, "--", cmd, ...cmdArgs]));