Files
OmniRoute/tests/unit/codex-client-headers.test.ts
Zeeshan Haque fa23670ea2 fix(codex): forward the caller client version upstream instead of a pinned default (#13708)
* fix(codex): forward the caller client version upstream instead of pinning 0.144.1

The Codex provider reported a hardcoded client version (DEFAULT_CODEX_CLIENT_VERSION)
to the ChatGPT backend. Newer gated models reject older clients, e.g.:

  The 'gpt-6-astra' model requires a newer version of Codex.

so the pinned value silently rots on every CLI upgrade, and a user on the latest
CLI is still refused.

CodexExecutor.buildHeaders also dropped the clientHeaders/model/health arguments
that the base class accepts (base.ts:509), so the caller User-Agent never reached
the version resolution.

- codexClient.ts: add getCodexClientVersionFromHeaders(), which reads the version
  the caller reports in its User-Agent (codex_cli_rs/<v>, codex_exec/<v>) or a
  version header, validated against SAFE_HEADER_TOKEN_PATTERN. getCodexUserAgent()
  now takes an optional version override.
- codex.ts: forward clientHeaders/model/health to super.buildHeaders(), and use
  getCodexClientVersionFromHeaders(clientHeaders) ?? getCodexClientVersion().

Falls back to the existing env override / default when the caller sends nothing.

* test(codex): cover getCodexClientVersionFromHeaders and clientHeaders-aware buildHeaders

Adds unit coverage for the caller-version forwarding introduced in this PR:
real Codex CLI User-Agent parsing, the generic version header, missing/empty
headers, non-Codex User-Agent, and CRLF/oversized injection attempts safely
returning null/falling back to the default version.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Zeeshan Haque <zeeshan@moonscape.local>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-19 00:04:17 -03:00

114 lines
4.4 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { getCodexClientVersionFromHeaders } from "../../open-sse/config/codexClient.ts";
import { CodexExecutor } from "../../open-sse/executors/codex.ts";
test("getCodexClientVersionFromHeaders: extracts the version from a real Codex CLI User-Agent", () => {
assert.equal(
getCodexClientVersionFromHeaders({
"user-agent": "codex_cli_rs/0.154.0 (Mac OS 26.6.2; arm64)",
}),
"0.154.0"
);
assert.equal(
getCodexClientVersionFromHeaders({
"user-agent":
"codex_exec/0.154.0 (Mac OS 26.6.2; arm64) xterm-256color (codex_exec; 0.154.0)",
}),
"0.154.0"
);
});
test("getCodexClientVersionFromHeaders: prefers a valid generic version header over User-Agent", () => {
assert.equal(
getCodexClientVersionFromHeaders({
version: "9.9.9",
"user-agent": "codex_cli_rs/0.154.0 (Mac OS 26.6.2; arm64)",
}),
"9.9.9"
);
});
test("getCodexClientVersionFromHeaders: returns null when headers are absent or empty", () => {
assert.equal(getCodexClientVersionFromHeaders(null), null);
assert.equal(getCodexClientVersionFromHeaders(undefined), null);
assert.equal(getCodexClientVersionFromHeaders({}), null);
});
test("getCodexClientVersionFromHeaders: returns null for a non-Codex User-Agent with no version header", () => {
assert.equal(getCodexClientVersionFromHeaders({ "user-agent": "curl/8.4.0" }), null);
});
test("getCodexClientVersionFromHeaders: rejects a CRLF injection attempt in the version header", () => {
assert.equal(getCodexClientVersionFromHeaders({ version: "1.0.0\r\nX-Injected: evil" }), null);
});
test("getCodexClientVersionFromHeaders: rejects a version header longer than the 32-char safe token limit", () => {
const overlong = "1.0.0-" + "a".repeat(30);
assert.ok(overlong.length > 32);
assert.equal(getCodexClientVersionFromHeaders({ version: overlong }), null);
});
test("getCodexClientVersionFromHeaders: a CRLF/oversized User-Agent injection only ever yields the captured digits", () => {
assert.equal(
getCodexClientVersionFromHeaders({
"user-agent": "codex_cli_rs/1.0.0\r\nX-Evil: 1",
}),
"1.0.0"
);
});
test("CodexExecutor.buildHeaders forwards the caller's Codex client version from clientHeaders", () => {
const executor = new CodexExecutor();
const fromUserAgent = executor.buildHeaders({ accessToken: "codex-token" }, true, {
"user-agent": "codex_cli_rs/0.160.2 (Mac OS 26.6.2; arm64)",
});
assert.equal(fromUserAgent.Version, "0.160.2");
assert.equal(fromUserAgent["User-Agent"], "codex-cli/0.160.2 (Windows 10.0.26200; x64)");
const fromVersionHeader = executor.buildHeaders({ accessToken: "codex-token" }, true, {
version: "9.9.9",
});
assert.equal(fromVersionHeader.Version, "9.9.9");
assert.equal(fromVersionHeader["User-Agent"], "codex-cli/9.9.9 (Windows 10.0.26200; x64)");
});
test("CodexExecutor.buildHeaders falls back to the default client version when clientHeaders is absent, empty, or unusable", () => {
const executor = new CodexExecutor();
const noHeaders = executor.buildHeaders({ accessToken: "codex-token" }, true);
assert.equal(noHeaders.Version, "0.153.4");
const emptyHeaders = executor.buildHeaders({ accessToken: "codex-token" }, true, {});
assert.equal(emptyHeaders.Version, "0.153.4");
const nonCodexUserAgent = executor.buildHeaders({ accessToken: "codex-token" }, true, {
"user-agent": "curl/8.4.0",
});
assert.equal(nonCodexUserAgent.Version, "0.153.4");
});
test("CodexExecutor.buildHeaders rejects injection attempts in the caller's version/User-Agent headers", () => {
const executor = new CodexExecutor();
const crlfVersion = executor.buildHeaders({ accessToken: "codex-token" }, true, {
version: "1.0.0\r\nX-Injected: evil",
});
assert.equal(crlfVersion.Version, "0.153.4");
assert.equal(crlfVersion["User-Agent"].includes("\r\n"), false);
const overlongVersion = executor.buildHeaders({ accessToken: "codex-token" }, true, {
version: "1.0.0-" + "a".repeat(30),
});
assert.equal(overlongVersion.Version, "0.153.4");
const injectedUserAgent = executor.buildHeaders({ accessToken: "codex-token" }, true, {
"user-agent": "codex_cli_rs/1.0.0\r\nX-Evil: 1",
});
assert.equal(injectedUserAgent.Version, "1.0.0");
assert.equal(injectedUserAgent["User-Agent"].includes("\r\n"), false);
assert.equal(injectedUserAgent["User-Agent"], "codex-cli/1.0.0 (Windows 10.0.26200; x64)");
});