|
| 1 | +# Release v1.3.0 |
| 2 | + |
| 3 | +Release line: `stable` |
| 4 | + |
| 5 | +This minor release lands the Phase 1 post-audit hardening pass: 20 focused PRs addressing 49 findings from the 2026-04-17 master audit, spanning path-guard hardening, OAuth hygiene, routing correctness, storage atomicity, schema discipline, and CLI diagnostics. |
| 6 | + |
| 7 | +## Scope |
| 8 | + |
| 9 | +- Published package version: `1.3.0` |
| 10 | +- Previous stable release: `v1.2.7` |
| 11 | +- Base audit: `docs/audits/MASTER_AUDIT.md` (commit range v1.2.4..v1.3.0) |
| 12 | +- Total PRs merged: 20 (#393 docs, #394-#412 implementation) + 7 post-audit remediation commits + 1 follow-up PR (#413) |
| 13 | +- Tests: 3345 → 3527 (+182) |
| 14 | +- Semver rationale: minor bump. No breaking public-API changes. One opt-in feature flag (`routingMutex`) added with legacy default. |
| 15 | + |
| 16 | +## What Changed |
| 17 | + |
| 18 | +### Security & Path Hardening |
| 19 | + |
| 20 | +- **`resolvePath()`** now rejects lookalike-prefix paths (e.g. `HomeX` vs `Home/`) using `path.relative()` comparison — blocks a sandbox-escape class (AUDIT-C1 / AUDIT-H1) (PR-A) |
| 21 | +- **OAuth URLs redacted** in user-facing login output — prevents token-leak via clipboard or terminal scrollback (AUDIT-H4) (PR-B) |
| 22 | +- **`AUTH_REDIRECT` single source of truth** — OAuth callback host unified to `127.0.0.1:1455` across bind, copy, and HTML; removes 4 duplicate hardcoded sites (AUDIT-H5 / M14 / M30) (PR-C) |
| 23 | + |
| 24 | +### Routing & Account Selection |
| 25 | + |
| 26 | +- **Hybrid selector returns `null`** when no accounts are available (was returning a stale fallback) (AUDIT-H2) (PR-D) |
| 27 | +- **Short-429 retry** now marks the account unavailable BEFORE the retry sleep, closing a TOCTOU window that let two requests race to the same rate-limited account (AUDIT-H3) (PR-E) |
| 28 | +- **Active-account pointer normalization** on disable/remove — prevents a dangling pointer when the active account is administratively removed (AUDIT-H10) (PR-F) |
| 29 | +- **Routing mutex + SelectionRecord type** — opt-in `routingMutex: "enabled" | "legacy"` config flag wraps cursor mutation in an async mutex. Default is `"legacy"` for one release cycle; enable via `CODEX_AUTH_ROUTING_MUTEX=enabled`. Four property tests (400 generated cases, fast-check) prove linearizability under concurrent requests (AUDIT-M06 / D-02 / D-09) (PR-N) |
| 30 | + |
| 31 | +### Storage & Recovery |
| 32 | + |
| 33 | +- **Recovery storage** migrated to atomic write + retry-safe delete pattern (AUDIT-M01) (PR-H) |
| 34 | +- **Account-clear ordering** now writes the reset marker BEFORE deletion and retries `EPERM` on read (AUDIT-M04 / M05) (PR-I) |
| 35 | +- **Per-project vs CLI-sync config conflict** now surfaced to the user instead of silently bypassing project-scoped isolation (AUDIT-M09) (PR-J) |
| 36 | +- **Zod at JSON.parse boundaries** — storage reads are now Zod-validated at 12 sites in `lib/storage/**`. New `safeParseJson<T>(raw, schema, context)` helper guards both `SyntaxError` and schema violations. V3 storage schema is the authoritative normalizer via `AnyAccountStorageSchema`. Recovery + request + config Zod migration deferred to follow-up PRs (AUDIT-M20) (PR-L) |
| 37 | + |
| 38 | +### Request Pipeline & SSE |
| 39 | + |
| 40 | +- **Malformed SSE JSON chunks** now surface as structured warnings instead of silent buffer drops. 10MB buffer cap documented. Deprecation/sunset headers logged uniformly across success AND failure paths (AUDIT-H9 / M16 / M18 / M34) (PR-K) |
| 41 | + |
| 42 | +### CLI Dashboard & Diagnostics |
| 43 | + |
| 44 | +- **`codex auth why-selected [--now|--last] [--json]`** — new diagnostic command surfacing per-candidate hybrid scoring breakdown (health, tokens, freshness, capability boost, PID bonus, final score) with reason text explaining the selection decision (PR-P) |
| 45 | +- **`codex auth verify [--paths|--flagged|--all] [--json]`** — new self-test command that walks the storage path resolution chain and exercises `resolvePath()` sandbox with known-good and known-bad inputs. Confirms the PR-A lookalike-prefix fix is live. `verify-flagged` kept as back-compat alias (PR-P) |
| 46 | +- **`lib/codex-manager/settings-hub.ts`** (808 LOC) split into 5 sub-concern files under `lib/codex-manager/settings-hub/`: `dashboard`, `backend`, `experimental`, `shared`, `index`. Each <500 LOC. Old file kept as a 9-line re-export stub for test compat. `lib/AGENTS.md` updated to retire the stale 2100-LOC claim (AUDIT-M24 / G-01 / JN-03) (PR-M) |
| 47 | + |
| 48 | +### Observability |
| 49 | + |
| 50 | +- **`getAccountHealth()`** reads the tracker directly; documents the field-name drift vs `ManagedAccount`. `CHANGELOG.md` updated (AUDIT-M08 / D-04) (PR-O) |
| 51 | + |
| 52 | +### Tooling, Docs & Tests |
| 53 | + |
| 54 | +- **`pack:check` + tmp hygiene** — `npm run pack:check` now builds first and tests use `os.tmpdir()`; 6 stray `tmp*` directories at repo root cleaned up (AUDIT-H7 / M31) (PR-G) |
| 55 | +- **Dual-linter scope documented** — ESLint vs Biome boundaries clarified; husky `prepare` hook side-effect documented (AUDIT-M21 / M22 / M23 partial) (PR-T) |
| 56 | +- **Truthup docs pass** — `lib/AGENTS.md` staleness fixed; `docs/reference/storage-paths.md` `deriveProjectKey` typo corrected; CHANGELOG drift analysis deferred (AUDIT-H8 / M32 / L04) (PR-Q) |
| 57 | +- **Phase 1 regression suite** — locked in audit invariants (PKCE S256, state entropy, SSE failover) (AUDIT-L01 / M03 partial) (PR-S) |
| 58 | +- **Master audit report** — `docs/audits/MASTER_AUDIT.md` + `docs/audits/evidence/findings-index.json` published (PR #393) |
| 59 | + |
| 60 | +## Config Changes |
| 61 | + |
| 62 | +- **NEW**: `PluginConfig.routingMutex: "enabled" | "legacy"` — default `"legacy"`. Env override: `CODEX_AUTH_ROUTING_MUTEX`. Recommended flip to `"enabled"` default in v1.4.0 after one release cycle of opt-in use. |
| 63 | + |
| 64 | +## Known Deferred (Phase 1.5 / Phase 2) |
| 65 | + |
| 66 | +Per `.sisyphus/notepads/phase1-implementation/f1-defer-notes.md`, the following audit findings have explicit defer rationale and are tracked for a follow-up "Audit follow-up" ticket: |
| 67 | + |
| 68 | +- **MEDIUM (11)**: M07 (tracker persistence), M10 (stream failover policy), M11 (callback graceful close), M12 (JWT exp decode), M15 (manual-paste UX), M17 (observability envelope), M26 (--json coverage audit), M27 (codex-cli vs codex-manager boundary docs), M28 (experimental stability policy), M29 (error taxonomy), M33 (semver verification — THIS release) |
| 69 | +- **LOW (11)**: L02, L03, L05, L06, L07, L08, L09, L10, L12, L13, L14 — batch-deferred to single janitorial PR |
| 70 | + |
| 71 | +### Post-Audit Remediation Commits |
| 72 | + |
| 73 | +After the 20 PRs landed, a full 7-risk-category audit was run against every PR. Findings: **0 CRITICAL, 13 HIGH (all addressed), 31 MEDIUM (report-only per tier policy), 29 LOW**. Per-PR JSON reports at `.sisyphus/notepads/phase1-audit/reports/pr###.json`. Triage: `.sisyphus/notepads/phase1-audit/TRIAGE-SUMMARY.md`. |
| 74 | + |
| 75 | +The following 7 fix commits were pushed on top of the original PR heads to close the HIGH findings: |
| 76 | + |
| 77 | +| PR | Fix commit | Addressed | |
| 78 | +|----|-----------|-----------| |
| 79 | +| #412 | `21c466a` | Property tests now use external concurrency observer (proves mutex actually serializes) | |
| 80 | +| #401 | `f877c85` | Completed atomic write migration (`injectTextPart`, `prependThinkingPart`) + `renameSync` retry on EBUSY/EPERM | |
| 81 | +| #410 | `b3c2945` | `verify --paths` sandbox probe hardened against `cwd=/` edge case + new commands documented | |
| 82 | +| #399 | `d7af34d` | All-disabled dangle + `cursorByFamily` divergence + 3 edge-case regression tests | |
| 83 | +| #396 | `f18d721` | `lib/ui/copy.ts` routed through `AUTH_REDIRECT` SSOT | |
| 84 | +| #396 | `475ea37` | Added `AUTH_REDIRECT` to codex-manager-cli test mock (follow-up to `f18d721`) | |
| 85 | +| #406 | `d9f7253` | Dual-linter docs corrected to match actual wiring (ESLint-only in lint-staged; Biome manual; CI enforcement confirmed in `ci.yml` + `pr-ci.yml`) | |
| 86 | + |
| 87 | +## Residual Known Deferred |
| 88 | + |
| 89 | +- **Pre-existing `removeAccount` dangle** (`lib/accounts/pointer.ts`): when the active account is removed and it was the last in its family array, the pointer is set to `-1` instead of falling back to another remaining account in that family. Out of scope for PR #399 per tier policy (pre-existing). Follow-up PR #413 closes this specific case with its own regression tests. |
| 90 | + |
| 91 | +## Validation |
| 92 | + |
| 93 | +- `npm test` — 3527 tests pass (was 3345, +182) |
| 94 | +- `npm run typecheck` — exit 0 |
| 95 | +- `npm run lint` — exit 0 |
| 96 | +- `npm run audit:ci` — 0 vulnerabilities |
| 97 | +- `npm run pack:check` — 822KB / 968 files (under budget) |
| 98 | +- `npm run vendor:verify` — 2 components / 8 files verified |
| 99 | +- `npm run clean:repo:check` — green |
| 100 | +- `npm run build` — exit 0 |
| 101 | + |
| 102 | +## Semver Verification (AUDIT-M33) |
| 103 | + |
| 104 | +Commit range v1.2.4..v1.3.0 audited for silent breaking changes: |
| 105 | + |
| 106 | +- **v1.2.4 → v1.2.5**: observability wording clarifications — docs only |
| 107 | +- **v1.2.5 → v1.2.6**: forwarded observability fix — patch-appropriate |
| 108 | +- **v1.2.6 → v1.2.7**: native Codex CLI install support — wrapper additive, no removal |
| 109 | +- **v1.2.7 → v1.3.0**: Phase 1 post-audit — MINOR appropriate: |
| 110 | + - New public surface: `codex auth why-selected`, `codex auth verify` commands |
| 111 | + - New opt-in flag: `routingMutex` (default legacy = zero behavior change) |
| 112 | + - New types exported: `SelectionRecord`, `HybridSelectionCandidateTrace`, `HybridSelectionTraceResult`, `FlaggedAccountStorageV1Schema`, `AccountsJournalEntrySchema` |
| 113 | + - No removed exports, no changed function signatures, no changed storage format |
| 114 | + - `verify-flagged` preserved as alias (no breaking removal) |
| 115 | + |
| 116 | +**Conclusion**: minor bump is correct; no major bump justified. |
| 117 | + |
| 118 | +## Related |
| 119 | + |
| 120 | +- Audit report: `docs/audits/MASTER_AUDIT.md` |
| 121 | +- Audit findings index: `docs/audits/evidence/findings-index.json` |
| 122 | +- Previous release notes: `docs/releases/v1.2.7.md` |
| 123 | + |
| 124 | +## PR Index |
| 125 | + |
| 126 | +| PR # | Branch | Title | |
| 127 | +|---|---|---| |
| 128 | +| #393 | docs/master-repository-audit-2026-04-17 | docs(audit): add master repository audit report and evidence | |
| 129 | +| #394 | fix/phase1-tests-green-and-resolvepath | fix(phase1): reject resolvePath lookalike-prefix paths + restore auth list reset message | |
| 130 | +| #395 | fix/oauth-url-redaction | fix(auth): redact OAuth URL in user-facing login output | |
| 131 | +| #396 | refactor/redirect-uri-ssot | refactor(auth): introduce AUTH_REDIRECT single source of truth | |
| 132 | +| #397 | fix/hybrid-selector-null-contract | fix(routing): hybrid selector returns null when no accounts are available | |
| 133 | +| #398 | fix/short-429-race | fix(routing): mark account unavailable before short-429 retry sleep | |
| 134 | +| #399 | fix/active-pointer-normalization | fix(accounts): normalize active pointer when the active account is disabled | |
| 135 | +| #400 | fix/release-hygiene | fix(release): pack:check builds first + tests use os.tmpdir | |
| 136 | +| #401 | fix/recovery-atomic-writes | fix(recovery): atomic writes + retry-safe deletes for recovery storage (R6) | |
| 137 | +| #402 | fix/storage-clear-ordering | fix(storage): write reset marker before deletions + retry EPERM on read | |
| 138 | +| #403 | fix/project-scope-no-silent-bypass | fix(routing): surface the per-project vs CLI-sync config conflict | |
| 139 | +| #404 | fix/request-observability | fix(request): surface malformed SSE JSON chunks as structured warnings | |
| 140 | +| #405 | docs/truthup | docs: truthup AGENTS.md staleness + deriveProjectKey typo | |
| 141 | +| #406 | chore/config-hygiene | chore(config): document dual-linter scope + husky prepare-hook side effect | |
| 142 | +| #407 | test/audit-regression-suite | test(audit): add Phase 1 regression suite locking in audit invariants | |
| 143 | +| #408 | refactor/health-unify | docs(health): document field-name drift vs ManagedAccount (AUDIT-M08) | |
| 144 | +| #409 | refactor/zod-storage-boundaries | refactor(types): add Zod safeParseJson at storage JSON boundaries | |
| 145 | +| #410 | feat/cli-why-selected-and-verify-paths | feat(cli): add codex auth why-selected and verify --paths commands | |
| 146 | +| #411 | refactor/settings-hub-split | refactor(codex-manager): split settings-hub.ts into 5 sub-concern files | |
| 147 | +| #412 | refactor/routing-mutex | refactor(routing): add feature-flagged routing mutex and SelectionRecord | |
| 148 | +| #413 | fix/active-pointer-followup | fix(accounts): resolve residual removeAccount dangle (follow-up to #399) | |
0 commit comments