Curated-plugin startup_sync can reset an unrelated repository (refs/codex/curated-sync, hook-bypassed)

Open 💬 2 comments Opened Jun 21, 2026 by anaent

Version: codex-cli 0.141.0 (macOS, arm64)

Summary

When Codex CLI runs with its working directory inside an unrelated git repository (or a git worktree), the curated-plugin startup_sync can execute git fetch + git reset --hard refs/codex/curated-sync against that repository instead of its own plugin store. The result is that the host repo gains a refs/codex/curated-sync ref and the checked-out branch is hard-reset to the OpenAI curated-plugins commit (update(codex-security) v0.1.9, tree = .agents/ plugins/ README.md .gitignore), which surfaces as a mass deletion of the host repo's real files.

Because the sync runs every git command with -c core.hooksPath=/dev/null, host-repo reference-transaction / pre-* hooks cannot defend against it.

Impact

  • Host repo's current branch is silently reset to an unrelated commit.
  • In a multi-worktree checkout (shared object store + packed-refs), the foreign refs/codex/* ref and the reset are visible to / affect sibling worktrees.
  • Uncommitted and un-pushed work on the reset branch is destroyed by the --hard.

Where

core-plugins/src/startup_sync.rs. Embedded operation sequence (from the shipped binary):

git init curated plugins repo
git fetch curated plugins repo      # --depth 1 --no-tags  → refs/codex/curated-sync
git reset curated plugins repo      # reset --hard refs/codex/curated-sync
git clean curated plugins repo

…each invoked with -c core.hooksPath=/dev/null.

Root cause (observed)

The sync's git invocations can resolve their gitdir from the Codex session current_dir (a user worktree) instead of a pinned CODEX_HOME/.tmp/plugins gitdir. When current_dir is inside a host repo, discovery lands on the host repo's gitdir (or, for a worktree, its shared common dir), so the fetch writes refs/codex/* there and the reset moves the host branch.

Observed twice:

  • ~/.codex/.tmp/plugins present with no .git; Codex session cwd was a git worktree; that worktree's own branch reflog showed reset: moving to refs/codex/curated-sync; loose ref appeared in the host repo's common .git/refs/codex/.
  • Then ~/.codex/.tmp/plugins/.git was explicitly initialized as a mitigation, the host ref was purged, and the leak still recurred: DevConsole refs/codex=1 ; plugin-store .git present=yes. This falsifies the workaround "pre-create the plugin-store .git" and indicates the failing fetch/reset path is using session cwd, not the plugin-store gitdir.

Reproduction

# host repo with a worktree
git init src && (cd src && git commit --allow-empty -m foreign)
git init dc  && (cd dc && git commit --allow-empty -m main && git branch feature)
git -C dc worktree add ../dc-wt feature

# simulate the sync's hook-bypassed write with current_dir = the worktree
cd dc-wt
git -c core.hooksPath=/dev/null fetch ../src "+refs/heads/master:refs/codex/curated-sync"
git -C ../dc for-each-ref refs/codex     # -> refs/codex/curated-sync present in the HOST repo

Suggested fixes

  1. Pin the gitdir for startup_sync. Run the sync git ops with explicit GIT_DIR/GIT_WORK_TREE (or equivalent internal API) pointing at CODEX_HOME/.tmp/plugins, and verify the resolved gitdir is inside CODEX_HOME before fetch/reset. Do not inherit the user session cwd.
  2. Fail closed on gitdir mismatch. Before fetch or reset --hard, assert that the repository being operated on is the plugin store. If the resolved gitdir is outside CODEX_HOME, abort sync rather than touching it.
  3. Stop bypassing safety hooks for ambient repos. -c core.hooksPath=/dev/null makes host repo hooks unable to defend against this. If hook bypass is needed for the plugin store, apply it only after the gitdir has been proven to be inside CODEX_HOME.
  4. Provide a documented opt-out. A stable config key to disable curated-plugin sync. (The binary appears to reference a plugins_enabled field under an [experimental] section, but the exact TOML syntax is not documented — please confirm.)

Workaround for affected users

  • Delete the foreign ref: git update-ref -d refs/codex/curated-sync in the host repo.
  • Recover any reset branch from its reflog @{1} tip.
  • Do not rely on host repo hooks as a complete mitigation: Codex runs the sync with -c core.hooksPath=/dev/null.
  • Do not rely on pre-creating CODEX_HOME/.tmp/plugins/.git: this was tested and the leak still recurred while that .git was present.
  • The only reliable local containment found so far is operational: avoid launching Codex sessions with cwd inside important git worktrees until startup_sync is fixed or a documented opt-out exists.

Open question for maintainers

Can you confirm the exact startup_sync current_dir / gitdir selection for git fetch and git reset --hard refs/codex/curated-sync? The observed host-branch reset, plus recurrence while CODEX_HOME/.tmp/plugins/.git existed, implies those operations can run against the user session cwd rather than the plugin store.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗