Three findings from an adversarial pass over the v0.2.34..v0.4.0 delta.
-
Shape clustering read anchored files without the containment rule the
sweep enforces. _anchored_tokens did root / path on a thread-supplied
path, so a symlinked or ../ anchor leaked tokens from files the diff
never touched into the merge decision — while the README extends the
"never reads outside the changed files" claim to cluster_findings.py,
and this same release shipped _contained_path for exactly this vector
in sweep_siblings.py. It now routes through the same helper: a path
that escapes yields no tokens, and the cluster falls back to prose. -
A stray file at ~/.config/gh-review-loop shadowed a legacy directory
full of user data. state_dir() used exists(), which is true for files,
so resolution picked a path that cannot hold state and the next write
crashed with an uncaught FileExistsError (exist_ok does not cover
non-directories) while preferences.json and runs.jsonl sat untouched
in the legacy dir. is_dir() falls back to the legacy data instead. -
The relocated "Why this exists" README section was pasted without a
blank line after the preceding paragraph.
Tests: symlink and ../ anchors produce no shape merge; a file at the new
state path resolves to the legacy directory. 1102 pass, replay byte-stable.
Summary by Sourcery
Harden pre-launch review processing against untrusted anchor paths and invalid state-directory shadowing.
Bug Fixes:
- Prevent shape clustering from reading anchored files outside the changed repository through symlink or parent-directory escapes.
- Resolve state storage to the legacy directory when the new state path is occupied by a file, preserving existing user data and avoiding write failures.
Documentation:
- Restore the missing spacing before the README's “Why this exists” section.
Tests:
- Add coverage for symlinked and parent-directory anchor escapes and file-shadowed state-directory resolution.