Curated-plugin startup_sync can reset an unrelated repository (refs/codex/curated-sync, hook-bypassed)
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 foreignrefs/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/pluginspresent with no.git; Codex session cwd was a git worktree; that worktree's own branch reflog showedreset: moving to refs/codex/curated-sync; loose ref appeared in the host repo's common.git/refs/codex/.- Then
~/.codex/.tmp/plugins/.gitwas 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
- Pin the gitdir for startup_sync. Run the sync git ops with explicit
GIT_DIR/GIT_WORK_TREE(or equivalent internal API) pointing atCODEX_HOME/.tmp/plugins, and verify the resolved gitdir is insideCODEX_HOMEbefore fetch/reset. Do not inherit the user session cwd. - Fail closed on gitdir mismatch. Before
fetchorreset --hard, assert that the repository being operated on is the plugin store. If the resolved gitdir is outsideCODEX_HOME, abort sync rather than touching it. - Stop bypassing safety hooks for ambient repos.
-c core.hooksPath=/dev/nullmakes 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 insideCODEX_HOME. - Provide a documented opt-out. A stable config key to disable curated-plugin sync. (The binary appears to reference a
plugins_enabledfield 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-syncin 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.gitwas 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.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗