BOHICA-LABS / BOHICA-LABS/vsdd-factory

bug(scratch-isolation): cp -r of a git worktree silently poisons the shared repo's .git/config

Open
#508 1 comment 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.