Repository navigation
fix(review): stop model-written text quoting leaked secrets and personal data - #706
Merged
Merged
Conversation
…nal data When a change commits a credential or a personal or financial record, reviewers quoted the value in their findings, so the review republished the leak on the pull request. Add one shared output rule, llm.SensitiveValueRule, to every prompt whose model text cr posts or logs: the reviewer findings contract (which coverage repair inherits), the rollup and selection contracts, thread analysis replies and summaries, and the dossier discussion summary. The model names the kind of value and its location and masks any fragment to at most its last 2 characters, instead of reproducing it. Contract tests assert the rule in each prompt, and a dry-run test drives a review through every model stage and checks each prompt the adapter receives. The rule fixes the source; there is no after-the-fact redactor, since a pattern cannot tell a real identifier from an example. Twapp-Session: 59d912f3-982f-402e-8bd8-3e31269a28b0
piekstra-dev
approved these changes
Nov 3, 2026
piekstra-dev
left a comment
Collaborator
There was a problem hiding this comment.
Automated PR Review
Reviewed commit: 24f9028be347
Profile: reviewer - Posting as: piekstra-dev
Summary
| Reviewer | Findings |
|---|---|
| go:implementation-tests | 0 |
| structure:repo-health | 0 |
| policies:conventions | 0 |
| security:code-auditor | 0 |
| documentation:docs | 0 |
| go:implementation | 0 |
Reviewer Coverage
Complete coverage details are retained in the local coverage.json artifact.
go:implementation-tests— complete (broad); scope: 7 files; examples:internal/dossier/dossier.go,internal/dossier/dossier_test.go,internal/llm/disclosure.go,internal/pipeline/prompts.go,internal/pipeline/sensitive_value_rule_test.go; 2 omitted; body-inspected: 7 files; examples:internal/dossier/dossier.go,internal/dossier/dossier_test.go,internal/llm/disclosure.go,internal/pipeline/prompts.go,internal/pipeline/sensitive_value_rule_test.go; 2 omitted; skipped: none; constraints: Reviewed from the diff between base and head; tests were not executed.structure:repo-health— complete (broad); scope: 5 files; examples:docs/checkout-native-review-contract.md,internal/dossier/dossier.go,internal/llm/disclosure.go,internal/pipeline/prompts.go,internal/threadanalysis/threadanalysis.go; body-inspected: 5 files; examples:docs/checkout-native-review-contract.md,internal/dossier/dossier.go,internal/llm/disclosure.go,internal/pipeline/prompts.go,internal/threadanalysis/threadanalysis.go; skipped: none; constraints: Reviewed the diff only; tests were not run. The new test file and README are outside assigned scope and were seen only as diff context.policies:conventions— complete (broad); scope: 2 files; examples:README.md,docs/checkout-native-review-contract.md; body-inspected: 2 files; examples:README.md,docs/checkout-native-review-contract.md; skipped: none; constraints: Shared cli-common docs were not available in context; review limited to repo-local docs consistency in the two assigned files. Code and tests were not in this assignment's scope.security:code-auditor— complete (broad); scope: 4 files; examples:internal/dossier/dossier.go,internal/llm/disclosure.go,internal/pipeline/prompts.go,internal/threadanalysis/threadanalysis.go; body-inspected: 4 files; examples:internal/dossier/dossier.go,internal/llm/disclosure.go,internal/pipeline/prompts.go,internal/threadanalysis/threadanalysis.go; skipped: none; constraints: Reviewed diffs only; did not run tests or confirm that the llm import exists in each edited file.documentation:docs— complete (broad); scope: 2 files; examples:README.md,docs/checkout-native-review-contract.md; body-inspected: 2 files; examples:README.md,docs/checkout-native-review-contract.md; skipped: none; constraints: Checked the docs against internal/llm/disclosure.go and the PR intent. Did not independently verify each prompt-to-field mapping in the table against the other Go call sites.go:implementation— complete (broad); scope: 4 files; examples:internal/dossier/dossier.go,internal/llm/disclosure.go,internal/pipeline/prompts.go,internal/threadanalysis/threadanalysis.go; body-inspected: 4 files; examples:internal/dossier/dossier.go,internal/llm/disclosure.go,internal/pipeline/prompts.go,internal/threadanalysis/threadanalysis.go; skipped: none; constraints: none
0 PR discussion threads considered. 0 summarized; 0 resolved.
Completed in 1m 27s | ~$0.56 (est.) | claude-sonnet-5-5 | cr 0.10.362
| Field | Value |
|---|---|
| Model | claude-sonnet-5-5 |
| Reviewers | go:implementation-tests, structure:repo-health, policies:conventions, security:code-auditor, documentation:docs, go:implementation |
| Engine | claude_cli · claude-sonnet-5-5 |
| Reviewed by | cr · piekstra-dev |
| Duration | 1m 27s wall · 2m 52s compute |
| Cost | ~$0.56 (est.) |
| Pricing basis | baseline-2026-10-02/pricing |
| Tokens | 64 in / 8.8k out |
Per-workstream usage
orchestrator-selection— claude-sonnet-5-5- In: 6
- Out: 1.7k
- Cache read: 24.4k
- Cache create: 13.3k
- Cost: ~$0.07 (est.)
- Duration: 17s
go:implementation-tests— claude-sonnet-5-5- In: 8
- Out: 1.1k
- Cache read: 46.3k
- Cache create: 16.1k
- Cost: ~$0.08 (est.)
- Duration: 25s
structure:repo-health— claude-sonnet-5-5- In: 10
- Out: 1.3k
- Cache read: 63.2k
- Cache create: 14.9k
- Cost: ~$0.09 (est.)
- Duration: 30s
policies:conventions— claude-sonnet-5-5- In: 8
- Out: 976
- Cache read: 35.0k
- Cache create: 15.0k
- Cost: ~$0.08 (est.)
- Duration: 21s
security:code-auditor— claude-sonnet-5-5- In: 8
- Out: 970
- Cache read: 42.9k
- Cache create: 12.4k
- Cost: ~$0.07 (est.)
- Duration: 23s
documentation:docs— claude-sonnet-5-5- In: 8
- Out: 1.1k
- Cache read: 42.2k
- Cache create: 12.0k
- Cost: ~$0.07 (est.)
- Duration: 26s
go:implementation— claude-sonnet-5-5- In: 10
- Out: 1.2k
- Cache read: 62.4k
- Cache create: 14.1k
- Cost: ~$0.08 (est.)
- Duration: 18s
orchestrator-rollup— claude-sonnet-5-5- In: 6
- Out: 430
- Cache read: 52.6k
- Cache create: 2.5k
- Cost: ~$0.02 (est.)
- Duration: 8s
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When a PR commits a credential or a personal or financial record (an account number, a balance, a name), reviewers quote the value verbatim in their findings. On a public repository those comments are public, so the review republishes the leak it found. Only a catalog reviewer whose own prompt forbade quoting avoided it. Every other reviewer, and every other catalog, did not.
Change
One shared output rule,
llm.SensitiveValueRule(internal/llm/disclosure.go), is added to every prompt whose model-written text cr posts or logs:findingsOutputContractWithRelocations); coverage repair inherits itconstraints(rollup diagnostics)cr reviewandcr respond)reply_bodyandsummary, posted as thread repliesreview_event_rationale,dedupe_logreasons (run artifacts)reasoningand per-reviewerrationale(progress logs)The rule tells the model to name the kind of value and where it is (file, line, JSON path or key) instead of reproducing it. If a fragment is needed, it masks the value to at most its last 2 characters ("account number ending ..21"). The rule applies even when the value is the subject of the finding.
The change adds no regex redactor. A pattern cannot tell a real identifier from an example, and the problem is fixed at its source.
Prompt fingerprints include the prompt text, so cached tasks from earlier runs are invalidated and rerun under the new contract.
Tests
TestOutputContractsCarrySensitiveValueRulechecks the selection (both budget modes), findings (plain and with relocation and symlink assignments) and rollup contracts.TestEveryReviewPromptCarriesSensitiveValueRulesends one dry-run review through all six model stages: dossier summary, selection, thread analysis, reviewer, coverage repair and rollup. It asserts that every prompt the adapter receives carries the rule.TestPromptForInputCarriesSensitiveValueRule(threadanalysis) andTestDossierDiscussionSummaryPromptCarriesSensitiveValueRule(dossier) are package-level checks.make checkpasses.Docs
docs/checkout-native-review-contract.md.Eval
The
privacy-inline-jsoncase inpiekstra/cr-reviewer-catalog(.codereview/benchmarks/review-lenses.yml, branchfeat/review-lenses) measures this. Rerun it against this build once the reviewer profile's credential is available.