Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ _In development — bullets added per PR; finalized at release._
### 🔒 Security

- **fix(mitm): exact host membership in the MITM hosts test (CodeQL false positive)** — `tests/unit/mitm-tool-hosts.test.ts` checked host membership with `Array.includes(host)`, which CodeQL's `js/incomplete-url-substring-sanitization` heuristic misreads as a `String.includes()` URL-substring sanitization test (HIGH false positive). Switched to `.some((h) => h === host)` — identical semantics, no flagged pattern. ([#4386](https://github.com/diegosouzapw/OmniRoute/pull/4386))
- **fix(api): migrate the deprecated Codex `[features].codex_hooks` flag to `[features].hooks`** — Codex renamed the `codex_hooks` feature flag to `hooks`; recent Codex CLI versions ignore the old key and print a deprecation notice. When OmniRoute rewrites an existing `~/.codex/config.toml` (configuring/resetting the Codex provider) it now carries the user's intent forward by renaming `[features].codex_hooks` → `[features].hooks` (preserving its value, never clobbering an already-present `hooks`) and dropping the deprecated key. No-op when the flag is absent. (thanks @Bian-Sh)

---

Expand Down
7 changes: 7 additions & 0 deletions src/app/api/cli-tools/codex-settings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { cliModelConfigSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { getApiKeyById } from "@/lib/localDb";
import { normalizeCodexBaseUrl } from "@/shared/utils/codexBaseUrl";
import { migrateCodexFeatureFlags } from "@/shared/utils/codexConfig";

const getCodexConfigPath = () => getCliConfigPaths("codex").config;
const getCodexAuthPath = () => getCliConfigPaths("codex").auth;
Expand Down Expand Up @@ -252,6 +253,9 @@ export async function POST(request: Request) {
/* No existing config */
}

// Carry the user's intent forward off the deprecated Codex feature flag (#1327).
migrateCodexFeatureFlags(parsed);

// Update only OmniRoute related fields (api_key goes to auth.json, not config.toml)
parsed._root.model = model;

Expand Down Expand Up @@ -351,6 +355,9 @@ export async function DELETE(request: Request) {
throw error;
}

// Carry the user's intent forward off the deprecated Codex feature flag (#1327).
migrateCodexFeatureFlags(parsed);

// Remove OmniRoute related root fields
delete parsed._root.openai_base_url;

Expand Down
30 changes: 30 additions & 0 deletions src/shared/utils/codexConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Helpers for generating/maintaining the Codex CLI `config.toml`.
*/

interface ParsedCodexToml {
_root: Record<string, unknown>;
_sections: Record<string, Record<string, unknown>>;
}

/**
* Migrate the deprecated Codex `[features].codex_hooks` flag to `[features].hooks`.
*
* Codex renamed the feature flag; recent Codex CLI versions ignore the old key and
* print a deprecation notice. When OmniRoute rewrites an existing `config.toml` it
* should carry the user's intent forward by renaming the key (preserving its value)
* and dropping the deprecated one. A no-op when `[features]` or `codex_hooks` is
* absent, and it never clobbers an already-present `hooks` value. (#1327)
*
* Operates in place on the route's parsed-TOML shape (`{ _root, _sections }`).
*/
export function migrateCodexFeatureFlags(parsed: ParsedCodexToml): ParsedCodexToml {
const features = parsed?._sections?.features;
if (!features || typeof features !== "object") return parsed;
if (!Object.prototype.hasOwnProperty.call(features, "codex_hooks")) return parsed;
if (!Object.prototype.hasOwnProperty.call(features, "hooks")) {
features.hooks = features.codex_hooks;
}
delete features.codex_hooks;
return parsed;
}
32 changes: 32 additions & 0 deletions tests/unit/codex-config-hooks-migration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import test from "node:test";
import assert from "node:assert/strict";

// Regression for port-from-9router#1327: Codex deprecated the `[features].codex_hooks`
// flag in favor of `[features].hooks`. The codex-settings generator parses an existing
// config.toml and writes it back but never migrated the deprecated key, so users with an
// old config kept a key recent Codex CLI versions ignore.
const { migrateCodexFeatureFlags } = await import("../../src/shared/utils/codexConfig.ts");

test("#1327: renames deprecated [features].codex_hooks to [features].hooks", () => {
const parsed = { _root: {}, _sections: { features: { codex_hooks: true } } };
migrateCodexFeatureFlags(parsed);
assert.deepEqual(parsed._sections.features, { hooks: true });
});

test("#1327: keeps an existing hooks value and removes the deprecated key", () => {
const parsed = { _root: {}, _sections: { features: { codex_hooks: true, hooks: false } } };
migrateCodexFeatureFlags(parsed);
assert.deepEqual(parsed._sections.features, { hooks: false });
});

test("#1327: leaves a config that already uses hooks untouched", () => {
const parsed = { _root: {}, _sections: { features: { hooks: true } } };
migrateCodexFeatureFlags(parsed);
assert.deepEqual(parsed._sections.features, { hooks: true });
});

test("#1327: no-op when there is no [features] section", () => {
const parsed = { _root: { model: "x" }, _sections: {} };
migrateCodexFeatureFlags(parsed);
assert.deepEqual(parsed._sections, {});
});
Loading