Skip to content

Keep agent-planted git hooks and drivers out of capture - #2071

Merged
ppXD merged 1 commit into
mainfrom
fix/keep-agent-git-hooks-out-of-capture
Oct 6, 2026
Merged

ppXD merged 1 commit into
mainfrom
fix/keep-agent-git-hooks-out-of-capture

Conversation

@ppXD

@ppXD ppXD commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • After the agent's turn, the capture, the re-attach capture and the branch commit ran git inside the agent-writable clone, with the network on and no hardening. A tampered .git could therefore run code:
    • client hooks (a vetoing pre-commit silently lost the produced branch)
    • an agent-set core.hooksPath
    • a core.fsmonitor program
    • textconv or external diff drivers
  • Every git command over the agent clone now goes through AgentCloneGitCommand (backend/src/CodeSpace.Core/Services/Agents/Workspace/AgentCloneGitCommand.cs). It adds -c core.hooksPath=/dev/null, -c core.fsmonitor=, and --no-ext-diff --no-textconv on diffs, and runs with no network and no credential.
  • One vector is accepted, not closed: a repo filter.<driver>.clean still runs on add -A, because git has no switch for it. It runs at the agent's uid, with no secret and, under bubblewrap, no egress.
    • The sandbox lane's floor now counts the bubblewrap case that proves this (97 in this stack: main 92, +4 from the goal-channel change below it, +1 here), and the lane requires that case to have run and passed.
    • The case's probe reports "severed" only for a connection the kernel refused; a probe that could not run writes "probe-failed".
  • The authenticated push and its ls-remote readback are out of scope here and still run in the agent clone.

Test plan

  • Unit: AgentCloneGitCommandTests; the LocalGitWorkspaceProviderTests routing Theory (every post-turn command in the clone is hardened, off the network, credential-free)
  • Integration: AgentCloneGitHookIsolationFlowTests: 13 planted vectors stay silent under the hardened capture and commit, and a positive-control Theory shows each one fires when unhardened. Run on git 2.33.0, 2.50.1 and 2.56.0. The pre-auto-gc plant also sets maintenance.gc.enabled=true: from git 2.54 auto maintenance uses the geometric strategy by default, which skips the gc task, so without that key the positive control never fired on CI's git 2.55.0.
  • Workflow: YAML parses; the embedded python compiles; the probe's reached, severed and probe-failed verdicts were exercised locally
  • Sandbox lane (Linux, bubblewrap): GitWorkspaceIsolationE2ETests.A_repo_clean_filter_runs_during_capture_with_its_egress_severed passes and the lane reports ≥ 97 executed

The capture, the re-attach capture and the branch commit ran git inside
the agent's clone after its turn, with the network on and no hardening.
An agent that wrote its own .git during the run could make those
commands run its code: client hooks (a vetoing pre-commit silently lost
the produced branch), an agent-set core.hooksPath, a core.fsmonitor
program, and textconv or external diff drivers selected through
.gitattributes or diff.external.

Every git command over the agent clone now goes through one builder,
AgentCloneGitCommand: -c core.hooksPath=/dev/null, -c core.fsmonitor=
and --no-ext-diff --no-textconv on diffs, with no network and no
credential. The fsmonitor value is empty rather than "false" because
git before 2.36 reads the key only as a program path and runs a
PATH-resolved "false" on every index refresh. The hooks path is
/dev/null rather than an empty value, which git resolves to the
filesystem root. The -c values also reach the gc --auto a commit can
start, so an agent that lowers gc.auto and re-enables the maintenance
gc task (off by default since git 2.54) cannot run pre-auto-gc either.

A repo filter.<driver>.clean still runs on add -A because git has no
command-line switch for it. It runs at the agent's own uid with no
secret and, under bubblewrap, with no egress. Global and system config
stay in force for git-lfs and operator CA or proxy settings. The
sandbox lane's floor counts the bubblewrap case that proves the egress
is severed, and the lane requires that case to have passed, because a
skipped case reads as green.

The authenticated push and its ls-remote readback still run in the
agent clone. Moving them to a platform-owned repository is a separate
change.
@ppXD
ppXD changed the base branch from fix/pass-every-claude-goal-as-a-structured-message to main October 6, 2026 11:04
@ppXD
ppXD force-pushed the fix/keep-agent-git-hooks-out-of-capture branch from 8b87a80 to f68742f Compare October 6, 2026 11:04
@ppXD
ppXD merged commit 03abe22 into main Oct 6, 2026
6 checks passed
@ppXD
ppXD deleted the fix/keep-agent-git-hooks-out-of-capture branch October 6, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant