Skip to content

guard: block git writes outside the bound source worktree and shell-substituted payloads #284

Description

@lidge-jun

What happened

On 2026-10-01 an OpenCodex HOTL session (opencodex, session 01a0f5b2-21d7-7051-a4da-193ebc397f34) had a task-owned source worktree bound with cxc session source /…/opencodex/.tmp/lanes/omo. The native cwd was the main checkout, with local branch dev.

While editing plan docs, the agent ran a string substitution through exec_command. The replacement text contained Markdown backticks, for example git cherry-pick 961a4b569..ada7ec14b1, and was passed to the shell inside double quotes. zsh treated the backticks as command substitution and ran them. No workdir was given, so they ran in the native cwd, not the bound source worktree.

The result was a real git cherry-pick on the main checkout's local dev. It created stray commit fa7b955db0 and stopped mid-sequence on a modify/delete conflict. Other substituted fragments also ran. gh pr merge <n> … only failed to execute because of a shell parse error on <n>. The checkout was recovered with git cherry-pick --abort. Nothing was pushed.

Nothing in codexclaw stopped any of this, even though the session's FSM knew that implementation belonged in the bound source worktree.

Why this matters

  • cxc session source declares where implementation happens, but a git write outside that root goes through silently.
  • One quoting mistake can rewrite or merge on a shared integration branch. Here it was the main checkout of a repo with protected dev, and a successful gh pr merge would have been an external, irreversible action.
  • The trigger is common in agent work: generated text (Markdown with inline code) is interpolated into sh -c / zsh -lc arguments.

Proposal

  1. PreToolUse guard for git writes outside the bound source. When a session has a pinned sourceRoot, deny or require explicit override for shell commands that run git write verbs (cherry-pick, commit, merge, rebase, reset, checkout/switch, am, revert, push, branch -D/-f) whose effective repository (resolved cwd / -C / --git-dir) is not the source root. Read-only git verbs stay allowed. Report the resolved root in the denial so the agent can retry with the right workdir.
  2. Command-substitution tripwire for interpolated payloads. Warn, or deny under HOTL, when a command's argument strings contain backtick or $( sequences inside a python -c / perl -e / sh -c payload or a quoted CLI argument, which usually means data was interpolated into shell code. Point the agent to apply_patch, a heredoc with a quoted delimiter, or --body-file style inputs.
  3. High-risk external verbs. Treat gh pr merge, gh pr close, gh issue close, and git push inside a substituted or -c payload as deny-by-default even without a source binding.
  4. Add a short rule to the cxc-dev git/safety reference: never pass generated text through shell quoting. Use apply_patch or files for text edits.

Acceptance

  • With a bound source root, git cherry-pick A..B run in the native cwd is refused with a message naming both roots. The same command with workdir set to the source root is allowed.
  • A zsh -lc payload that contains a backticked git push is flagged before execution.
  • Read-only commands (git log, git status, git diff, gh pr view) are unaffected.

Related (not codexclaw, for context)

The same session also hit two Codex app behaviors. set_thread_archived silently removed managed worktrees that held uncommitted work; it was recoverable from refs/codex/snapshots/*. And create_thread always starts threads with the :workspace permission profile, regardless of the app's full-access default.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions