fix(module-25): close command-injection + lab-integrity issues

- agent-job.yml: pass untrusted issue body via env (BODY), never interpolated
  into a run: shell line (fixes GHA expression-injection). Adds security note.
- lab/.gitignore: keep propose_pr's `git add -A` from sweeping __pycache__ and
  copied scaffolding into the review diff.
- agent_runner.py: simulated reject() now removes the agent's untracked files
  (git restore can't), and the Module 2 restore line only prints for the real
  tracked-edit path.
- README: clarify --simulate uses a deterministic stand-in, not the delete issue.

Closes #24
Closes #25
Closes #26
Closes #27

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT
This commit is contained in:
2026-06-22 14:37:09 -04:00
parent 2684095e2f
commit c372e8452d
4 changed files with 65 additions and 9 deletions
+16 -3
View File
@@ -228,9 +228,21 @@ shows how the exact same flow runs on a real forge as a triggered/scheduled job.
don't have one wired up, the script's `--simulate` mode demonstrates every gate and loop
deterministically with no agent at all — do that first regardless.
> **What `--simulate` actually does — read this before Part A.** To stay deterministic and never
> touch your real `cli.py` / `tasks.py`, `--simulate` does **not** implement
> `issue-delete-command.md`. Instead it writes a small, self-contained stand-in (`agent_demo.py` with
> a `discount()` function, plus its test) and runs the *real* gate (ruff + pytest) against that. So
> Parts AC exercise the machinery and the gates — not the delete feature itself. The issue is only
> truly implemented in **Part D**, with a live agent. When you review the simulated diff you'll see
> the `discount()` demo, not a `delete` command; that's expected, and it's why the simulation is
> reproducible enough to teach with.
### Part A — See the gate catch a bad change (simulated, no agent needed)
Copy `agent_runner.py` and `issue-delete-command.md` into your `tasks-app` folder. Then, from a clean
Copy `agent_runner.py` and `issue-delete-command.md` into your `tasks-app` folder, along with this
module's `lab/.gitignore` (append its lines to the `.gitignore` you already have from Module 2 rather
than overwriting it). Commit that `.gitignore` first — it keeps the lab scaffolding and Python caches
out of the agent's `git add -A`, so the change you review in Part B is clean. Then, from a clean
branch:
```bash
@@ -254,8 +266,9 @@ python agent_runner.py issue-to-pr issue-delete-command.md --simulate good
This time the planted change is correct. The gate passes, the script commits to the branch and prints
the diff for review plus the exact `git push` / open-PR command. **It does not merge.** Open the diff
and review it with the Module 10 checklist — you are the human gate, and that step doesn't go away
just because an agent did the typing.
and review it with the Module 10 checklist. Remember (from the note above) that the simulated diff is
the self-contained `discount()` stand-in, not a `delete` command — but the review *motion* is the real
lesson: you are the human gate, and that step doesn't go away just because an agent did the typing.
### Part C — Run the self-healing loop