Skip to content

security(review): admit judge output as untrusted input; cache keyed by the exact prompts (#644) - #652

Merged
ajianaz merged 1 commit into
developfrom
security/644-untrusted-judge-output
Oct 15, 2026
Merged

ajianaz merged 1 commit into
developfrom
security/644-untrusted-judge-output

Conversation

@ajianaz

@ajianaz ajianaz commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #644. Part 3/6. Stacked on #651 (#649), which is stacked on #650 (#648): earlier commits here are those PRs; the diff shrinks as they merge.

What

  • judge::Packet now carries the finished prompts (llm::ReviewPrompts: hardened system prompt + user prompt with the fenced diff), so a judge that is not the HTTP LLM gets the same defenses.
  • judge::admit(issues, valid_files, source): the one gate for any judge's output. Clears rule_id/also_matches, truncates text (title 300, body/fix 8000 chars), caps at 200 findings (most severe kept), drops files outside the diff/batch, and turns a line the diff/file does not contain into a file-level finding. Applied in review_with and in cora scan per batch. Deterministic findings merge after, so a judge can add but never remove.
  • Cache moved behind HttpJudge: stores the raw model answer keyed by the exact prompts + model/provider/base_url/temperature.
  • cora scan wraps each batch in an untrusted-data fence (same as review).

Why

Defenses against prompt injection only existed inside the HTTP path, and anything a model returned flowed to formatters, SARIF upload and history unchecked: a forged rule_id feeds suppression and the deterministic cap. The old cache key (diff + model + provider + base_url + temperature) ignored focus, rules, profile, sanitize, ignore.rules, hook.min_severity and could serve a stale or other-mode result.

Testing

  • cargo fmt --check, cargo clippy --all-targets --features tree-sitter -- -D warnings, cargo test --features tree-sitter (1156 passed; new: 9 admit tests, hostile-output pipeline test, cache-key tests, scan fence tests).
  • Manual with a real LLM: miss, then hit (returning cached judge response), then add ignore.rules and confirm the finding disappears on a cache hit.
  • Not covered: sanitizer for scan (opt-in sanitize_comments stays review-only).

…by the exact prompts (#644)

judge::admit clears rule_id, bounds text and count, drops files and
lines the diff does not contain; applied to review and scan. The Packet
now carries the finished hardened prompts, and the review cache moves
behind HttpJudge keyed by those prompts, storing the raw model answer so
ignore.rules, min_severity and rules are applied on cache hits too.
Scan batches get an untrusted-data fence.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: ajianaz <ajianaz@users.noreply.github.com>
@ajianaz
ajianaz force-pushed the security/644-untrusted-judge-output branch from 1af7685 to e65774e Compare October 15, 2026 05:18
@ajianaz
ajianaz merged commit 2a51f34 into develop Oct 15, 2026
14 checks passed
@ajianaz
ajianaz deleted the security/644-untrusted-judge-output branch October 15, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security(review): treat judge output as untrusted input; self-contained Packet; correct cache key

1 participant