Skip to content

fix(oauth): harden OAuth login/import, quota diagnostics, service/update paths and shim token handling (B5) - #7397

Merged
lidge-jun merged 22 commits into
devfrom
fix/oauth-accounts-service-hardening-2.90
Oct 19, 2026
Merged

lidge-jun merged 22 commits into
devfrom
fix/oauth-accounts-service-hardening-2.90

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

Bundle B5 for 2.90: OAuth login/import, account health diagnostics, service/update hardening, and a Codex shim token-exposure fix. It carries nine contributor PRs onto current dev, reworks a tenth (#7385) to meet its review, and adds three new fixes.

Shim token exposure (security). All three Codex shims loaded the service API token into OPENCODEX_API_AUTH_TOKEN before checking OCX_SHIM_BYPASS, so a bypassed native launch handed the proxy's data-plane token to the real Codex and everything it spawned. Now:

  • POSIX, CMD and PowerShell shims skip the token load under bypass. The install probe (shim-probe.ts) sets bypass and never needed the token.
  • Already-installed wrappers get the fix too. The Unix revision marker moves to 4, and CMD/PowerShell wrappers gain a Windows revision marker. Install, auto-restore and diagnostics treat an older marked wrapper as obsolete.
  • Obsolete Windows wrappers are replaced through a transaction (src/codex/shim-windows-refresh.ts) that never truncates or writes through a concurrent replacement or a planted link. A partial sibling upgrade resumes automatically.
  • loadServiceTokenFromFile reads the inherited OCX_API_TOKEN_FILE through one bounded descriptor: no-follow and nonblocking on POSIX, a dev/ino identity recheck on Windows, regular file only, at most 4 KiB, single-line printable token.

#7380 quota diagnostics. A per-account quota probe that returns no quota now names one closed quotaFailure code, drawn from the existing ten-code set, so the GUI needs no change.

  • Anthropic rows previously gave no reason. They now report account_unavailable (no request sent), the HTTP status class, response_unusable, or timeout/transport_error.
  • Antigravity distinguishes a credential-refresh deadline (timeout) from account_unavailable.
  • No upstream text ever reaches the row, and account health and needsReauth are never changed.

#7382 via #7385, reworked. A Cockpit import record may carry an optional project_id. Discovery always wins. The fallback is used only when discovery finds no project, and only after the account's own token passes the existing Antigravity quota probe for that project. There is no shared default project.

Items fixed

Carried PRs

PR Author Taken Adapted / dropped
#7379 @Ingwannu Settled failed-flow dismissal, ADR-7378, tests Applied as-is
#7304 @luvs01 Explicit key mode is decided by configured intent, so an unresolvable key fails closed instead of reclassifying to OAuth Applied as-is
#7333 @luvs01 Bounded DSH profile reads + malformed encoding rejection Applied as-is
#7305 @luvs01 DSH test fixture drain Applied as-is
#7343 @luvs01 Claude CLI local errors kept out of subscription cooldown Applied as-is
#7301 @luvs01 Bounded Desktop library reads and preflight writes Fixed the maintainer P2: the real-ctime test now loops utimes until ctime really advances (bounded), asserts that, then requires rejection (10/10 runs). Also fixed a macOS-only picker test failure: the atomic writer renames onto the symlink-resolved /private/var path
#7313 @luvs01 Stop scheduler wrappers before Windows reinstall Applied as-is
#7277 @luvs01 Require RunAtLoad:true before Desktop relaunch Applied as-is (merge commit dropped)
#7342 @luvs01 Strip OpenSSL startup variables from the updater child env Applied as-is
#7385 @yobushka Optional project_id in Cockpit import Reworked per review: bounded schema (exact Google Cloud project grammar, 6–30 chars, no trimming; wrong type, blank, padding and oversize are invalid_record); probe-gated admission with the account's own token; discovery precedence; cancellation forwarded into the probe; no write on refusal, error or cancel; fakes only in tests; owning doc structure/providers/antigravity-account-import.md and providers guide

Co-authored-by: Ingwannu 186453546+Ingwannu@users.noreply.github.com
Co-authored-by: luvs01 27862058+luvs01@users.noreply.github.com
Co-authored-by: yobushka 51312852+yobushka@users.noreply.github.com

Held out

Judgment calls

  • No new quotaFailure codes. The GUI translates exactly the existing ten, and GUI work is out of scope, so every new diagnosis maps onto an existing code.
  • A refused or failed project_id probe reports the existing missing_project code; adding a new code would also touch the CLI and GUI code lists.
  • The Windows refresh publishes by hard link. A filesystem that refuses hard links leaves the obsolete wrapper restored and the refresh deferred. The alternative is a check-then-rename, which reopens the clobber race.

Security review

SECURITY REVIEW: PASS

A dedicated sol (gpt-6.1-sol) security review ran over the full diff, iterating until it passed:

  • Round 1: P1, existing Windows shims never upgraded.
  • Round 2: P2, the refresh could truncate a concurrent replacement.
  • Round 3: two P2s, non-exclusive staging and a stuck partial upgrade.
  • Round 4: three P2s (stage symlink swap, substituted aside, adopted sibling)
  • Round 5: one P2 (no hard-link support)
  • Round 6: SECURITY REVIEW: PASS (round 6, final head before the 74463a2 rebase; the rebase was clean apart from one test-layout line)

Each finding was fixed with a regression test. The sol code review found 0 P0 / 0 P1; its two P2s are covered above (one fixed, one held out).

Verification

All commands run on this host (macOS, Bun 1.4.3) from the repository root, at the final head rebased on origin/dev 74463a2.

  • bun run typecheck — pass.
  • bun run structure:check — pass. bun run structure:index was regenerated for the new providers/antigravity-account-import.md.
  • bun run privacy:scan — pass.
  • Layout, ratchet and boundary guards (tests/ci-workflows/file-size-ratchet.test.ts, import-cycle-ratchet.test.ts, structure-ssot.test.ts, tests/test-layout.test.ts, tests/test-layout-tooling.test.ts, tests/lab/core-lab-boundary.test.ts) — 128 pass, 0 fail.
  • Focused bun test --isolate over the 31 files covering every carried and new change (oauth status, fix(providers): preserve explicit key mode when credentials are unavailable #7304 boundary/upsert/selection/router, DSH, Claude CLI admission, Desktop library/picker, service wrapper/preflight, cold start, updater env, service secrets/WinSW, account import + fallback, quota failure reasons + account quota + accounts API, shim bypass/Windows revision/standalone) — 737 pass, 0 fail.
  • bun test --isolate tests/service/service.test.ts — 212 pass, 0 fail.
  • Shim suites (tests/codex-integration/codex-shim*.test.ts, codex-shim-path-readiness.test.ts) — under host load average 15–29 some spawn-based cases hit their 2–5 s ETIMEDOUT. Every one of those cases was rerun back-to-back on this branch and on a clean origin/dev 74463a2 checkout. Both give identical results: 7 pass, and 1 fails identically on both (Unix shim permits a real Codex process to start a new child invocation, a 2 s spawn timeout). Every shim test that changed in this PR (bypass token, Windows revision and transaction, standalone, readiness fixture) passes.
  • bun run test:changed (before the last rebase and the round-5 fix): 35393 pass / 102 skip / 4 fail across 1599 files.
    • Two failures were the round-5 tests, added while the run had already loaded the earlier source; they pass on the final head.
    • The other two were spawn-based shim tests hitting 2–5 s ETIMEDOUT. At load average 22–29, the same shim tests fail identically on a clean origin/dev checkout under the same load, so they are host contention.
    • An earlier test:changed found one real failure: a macOS-only picker fixture path in the fix(claude): bound Desktop library reads and preflight writes #7301 carry, fixed here.
  • Regressions were checked against the code without the fix: OAuth quota probe failures lack actionable diagnostics while account health remains healthy #7380 quota reasons 0/9 pass without the fix; Windows shim upgrade 0/4; round-4 identity bindings 0/3.
  • A full bun run test was not run: several worktrees share this host and spawn-timed tests are unreliable at this load. The rest of the cross-platform coverage is left to CI. Windows shim behavior is covered by template-text tests and process.platform emulation; native Windows shells were not executed.

No live provider, OAuth or ocx service calls were made.

Ratchet hot files touched: src/providers/quota.ts 549/558, src/codex/shim.ts 1243/1246, tests/service/service.test.ts 4104/4106. No touched file without a baseline cap reaches 2000 lines (scripts/test-layout/layout.json is 1998; it has no cap and passes the ratchet, but is close to the 2000 line mark).

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

lidge-jun and others added 22 commits October 20, 2026 06:49
Co-authored-by: Ingwannu <186453546+Ingwannu@users.noreply.github.com>
…ble (carry #7304)

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…#7343)

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…7301)

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…7313)

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…arry #7342)

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
All three Codex shims loaded the service API token into OPENCODEX_API_AUTH_TOKEN before checking OCX_SHIM_BYPASS, so a bypassed native passthrough launch handed the proxy data-plane token to the real Codex and its descendants. The POSIX, CMD and PowerShell shims now skip the token load under bypass; the Unix revision marker moves to 4 so installed wrappers are refreshed.

loadServiceTokenFromFile reads the inherited OCX_API_TOKEN_FILE through a no-follow, nonblocking descriptor, accepts only a regular file of at most 4 KiB and returns only a single-line printable token.
…tion

Re-applying the same utimes moves ctime only after the kernel clock ticks, so the real ctime case from #7301 often observed no change and the reader correctly returned {}. Repeat the call until ctime has advanced (bounded), assert that precondition, then require desktop_config_not_regular.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…t import (carry #7385)

An Antigravity account whose Cloud Code Assist project is not auto-discovered was rejected with missing_project even when the operator knows its project. A Cockpit record may now carry a bounded project_id (Google Cloud project grammar, 6-30 chars, exact match). Discovery keeps precedence; the fallback is admitted only after the account quota probe succeeds with that account own access token against that project. Probe refusal, probe errors and cancellation write nothing. No shared default project.

Co-authored-by: yobushka <51312852+yobushka@users.noreply.github.com>
Anthropic per-account quota rows reported quotaUnavailable with no reason, and Antigravity reported account_unavailable for any credential-side throw. The Anthropic usage read now reports a closed code for an HTTP status class, an unusable body or a thrown dispatch, and a probe that cannot obtain a credential reports account_unavailable without dispatching. Antigravity distinguishes a credential refresh deadline (timeout). Codes reuse the existing closed QUOTA_FAILURE_CODES set, carry no upstream text and never change account health.
…r restore case

The atomic writer renames onto the symlink-resolved target, so on macOS (/var -> /private/var) the rollback rename never matched the fixture path and the restore-failure injection did not fire. Compare both spellings.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…ort request

probeAntigravityUsageQuota accepts an optional caller signal that cancels every request of the probe and stops before the models fallback; the import verifier forwards the import signal and never admits after cancellation.
…e bypass

CMD and PowerShell wrappers now carry a Windows revision marker. A marked wrapper without it (installed before the bypass-token fix) is obsolete: installCodexShim and autoRestoreCodexShim rewrite it in place over the same backing launcher instead of reporting it healthy or already installed, and diagnostics report it unhealthy. On Windows the service token reader also binds the opened descriptor to the path by identity, so a link swapped in after lstat is refused.
…rrent replacement

The obsolete-wrapper refresh truncated whatever occupied the wrapper path. It now moves the observed wrapper aside and proves it is the probed file, stages the current wrapper beside it, and publishes with a hard link that refuses an occupied path. A concurrent replacement is restored or kept untouched and the refresh reports deferred.
…ume partial upgrades

The refresh stage is now created with O_CREAT|O_EXCL (and O_NOFOLLOW on POSIX) under an independent random name, and publication, withdrawal and cleanup are bound to the identity of the file this refresh created, so a planted link is never written through. Auto-restore resumes a Windows revision-only sibling mix (current plus obsolete wrappers) instead of deferring forever after one transient publish failure.
…ed identities

Publication keeps a linked entry only if it is the created stage, and withdraws a link to a swapped stage (file or symlink) while leaving independent replacements. Restoring the moved wrapper requires the aside to still be the moved file. Automatic revision recovery now refreshes only the admitted obsolete wrappers and first re-verifies every current sibling, so a replacement during the stability wait is never adopted.
…t be published

The refresh proves hard-link support in the wrapper directory with a throwaway pair before moving the wrapper aside, and if a restore link still fails it falls back to an identity-checked rename onto the empty path, so the launcher never stays missing.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner October 19, 2026 21:56
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T22:00:22.935278Z b1ffd89 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Oct 19, 2026
@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 8e591b24-73c9-48ee-a6c0-8a4454d91376
📥 Commits

Reviewing files that changed from the base of the PR and between 74463a2 and b1ffd89.

📒 Files selected for processing: 77
  • docs-site/src/content/docs/guides/claude-code.md
  • docs-site/src/content/docs/guides/providers.md
  • docs-site/src/content/docs/reference/management-api.md
  • scripts/test-layout/layout.json
  • src/adapters/claude-cli/adapter.ts
  • src/adapters/coding-agent/turn.ts
  • src/claude/desktop-3p-library.ts
  • src/claude/desktop-3p.ts
  • src/claude/desktop-picker-profile.ts
  • src/claude/desktop-remote-store-io.ts
  • src/cli/ensure-desktop-cold-start.ts
  • src/codex/cli-update-plan.ts
  • src/codex/shim-diagnostics.ts
  • src/codex/shim-fingerprint.ts
  • src/codex/shim-templates.ts
  • src/codex/shim-windows-refresh.ts
  • src/codex/shim.ts
  • src/lib/service-secrets.ts
  • src/oauth/account-import/google-antigravity-adapter.ts
  • src/oauth/account-import/parser.ts
  • src/oauth/account-import/types.ts
  • src/oauth/dsh.ts
  • src/oauth/login-flow-state.ts
  • src/providers/claude-cli-usage.ts
  • src/providers/initial-model-selection.ts
  • src/providers/key-store.ts
  • src/providers/quota-routing-cache.ts
  • src/providers/quota.ts
  • src/providers/quota/anthropic-account-quota.ts
  • src/providers/quota/antigravity.ts
  • src/providers/quota/failure-codes.ts
  • src/providers/quota/vendor-probes-oauth.ts
  • src/providers/routed-config.ts
  • src/server/management/oauth-account-routes.ts
  • src/service/orchestration.ts
  • structure/INDEX.md
  • structure/clients/claude-desktop-config-reads.md
  • structure/codex-cli-update.md
  • structure/decisions/ADR-7378-oauth-failed-flow-dismissal.md
  • structure/manifest.json
  • structure/ops/service-and-sidecars.md
  • structure/providers/anthropic-account-pool.md
  • structure/providers/antigravity-account-import.md
  • structure/providers/claude-cli-replay.md
  • structure/providers/dsh-account.md
  • structure/providers/oauth-code-display-login.md
  • structure/runtime.md
  • structure/subagents.md
  • structure/transports/inventory.md
  • tests/claude-integration/claude-desktop-picker-profile.test.ts
  • tests/cli/cli-ensure-desktop-cold-start.test.ts
  • tests/clients/desktop-3p-removal.test.ts
  • tests/clients/desktop-3p-write-files.test.ts
  • tests/clients/desktop-3p.test.ts
  • tests/codex-integration/codex-cli-update-plan.test.ts
  • tests/codex-integration/codex-shim-bypass-token.test.ts
  • tests/codex-integration/codex-shim-readiness.test.ts
  • tests/codex-integration/codex-shim-windows-revision.test.ts
  • tests/fixtures/test-layout-expected.json
  • tests/oauth/dsh-login.test.ts
  • tests/oauth/oauth-status-privacy.test.ts
  • tests/oauth/oauth-upsert-preserves-api-key.test.ts
  • tests/providers/claude-cli-usage-admission.test.ts
  • tests/providers/codebuddy-tool-bridge-turn.test.ts
  • tests/providers/dsh-account.test.ts
  • tests/providers/explicit-key-pause-oauth-boundary.test.ts
  • tests/providers/initial-model-selection.test.ts
  • tests/providers/provider-account-quota-failure-reasons.test.ts
  • tests/providers/resolved-model-policy.test.ts
  • tests/routing/router.test.ts
  • tests/server/account-import-project-fallback.test.ts
  • tests/service/service-desktop-runtime-preflight.test.ts
  • tests/service/service-install-wrapper-cleanup.test.ts
  • tests/service/service-ownership-handover.test.ts
  • tests/service/service-runtime-preflight.test.ts
  • tests/service/service-secrets.test.ts
  • tests/service/service.test.ts
+-----------------------------+
| The Da Vinci Code Reviewer. |
+-----------------------------+
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lidge-jun
lidge-jun merged commit dbb908f into dev Oct 19, 2026
27 of 30 checks passed
@lidge-jun
lidge-jun deleted the fix/oauth-accounts-service-hardening-2.90 branch October 19, 2026 21:58
lidge-jun added a commit that referenced this pull request Oct 19, 2026
Merged with #7397, the three new rows pushed layout.json to 2001 lines.
Fold them onto neighboring rows; the file stays at dev's 1998.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>
Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>
lidge-jun added a commit that referenced this pull request Oct 19, 2026
…l-result handling for 2.90 (#7395)

* fix(opengateway): normalize paired tool result inputs

Carry the input-normalization slice of draft #7281 (16263b7).
Preserve call/result identities and image content while projecting text arrays;
reject unsupported or malformed parts explicitly instead of forwarding them.
Keep the normalizer independently covered by mocked, immutable-input tests.

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* fix(opengateway): inspect empty completions with bounded replay

Carry the independent completion guard and replay-safety tests from draft
#7281 (16263b7). Inspect SSE/JSON only before meaningful output, release the
held prefix on time/size expiry, and permit at most one eligible retry callback.
Release the owned reader lock on cancellation and remove a redundant counter.

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* fix(opengateway): integrate completion recovery with the send budget

Port the remaining adapter/dispatch integration from draft #7281 (16263b7)
onto current dev. Map input incompatibility to HTTP 400 before any send;
cap empty-completion recovery at one physical send, including HTTP 503 and
connection-reset failures, while preserving existing admission and accounting.

Cover the actual handler with mocked fetches and keep English/Korean guidance
consistent. Split the OpenGateway structure contract to stay within document
budgets and distinguish native recovery from the translated opt-in guard.

Verification: typecheck, 118 focused tests, 27 layout tests, 9 size-ratchet tests,
structure and privacy checks, and the docs build passed. Changed-mode rerun
passed 19780 tests with 34 skips (30s timeout, two workers); the first run had
one unrelated package-tree subprocess timeout, which also passed standalone.

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* fix(mistral): preserve cache keys and recover from rate limits

Canonical key-auth Mistral Chat now sends prompt-cache keys, paces each
credential/model pair from measured refusal headers with bounded,
abortable waits outside the upstream header deadline, and retries 429s
with a slower default budget. A server delay beyond the retry budget is
kept non-replayable and its Retry-After reaches the caller. Relays,
other adapters, OAuth credentials and xAI are unchanged.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>

* fix(mistral): pace native Chat sends through the Mistral transport

A single-key native Chat route never re-resolved its transport, so the
new Mistral retry default replayed an over-budget Retry-After after 60
seconds instead of returning the refusal.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>

* fix(opengateway): share the exact-URL boundary between normalization and recovery

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* test(responses): inventory the OpenGateway completion owners

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* fix(mistral): keep evicted pacing deadlines in force

At 128 live identities the pacer dropped the oldest deadline, so that
credential/model pair could send before its Retry-After. Remember the
latest evicted deadline and make identities without an entry wait it
out; memory stays bounded and the only cost is conservative pacing
under extreme fan-out.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>

* test(layout): keep B4b registrations within the layout file budget

Merged with #7397, the three new rows pushed layout.json to 2001 lines.
Fold them onto neighboring rows; the file stays at dev's 1998.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>
Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>

* fix(mistral): hold only same-key replays on an over-budget Retry-After

Marking the refusal non-replayable also blocked key-pool rotation and
sanitized the upstream body. Use a same-target retry hold that only the
same-key 429 loops check (native Chat, adapter dispatch and
continuation, image and web-search loops), carried across response
rewraps, so another pooled key can still serve and the original status,
body and Retry-After reach the caller. Also keep an evicted identity's
live deadline in force when it re-enters with a shorter delay.

Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>

---------

Co-authored-by: HeiTuz <79418013+HeiTuz@users.noreply.github.com>
Co-authored-by: adaurban <31841049+adaurban@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant