BOHICA-LABS / BOHICA-LABS/vsdd-factory
bug(scratch-isolation): cp -r of a git worktree silently poisons the shared repo's .git/config
- Dominant language
- Rust
- Stars
- 2
- Forks
- 1
- Avg merge
- 6h 43m
- Merged PRs (30d)
- 29
Description
## Scratch-repo isolation failure — `cp -r ` silently poisons shared git config
Observed on a private project running the factory. Framework-pattern detail only.
### Pattern
An adversary agent was asked to create a "scratch repo" to test a guard's behavior in isolation. It copied a **git worktree** with `cp -r /tmp/scratch-` and ran commit-mutation experiments inside the copy. A worktree's `.git` is not a directory — it is a **pointer file** (`gitdir: /path/to/main/.git/worktrees/`) referencing the shared repo. Writes to `.git/config` inside the copy therefore modified the **real shared repo's config**, not an isolated one.
Configuration writes that landed in the shared repo, unnoticed:
- `user.email` → a placeholder scratch identity
- `user.name` → a placeholder scratch identity
- `commit.gpgsign` → `false`
- `core.hooksPath` → unset
When the scratch directory was later deleted with `rm -rf /tmp/scratch-`, the poisoning survived. There was no visible artifact indicating anything had changed.
### Impact measured
Across ~26 hours following the incident:
- **~25 subsequent commits** carried the placeholder author identity (unnoticed at commit time).
- **11 of those reached the default branch** via squash-merge (the merge commit author is the merger, but the merged commit trailer preserves the poisoned identity in `git log`).
- **1 factory commit was created unsigned** because the responsible agent relied on `commit.gpgsign=true` being set globally rather than passing `-S` explicitly.
Detection was accidental: the orchestrator ran `git log --pretty='%h %G? %an %s'` during routine verification and noticed one recent commit reading `%G? = N` and a run of `%an = ` values.
### Mitigations that survived the incident
1. **Explicit `-S` mandate in commit-making agent briefs.** The one unsigned commit was created by an agent that relied on the config; every other agent brief that mandated `git commit -S` produced signed commits despite `commit.gpgsign=false` in the shared config. Explicit-flag > config-driven for security-relevant defaults.
2. **Pattern is inconsistently applied, not unknown.** In the same transcript, a *later* scratch-repo run correctly used `git clone /tmp/scratch-` and worked in a truly isolated repo. The clone-based pattern is present in the agent's toolkit; it just wasn't used the first time.
### Framework asks
**Hard-line rule for scratch isolation.** Scratch repos MUST be created with `git clone` (or `git worktree add`, which creates a proper worktree file). `cp -r` of an existing worktree MUST NOT be used for scratch experiments — the copy is not isolated, and writes to `.git/config` in the copy affect the shared repo.
**Session-close config-integrity check.** Add a session-close (or per-agent-close) hook that snapshots `git config --local user.email`, `user.name`, `commit.gpgsign`, and `core.hooksPath` on session start and diffs them on close. Any unexpected change is a `HIGH` finding and blocks close until acknowledged.
**Identity check step in commit-making agent briefs.** Any agent that will run `git commit` must first verify `git config user.email`, `user.name`, and `commit.gpgsign` match project expectations, and MUST pass `-S` explicitly. Do not rely on config-driven signing.
### Cross-refs
- #457 (agent-report / operational reliability class — this is a different reliability class: agent action has invisible side effects on shared state)
- #285 (self-attestation contradicted by ground truth — related but distinct: here there was no attestation, only silent state corruption)
*(Framework-pattern detail only. From a private project running the factory.)*
Contributor guide
Assessment
This issue has not been assessed yet.