Add invigilator: anti-cheat trace audit for leave-one-out runs#21
Conversation
Audits an agent run's tool-call trace (agent-<id>.jsonl) and flags any file read/touched outside the allowed roots (the run env + shared raw-data dir). Root-based policy: every answer (real data/gold, full mapping, answer key) is outside the env, every legitimate input is inside it, so no per-identifier rules are needed. Two correctness details: cwd-aware Bash parsing (track cd, resolve relative tokens) to avoid false positives on 'cd env && cat data/...'; and lexical (non-symlink-following) containment so reads via the env's metadata symlink count as in-bounds. Ignores /dev|/proc|/sys tokens; surfaces all Bash for human review; exits non-zero on violations to gate a run. Validated against the real fold_ds07 run trace: CLEAN (reproduces the manual audit). 8 tests + doctest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @cmungall's task —— View job PR Review: Add invigilator: anti-cheat trace audit for leave-one-out runsTasks:
Overall this is well-designed, clearly documented, and makes the right tradeoffs (lexical over Bugs / Correctness1. _PATH_TOKEN = re.compile(r"(?:~|\.{1,2}|/)?/?[\w.@+\-]+(?:/[\w.@+\-]+)+")A token requires at least one 2. _CD = re.compile(r"^\s*cd\s+(?:--\s+)?['\"]?([^'\"&;|]+?)['\"]?\s*$")The after splitting, the segment is The This is minor for current use, but could cause false-negatives (missed 3. for tok in _PATH_TOKEN.findall(seg):
if tok in ("&&", "||"):
continue
4. 5. Bash multi-line command typer.echo(f" $ {c.splitlines()[0] if c else ''}")Only the first line of multi-line commands is shown in Design / API Concerns6. repo_root: Path = typer.Option(Path.cwd(), "--repo-root", ...)
repo_root: Optional[Path] = typer.Option(None, "--repo-root", ...),7. The 8. The code handles Performance9. Recursive The nested Test Coverage10. Missing test: 11. Missing test: multi-segment cd /tmp && cd .runs/cfg && cat data/gold/secretMultiple 12. Missing test: A command like 13. ("Bash", {"command": f"cd {env} && python3 -c \"open('data/processed/m.json')\""}),The path Minor / Nits
Summary
The most actionable issues are #6 ( --- | Branch: |
- Fix repo_root default-arg bug: Option default None, resolve in body (was Path.cwd() evaluated at import time) [#6] - Audit the LS tool (path) [#8]; check Grep/Glob 'glob'/'pattern' for path-bearing patterns [#7] - Flag 'cd' into an answer location (answer key / real gold/processed), so a single-component read after it isn't missed [#1] - Remove dead '&& ||' token guard [#3] - --show-bash prints full multi-line commands instead of just the first line [#5] - Tests: LS, Grep glob, cd-into-answer-key, multi-cd chain, ~ expansion 53 tests green; real fold_ds07 trace still CLEAN. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the review — addressed in 680dfca. Summary: Fixed
Deliberately deferred (low value for current use, noted)
The |
|
Claude finished @cmungall's task —— View job Claude finished @cmungall's task —— View job PR Review: Follow-up on
|
| Category | Count |
|---|---|
| Previously deferred, still present | 2 |
| New issues found | 2 |
| Nits | 1 |
The core audit logic, cwd-tracking, lexical-containment, LS coverage, and multi-cd chain handling are all correct. The only actionable items before merge are the misleading test (#13) and optionally extracting the shared answer-key predicate. Everything else is low-severity.
--- | Branch: feat/invigilator
What
An anti-cheat invigilator (
src/folds/invigilator.py) that audits an agent run's tool-call trace (agent-<id>.jsonl) and flags any file the agent read or touched outside the allowed roots. This is the audit backstop for the isolation model: the.runs/<cfg>/env removes the held-out cluster's answers (absence), the agent is told the boundary (instruction), and this verifies it after the fact (audit).Policy: root-based
An access is in-bounds iff it resolves under the run env (
.runs/<cfg>/) or the shared raw-data dir (~/ess-dive_wfsfa_soil_datasets/). This works because every answer (the realdata/gold/, the full mapping with the held-out entry, any answer-key dir) lives outside the env, while every legitimate input (skills, ablated code, filtered mapping, the held-out dataset's metadata via the env symlink, raw CSVs) lives inside one of those roots — so no fragile per-identifier rules are needed. ReadsMANIFEST.jsonto pick up the held-outdataset_identifiers (used to annotate violations) and exits non-zero on any violation, so it can gate a run.Two correctness details (both caught by building it)
cdinto the env and use relative paths (cd .runs/<cfg> && cat data/processed/…). A substring match false-positives; we trackcdper command and resolve relative tokens against the effective cwd. (A naive first pass flagged these; this is the fix.)data/external/ess-dive_meta); following symlinks would point those reads back outside the env and wrongly flag them. We normalize paths lexically but don't follow symlinks, so accessing an input via the env is in-bounds while reaching it by its real outside path is flagged./dev|/proc|/systokens (e.g.2>/dev/null) and surfaces every Bash command verbatim for human review — it never claims a command was clean when it couldn't parse it.Validation
/dev/nullredirects, CLI exit codes). Full suite: 48 green.fold_ds07subagent trace: CLEAN, exit 0, 7/7 reads in-bounds — reproduces the earlier manual audit.Use
Reusable by the Step-3 runner (gate each run) and Step-4 scorer (record the audit verdict per run). The runner should also persist each run's trace next to its outputs so this doesn't depend on
~/.claude/projects/….Builds toward the leakage-audit dimension of the comparison-strategies discussion.