Skip to content

Commit 29c8b4f

Browse files
authored
release: v2.2.0 stable + post-audit hardening (#504)
Cut v2.2.0 stable with post-audit LOW fixes and pre-release security/resilience hardening. Includes danger>warning tone-precedence coverage for styleAccountDetailText (CodeRabbit-requested). Full suite: 4283 passed; typecheck + lint clean. CodeRabbit: APPROVED.
1 parent 1317fe9 commit 29c8b4f

14 files changed

Lines changed: 151 additions & 28 deletions

File tree

.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codex-multi-auth",
3-
"version": "2.1.13-beta.3",
3+
"version": "2.2.0",
44
"description": "Install and operate codex-multi-auth for the official @openai/codex CLI with multi-account OAuth rotation, switching, health checks, and recovery tools.",
55
"interface": {
66
"composerIcon": "./assets/codex-multi-auth-icon.svg"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ test-results.md
3232
# local env files
3333
.env
3434
.env.local
35+
36+
# OMC session scratch (never publish)
37+
.omc/

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Generated: 2026-04-25
44
Commit: a87e005
55
Branch: main
6-
Package version: 2.1.13-beta.3
6+
Package version: 2.2.0
77

88
## OVERVIEW
99

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,9 +383,9 @@ codex-multi-auth doctor --json
383383

384384
## Release Notes
385385

386-
- Current prerelease: [docs/releases/v2.1.13-beta.3.md](docs/releases/v2.1.13-beta.3.md) — install via `npm i -g codex-multi-auth@beta`
387-
- Current stable: [docs/releases/v2.1.12.md](docs/releases/v2.1.12.md)
388-
- Previous stable: [docs/releases/v2.1.11.md](docs/releases/v2.1.11.md)
386+
- Current stable: [docs/releases/v2.2.0.md](docs/releases/v2.2.0.md) — install via `npm i -g codex-multi-auth`
387+
- Previous stable: [docs/releases/v2.1.12.md](docs/releases/v2.1.12.md)
388+
- Earlier stable: [docs/releases/v2.1.11.md](docs/releases/v2.1.11.md)
389389
- Earlier stable: [docs/releases/v2.1.10.md](docs/releases/v2.1.10.md)
390390
- Earlier stable: [docs/releases/v2.1.8.md](docs/releases/v2.1.8.md)
391391
- Earlier stable: [docs/releases/v2.1.7.md](docs/releases/v2.1.7.md)

docs/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ Public documentation for the `codex-multi-auth` Codex CLI multi-account OAuth ma
3232

3333
| Document | Focus |
3434
| --- | --- |
35-
| [releases/v2.1.13-beta.3.md](releases/v2.1.13-beta.3.md) | Current prerelease notes (install via `npm i -g codex-multi-auth@beta`) |
36-
| [releases/v2.1.12.md](releases/v2.1.12.md) | Current stable release notes |
35+
| [releases/v2.2.0.md](releases/v2.2.0.md) | Current stable release notes (install via `npm i -g codex-multi-auth`) |
36+
| [releases/v2.1.12.md](releases/v2.1.12.md) | Prior stable release notes |
3737
| [releases/v2.1.11.md](releases/v2.1.11.md) | Prior stable release notes |
3838
| [releases/v2.1.10.md](releases/v2.1.10.md) | Earlier stable release notes |
3939
| [releases/v2.1.9.md](releases/v2.1.9.md) | Earlier stable release notes |
Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
# v2.1.13-beta.3
1+
# v2.2.0
22

3-
Beta prerelease that lands the Phase 1 correctness/security audit (#499), the new
4-
`mcodex` launcher + cached statusline (#500), and a round of pre-release
5-
hardening surfaced by a whole-tree stress test (#503). It carries forward the
6-
cascade OAuth token-invalidation fix from v2.1.13-beta.2, the multi-workspace
7-
support from v2.1.13-beta.1, and the pinned-account 503 diagnostic from
8-
v2.1.13-beta.0.
9-
10-
This is a **prerelease**. Stable `v2.1.13` will land once the issue #486 root
11-
cause is identified and patched.
3+
Stable release. Promotes the v2.1.13-beta line to stable and adds the `mcodex`
4+
launcher. It bundles the Phase 1 correctness/security audit (#499), the new
5+
`mcodex` launcher + cached statusline (#500), the quota unsupported-model
6+
detail-shape detection (#501/#502), and the hardening surfaced by a whole-tree
7+
stress test (#503). It also carries the cascade OAuth token-invalidation fix, the
8+
multi-workspace support, and the pinned-account 503 diagnostic that shipped
9+
across v2.1.13-beta.0–beta.3.
1210

1311
## Install
1412

1513
```bash
16-
npm i -g codex-multi-auth@beta
14+
npm i -g codex-multi-auth
1715
```
1816

1917
## mcodex launcher (#500)
@@ -94,11 +92,22 @@ Security and correctness hardening across the runtime, storage, and prompt layer
9492
- `codex-multi-auth status` / `list` gained `--json` for machine-readable output,
9593
with a stable shape whether or not accounts are configured.
9694

97-
## Pre-release hardening (#503)
95+
## Quota detection (#501/#502)
96+
97+
- Detect an unsupported Codex model from the upstream error `detail` shape (not
98+
just the nested `error` envelope), so a Codex-gated account surfaces a friendly
99+
"Codex unavailable" note across the `best` / `forecast` / `report` / live-check
100+
surfaces instead of leaking the raw upstream "model is not supported" text.
101+
- A genuine transient failure mixed into the probe still surfaces as the real
102+
error (never masked behind the friendly note), so a real outage is not hidden.
103+
104+
## Post-audit hardening (#503 + follow-ups)
98105

99106
- Strip inbound `cookie` / `proxy-authorization` on both egress paths.
100107
- Bound the proxy's upstream error-body read (previously unbounded on 4xx/5xx).
101108
- Persist `runtime-observability.json` owner-only (`0o600` / dir `0o700`) on POSIX.
109+
- Reject NUL-byte paths in `resolvePath` (defense in depth) and require a strict
110+
integer for `switch <index>` (no silent float truncation).
102111
- Bump `vitest` to `^4.1.8` (dev-only) to clear GHSA-5xrq-8626-4rwp.
103112

104113
## Verification

lib/codex-manager.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,10 @@ function styleQuotaSummary(summary: string): string {
403403
return joinStyledSegments(rendered);
404404
}
405405

406-
function styleAccountDetailText(
406+
// Exported for unit tests: tone precedence (danger before warning) is
407+
// security/UX-relevant — a failure whose text contains "unavailable" must
408+
// render red, not be downgraded to yellow. See test/codex-manager-detail-tone.test.ts.
409+
export function styleAccountDetailText(
407410
detail: string,
408411
fallbackTone: PromptTone = "muted",
409412
): string {
@@ -422,10 +425,13 @@ function styleAccountDetailText(
422425
? "success"
423426
: fallbackTone;
424427
const suffixTone: PromptTone =
425-
/re-login|stale|warning|retry|fallback|unavailable|not available/i.test(suffix)
426-
? "warning"
427-
: /failed|error/i.test(suffix)
428-
? "danger"
428+
// danger wins first: a real failure whose text happens to contain
429+
// "unavailable"/"not available" (e.g. a 5xx "service not available")
430+
// must render red, not be downgraded to a yellow warning.
431+
/failed|error/i.test(suffix)
432+
? "danger"
433+
: /re-login|stale|warning|retry|fallback|unavailable|not available/i.test(suffix)
434+
? "warning"
429435
: "muted";
430436

431437
const chunks: string[] = [];
@@ -436,9 +442,9 @@ function styleAccountDetailText(
436442
}
437443

438444
if (/rate-limited/i.test(compact)) return stylePromptText(compact, "danger");
445+
if (/failed|error/i.test(compact)) return stylePromptText(compact, "danger");
439446
if (/re-login|stale|warning|fallback|unavailable|not available/i.test(compact))
440447
return stylePromptText(compact, "warning");
441-
if (/failed|error/i.test(compact)) return stylePromptText(compact, "danger");
442448
if (/ok|working|succeeded|valid/i.test(compact))
443449
return stylePromptText(compact, "success");
444450
return stylePromptText(compact, fallbackTone);

lib/codex-manager/commands/switch.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ export async function runSwitchCommand(
3636
return 1;
3737
}
3838

39+
// Require a plain positive integer. Number.parseInt would silently truncate
40+
// "1.5" -> 1 (or "2abc" -> 2), selecting a real account from malformed input;
41+
// reject anything that isn't all digits so the index is unambiguous.
42+
if (!/^\d+$/.test(indexArg.trim())) {
43+
(deps.logError ?? console.error)(`Invalid index: ${indexArg}`);
44+
return 1;
45+
}
3946
const parsed = Number.parseInt(indexArg, 10);
4047
if (!Number.isFinite(parsed) || parsed < 1) {
4148
(deps.logError ?? console.error)(`Invalid index: ${indexArg}`);

lib/storage/paths.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,12 @@ function canonicalizeExistingPrefix(targetPath: string): string {
442442
}
443443

444444
export function resolvePath(filePath: string): string {
445+
// Reject NUL bytes up front (defense in depth): a poison byte cannot traverse
446+
// out of an approved root, but it must never reach the fs layer — fail here
447+
// with a clear error rather than letting Node throw deep in a later read/write.
448+
if (filePath.includes(String.fromCharCode(0))) {
449+
throw new Error("Invalid path: contains a NUL byte");
450+
}
445451
let resolved: string;
446452
if (filePath.startsWith("~")) {
447453
resolved = join(homedir(), filePath.slice(1));

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)