Skip to content

security-guidance: resolve review paths against the repo root and harden the findings-array contract#76274

Open
ss251 wants to merge 2 commits into
anthropics:mainfrom
ss251:fix/security-guidance-review-paths
Open

security-guidance: resolve review paths against the repo root and harden the findings-array contract#76274
ss251 wants to merge 2 commits into
anthropics:mainfrom
ss251:fix/security-guidance-review-paths

Conversation

@ss251

@ss251 ss251 commented Jul 10, 2026

Copy link
Copy Markdown

Summary

  • The security-guidance plugin's agentic reviewers (background reviewer and commit reviewer) list changed files in their prompts exactly as parsed from diffs: relative paths with no repo root, root-anchored repo paths (/TaxEngine/Package.swift), and foreign absolute paths from other machines (/home/user/..., /Users/<other-dev>/...). The reviewer model then guesses absolute paths and burns a failed Read round-trip in nearly every run (Background security reviewer guesses wrong absolute paths (first Read always fails) and intermittently fails findings schema #74200), or loops on File does not exist for foreign paths (security-guidance plugin: commit reviewer Reads foreign/non-local paths from diffs, looping on 'File does not exist' #73271).
  • This PR adds a shared path normalizer in hooks/review_api.py: changed paths are resolved against the active worktree root, verified (realpath + os.path.commonpath containment + isfile) to remain inside it, and only verified absolute paths are emitted in the "you may Read these" list. Root-anchored and foreign-checkout paths are recovered via the longest existing repo suffix (minimum two path components — never a bare basename, so a same-named file elsewhere can't be silently substituted). Unresolvable paths are listed separately as not present in this checkout.
  • The generated prompt now states the absolute repository root, and llm.agentic_review uses the same public prompt builder as external callers, so the two prompt paths can't drift.
  • The structured-output contract for findings is hardened on both sides: the prompt/schema now explicitly require findings to be a JSON array for zero, one, or many results, and a tolerant client-side guard (_findings_list) normalizes a singleton findings object into a one-item list at both consumption sites — previously that shape iterated dict keys and silently produced zero findings.

Root cause

The prompt builders passed through whatever paths diff parsing produced, with no worktree anchoring or containment guard, and the findings contract relied on the model always emitting an array.

Validation / Evidence

  • 8 dependency-free stdlib unittest cases (hooks/test_review_paths.py) exercising the real runtime functions: relative-path absolutization, foreign-path exclusion + labeling, root-anchored recovery, foreign-checkout suffix recovery, bare-basename collision NOT mapped, repo-root + guarded-path prompt rendering, findings-array prompt/schema requirements, singleton-findings coercion.
  • Tests were written first and captured failing against the unmodified code; python3 -m py_compile clean on all touched files; all 8 tests pass.
  • Adversarial containment checks were run during review: /repo-evil/secret.py vs root /repo (excluded — commonpath, not string prefix), ../outside.py (excluded), symlink-inside-repo-pointing-outside (excluded via realpath), absolute path with .. resolving back inside (correctly readable).
  • Before/after prompt demonstration: old prompt emits TaxEngine/Package.swift and /home/user/App/missing.py verbatim with no root; new prompt states the absolute repo root, one verified absolute path, and one labeled-absent path.

Notes for reviewers

  • The suffix-recovery heuristic is best-effort by design: with the two-component minimum, it only maps when the intended relative path actually exists in this checkout; everything else is excluded and labeled rather than guessed.
  • The findings hardening reduces (client-side guard eliminates the observed singleton-object case) but cannot eliminate all schema-validation failures, since the model can still emit other malformed shapes.

Closes #74200
Closes #73271


AI-assisted: implemented by a Codex (gpt-5.6-terra) worker agent and independently reviewed by a fresh-context Claude Opus verifier (including the adversarial containment probes above), both operating under human supervision. We run this plugin ourselves; happy to iterate.

ss251 and others added 2 commits July 10, 2026 10:30
… findings array

Normalize changed-file paths in the security reviewer prompts: resolve
relative paths against the active worktree root, recover root-anchored
and foreign-checkout paths via existing repo suffix, exclude unresolved
paths from the readable list, and state the absolute repo root in the
prompt. Require the structured findings field to be a JSON array for
zero, one, or many results.

Fixes the first-Read-always-fails and foreign-path Read loops
(anthropics#74200, anthropics#73271).

AI-assisted (Codex gpt-5.6-terra worker, Claude-supervised).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8NGaAFCBE4HLGLojEwvvy
…ngleton findings

Address cross-provider review: never map foreign paths on a bare
basename match (wrong-file false confidence); normalize a singleton
findings object to a one-item list at both consumption sites; make
the empty-readable-paths prompt wording accurate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8NGaAFCBE4HLGLojEwvvy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant