Skip to content

[WRONG BRANCH] fix: prevent Codex refresh lock ownership races - #175

Draft
luvs01 wants to merge 1 commit into
mainfrom
codex/fix-refresh-lock-exploit-in-codex
Draft

[WRONG BRANCH] fix: prevent Codex refresh lock ownership races#175
luvs01 wants to merge 1 commit into
mainfrom
codex/fix-refresh-lock-exploit-in-codex

Conversation

@luvs01

@luvs01 luvs01 commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Motivation

  • The cross-process Codex refresh lock could be stolen when a process creates the lock file with openSync("wx") and only then writes JSON metadata, because isRefreshLockStale() treated any read/parse error as immediately stale.
  • A delayed or stale holder could therefore both enter the refresh critical section and later unlink a replacement lock, enabling duplicate rotating refresh requests and possible revocation of stored Codex credentials.

Description

  • Treat parse/read failures on the lock file as non-stale when the lock's mtime is recent, so a just-created but not-yet-written lock cannot be classified stale immediately (added statSync usage in isRefreshLockStale).
  • Record the acquired descriptor identity via fstatSync(fd) and only unlink the lock on release if the current path identity (statSync(path)) matches the held descriptor (compare dev and ino) to prevent deleting a replacement lock.
  • Added imports for fstatSync/statSync and the conditional unlink/ownership check in withCodexRefreshFileLock in src/codex/account-store.ts.
  • Added focused regression tests refresh does not steal a newly-created empty file lock and refresh owner does not remove a replacement file lock to tests/codex-account-store.test.ts to exercise the initialization and replacement races.

Testing

  • bun test tests/codex-account-store.test.ts — the updated account-store test file ran and all tests passed (25 pass, 0 fail).
  • bun run typecheck — TypeScript typecheck completed successfully.
  • bun run privacy:scan — privacy scan completed successfully.
  • bun run test — a full-suite run was attempted but the repository contains unrelated, pre-existing authentication test failures and long-running timeouts that prevented a green full-suite result; the focused regression and static checks above are green.

Codex Task

Summary by CodeRabbit

  • Bug Fixes
    • Improved refresh-lock handling to prevent concurrent operations from taking over locks during initialization.
    • Prevented cleanup from accidentally removing a replacement lock.
  • Tests
    • Added coverage for refresh-lock race conditions and safe lock replacement handling.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

⚠️ Wrong target branch

This pull request currently targets main, but pull requests must target one of dev.

@luvs01 Please retarget this PR to dev. All contributions go to dev; main receives only release promotions. See our Contributing guide for details. Thanks! 🙏

Its title has been prefixed with [WRONG BRANCH].

This pull request is being kept as a draft automatically. Once every issue above is resolved, it will be marked ready for review again.

@github-actions github-actions Bot changed the title fix: prevent Codex refresh lock ownership races [WRONG BRANCH] fix: prevent Codex refresh lock ownership races Aug 8, 2026
@github-actions
github-actions Bot marked this pull request as draft August 8, 2026 14:58
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The refresh-lock flow now protects empty locks during initialization and verifies device/inode identity before cleanup. Tests cover waiting for an external refresh and preserving a replacement lock file.

Changes

Refresh lock race handling

Layer / File(s) Summary
Protect empty refresh locks
src/codex/account-store.ts, tests/codex-account-store.test.ts
At src/codex/account-store.ts:330-336, empty or invalid locks use modification time to determine staleness. The test at tests/codex-account-store.test.ts:296-323 verifies that initialization waits for another process and avoids fetch.
Verify lock ownership before cleanup
src/codex/account-store.ts, tests/codex-account-store.test.ts
At src/codex/account-store.ts:372-382, cleanup unlinks the lock only when device and inode match the opened descriptor. The test at tests/codex-account-store.test.ts:325-344 verifies that replacement locks remain intact.

Estimated code review effort: 2 (Simple) | ~15 minutes

Suggested reviewers: lidge-jun, wibias

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: preventing Codex refresh lock ownership races.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-refresh-lock-exploit-in-codex

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/codex/account-store.ts`:
- Around line 330-336: Make refresh-lock removal atomic with ownership: in
src/codex/account-store.ts lines 330-336, replace the boolean stale-check flow
with an atomic mechanism that reclaims the inspected lock without returning a
result that callers later unlink by pathname; in lines 372-382, replace the
statSync(path)/unlinkSync(path) release sequence with the same owner-bound
removal mechanism, since a second identity check does not close the race. Add a
regression test that replaces the lock after identity validation and before
removal, verifying the replacement lock is preserved.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dd8eba7d-e586-4b2b-9e3c-66291920bd1c

📥 Commits

Reviewing files that changed from the base of the PR and between 8a9c0ef and 5910915.

📒 Files selected for processing (2)
  • src/codex/account-store.ts
  • tests/codex-account-store.test.ts

Comment on lines +330 to +336
// A newly-created lock is briefly empty while its owner writes metadata.
// Do not let a waiter steal it during that acquisition window.
try {
return Date.now() - statSync(path).mtimeMs > REFRESH_LOCK_STALE_MS;
} catch {
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make refresh-lock removal atomic with lock ownership.

Both paths validate a lock and later remove path by pathname. Another process can replace the path in that interval. The subsequent unlinkSync(path) then deletes an active replacement lock.

  • src/codex/account-store.ts#L330-L336: Change stale-lock reclamation so the inspected stale lock is reclaimed atomically. Do not let the caller unlink a path from a boolean stale result.
  • src/codex/account-store.ts#L372-L382: Replace the statSync(path) then unlinkSync(path) release sequence with an owner-bound release mechanism. A second identity check cannot close this race.

Add a regression test that replaces the lock after identity validation and before removal.

📍 Affects 1 file
  • src/codex/account-store.ts#L330-L336 (this comment)
  • src/codex/account-store.ts#L372-L382
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/account-store.ts` around lines 330 - 336, Make refresh-lock removal
atomic with ownership: in src/codex/account-store.ts lines 330-336, replace the
boolean stale-check flow with an atomic mechanism that reclaims the inspected
lock without returning a result that callers later unlink by pathname; in lines
372-382, replace the statSync(path)/unlinkSync(path) release sequence with the
same owner-bound removal mechanism, since a second identity check does not close
the race. Add a regression test that replaces the lock after identity validation
and before removal, verifying the replacement lock is preserved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant