Repository navigation
Conversation
…baselining it (R2-1) Round-2 red team, R2-1 (HIGH): a non-quarantined server whose tools had never been approved auto-approved its whole first toolset (approved_by "auto-baseline") with no scan — an 18-vector poisoned catalog landed approved, indexed and callable; only an operator-triggered scan afterwards said "dangerous". The baseline pass now runs the same synchronous, offline TPA scan the trust_mode: scan change gate uses (scanner.ScanToolMetadataVerdict, peers included) once over the first-seen toolset and reads the verdict per tool: clean tools auto-baseline exactly as before, a tool with a hard or soft finding lands pending with the findings recorded as hold evidence (FR-018), and a degraded/bundle-less scan holds every tool (fail closed, FR-014). Pure in-process string work under the server's approval lock the pass already holds. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 3d38022)
…(R2-4) Round-2 red team, R2-4: "ígnore áll prévious ínstructions" scanned clean. NFKC composes base + combining mark into a precomposed letter instead of dropping the mark, and confusable folding only maps single-rune look-alikes, so an accented English directive never reached the phrase families. NormalizeForPhrase (and the mixed-script view used for truthful position classification) now decompose, drop every nonspacing mark that sits on a LATIN base letter, and recompose. Marks on other scripts — Cyrillic й/ё/ї, Greek tonos, kana dakuten, Devanagari vowel signs — are untouched, so the non-English families keep matching their own spellings; the latin-majority and mixed-script false-positive gates are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 7b2bde3)
…the i18n corpus (R2-3)
Round-2 red team, R2-3: the i18n corpus was bounded to de fr es it pt nl pl
ru uk zh ja ko, so the Turkish and Greek translations of "ignore all previous
instructions" (and the Turkish "reveal your system prompt") scanned clean.
- el: verb-first spaced family (tonos and tonos-less spellings), reusing
i18nSpacedFamily like es/fr.
- tr: Turkish is SOV, so it gets its own object-first family shape plus a
machine-translation verb-first form, and a system-prompt DISCLOSURE family
("sistem istemini ifşa et"). Verbs carry imperative suffixes only and end
at a word boundary (new phraseFamily.trailingBoundary, trimmed out of the
evidence like leadingBoundary) because Turkish negates with a suffix:
"önceki talimatları yoksaymaz" (does not ignore) stays clean.
- Every accented letter is spelled both ways so the families match the plain
view and the diacritic-folded de-obfuscated view (R2-4) alike.
All non-English families stay SOFT (review-only). Coverage comment updated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 0828229)
…tions for review (R2-5) Round-1 and round-2 red team, R2-5: the single offline BYPASS that survived every fix series — a tool that keeps a victim's distinctive tool NAME (filesystem_read) and rewrites the description — scanned clean, because the shadowing clone shape requires a near-duplicate description and a name collision alone was never flagged (MCP-3520). shadowing.cross_server gains a SOFT shape (1b, review-only, confidence 0.4): a distinctive name another server also exposes, with a genuinely different description, is surfaced — it holds a trust_mode: scan change and a first-seen baseline tool (R2-1 gate) but never auto-quarantines — UNLESS the description names its own server (provenance: "Lists ElevenLabs voices", "Browse Kaggle's registry"), which is how honest servers that share a compound name describe themselves and what an impersonator cannot afford to write. The MCP-3520 pair stays clean under that rule; common/short names and identical sub-floor descriptions are unchanged (no signal). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit fd25827)
…utations and the R2 experiment script - cmd/tpa-redteam: translate-turkish, translate-greek, translate-korean, combining-diacritics, translate-turkish-combined mutations (R2-3/R2-4 probes). - scripts/tpa-lab-redteam-round2.sh: R2-1 auto-baseline poisoning and R2-2 unquarantine-race experiments against a real daemon with Docker-isolated upstreams (same safety model as the round-1 suite). - handoff prompt for the round-2 verification/fix engagement. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit e64b381)
…line passes (R2-1 follow-up) The EXP-4 TOCTOU lab run exposed a gap in the first R2-1 fix: when EVERY first-seen tool is held by the baseline scan, no baseline marker is written (FR-025), so the next identical tools/list is again a baseline pass and the "stranded pending record, unchanged since recorded" promotion (MCP-2931 #4) approved the held tool as auto-baseline and cleared its scan evidence. The promotion now also requires the baseline scan to be green for the tool. The hold evidence on a pending record is refreshed against the LIVE contract on every baseline pass, so it always describes what the operator sees: a poisoned contract stays pending with its evidence across any number of repeats; a server that swaps to a clean contract gets only that clean contract baselined (once seen unchanged), and a swap back is a rug pull. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 9ecc8b0)
… name squat is review-only - EXP-4 (TOCTOU) premised on discovery APPROVING the poisoned list-1 contract. Since R2-1 that contract is scanned and held at discovery, so the precondition now reads the daemon log too: 'held' (both poisoned tools held with a dangerous verdict) is the HOLDS premise, 'yes' (a poisoned first-seen contract approved) is scored HOLE as an R2-1 regression, and 'no' stays INCONCLUSIVE. Stricter than before: the hole is caught one gate earlier and an approved poisoned contract can no longer score HOLDS. - lab_test: the M05 name squat must be surfaced for review (soft) and must never be HARD (MCP-3520 unchanged). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit bc01f01)
…ing record already carries Follow-up to the repeat-pass gate: the baseline gate only WRITES evidence (why the live contract is held); a clean verdict on a pass that does not promote leaves existing evidence alone — a legacy collapsed lock adopted with its scan signals (Spec 105 FR-009), or the refused first contract of a TOCTOU swap — so the review screen keeps showing what was refused until promotion (ClearScanHold) or an operator approval clears it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit add399e)
Adversarial review (5 lenses, 3 skeptics per finding) of the round-2 series: - R2-4: decompose (NFD) BEFORE homoglyph folding and strip Latin-base marks AFTER it, so a combining mark on a Cyrillic/Greek look-alike base (а́ll, ignόre) folds to the plain word instead of leaving a residual accented Latin letter that downgraded the override to the squashed soft pass. Pure-ASCII fast path; non-ASCII text is always recomposed. - The mixed-script view's finding detail says 'diacritics on Latin letters' when no word mixes scripts (detect.HasMixedScriptWords); the #1589 line-break cap folds the raw word stream the same way as the mixed view so an accented matched word no longer escapes the cap. - R2-3 Greek: final sigma is matched as [σς] (strings.ToLower maps Σ to σ word-finally, so an ALL-CAPS override scanned clean). - R2-3 Turkish: 'edin/ediniz' imperative of 'et' compounds (ifşa edin, iptal edin, göz ardı edin, reddedin); bare-ASCII 'paylas' survives Normalize's stem(); verbal nouns that double as negative imperatives (uyma, izleme, uygulama, takma, umursama, dikkate alma) count only in the polite form or clause-finally; 'mevcut' dropped from the qualifiers; 'istem' spelled with its case endings so 'istemci' (client) does not match; the disclosure family takes 'göster/yazdır/açıkla/tekrarla' only on the prompt noun or after 'gizli', so 'Sistem mesajlarını göster' stays clean. - cmd/tpa-redteam: mutation descriptions no longer claim tr/el/ko are uncovered. - scripts/tpa-lab-redteam-round2.sh: R2-1 HOLDS requires BOTH poisoned vectors pending-or-worse AND a blocked call; an empty or unparsed status is INCONCLUSIVE, an approved vector is a HOLE. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit d8c22a5)
…ter the Sol 6.1 cross-review
opencode gpt-6.1-sol REQUEST_CHANGES, 7 findings; 6 fixed, 1 refuted:
- (refuted, pinned) trust_mode: auto is a lifted gate (IsQuarantineSkipped →
!enforceNewTools), so it never reaches the baseline scan and the new-record
and pending-record branches cannot disagree under auto; the scan is the
manual/scan-mode baseline's gate by design. Test
TestCheckToolApprovals_AutoTrustMode_IsNotScanGated.
- Turkish disclosure family: 'istem' with bounded case endings, so 'Sistem
istemcisini göster' (show the system client) stays clean.
- exp4_poison_recorded honours the record status: an APPROVED poisoned
summarize_text is 'yes' (R2-1 regression), a PENDING/changed one is the
hold itself ('held'); the export lists pending records too.
- scripts/tpa-lab-redteam-round2.sh: sources the lab lib; lab_scrub_mcpproxy_env
before anything runs; --data-dir/--listen pinned; lab_config_require before
serve; lab_config_use_image so IMAGE is the image under test; fresh mktemp
run dir per invocation (round2-latest symlink) so R2-1 is always first-seen;
lab_port_busy preflight without killing; result lines printed as they land;
fail-closed exit via suite_exit_status.
- Lab tests: round-2 runner ignores inherited overrides and pins flags,
refuses an unchecked template without invoking the binary, uses a fresh run
dir per invocation; EXP-4 tables carry real export statuses.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 609c5d8)
…oval of either is the regression Sol 6.1 round-2 review, finding 1: the stored-record path set 'held' from summarize_text alone, so a run that held the summary but approved the poisoned convert_currency scored HOLDS. A second marker identifies the poisoned currency contract; 'yes' (HOLE) on an approved poisoned record of either tool takes precedence, 'held' needs both tools pending/changed with their poisoned descriptions (or the daemon-log line that already required both), and mixed-status cases are pinned. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit a2ba913)
…e and name-squat shape in the feature docs - round2-2026-10-20.md: per-finding before/after, the commands run and their output, EXP-4 under R2-1, the unit tests added, the adversarial review round (20 confirmed findings and their outcomes), the three opencode gpt-6.1-sol cross-review rounds verbatim (REQUEST_CHANGES ×2 → SHIP), and what was considered but not implemented (llm_judge backstop, whole-server hold, the plain unquarantine door, the P4 exemption limits). - security-quarantine.md / tool-quarantine.md: the trusted first-seen baseline is scanned before it is trusted; flagged tools land pending with evidence; the server-approval promotion does not re-scan. - tool-scanner.md / security-quarantine.md / agent-tokens.md: the soft name-squat shape of shadowing.cross_server and its provenance exemption. - CLAUDE.md: one clause on the scanned baseline. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit f349c77)
…ntract under the baseline gate On main the Spec 118 evidence scan (#1689) re-describes a pending record whose definition moved, so a TOCTOU swap to a clean contract reads awaiting_review (current definition) rather than keeping the refused contract's scan_findings. The baseline gate still never erases evidence itself; the assertion now pins the merged behaviour. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…log line #1689 reworded the synchronous scan's non-green hold line (one line now for the scan-mode gate and the manual-mode evidence scan), so EXP-6b/6c's scan-gate proof came back INCONCLUSIVE on current main. The probe accepts the new wording alongside the old; the generic 'changed since approval' line still does not count. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Deploying mcpproxy-docs with
|
| Latest commit: |
d0e9a17
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://914026c8.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://fix-tpa-round2-protections.mcpproxy-docs.pages.dev |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Contributor
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 39473821545 --repo smart-mcp-proxy/mcpproxy-go
|
This was referenced Oct 22, 2026
Dumbris
pushed a commit
that referenced
this pull request
Oct 22, 2026
# Pull Request ## Description Round 3 of the UTF-8-safe truncation sweep started in #1647 and continued in #1664 / #1665. Three production sites still cut text with a raw byte slice (`s[:n]`), so a non-ASCII value whose cut offset lands inside a multi-byte rune is published as invalid UTF-8, and JSON encoding rewrites the tail to U+FFFD. ### 1. `internal/storage` — the activity-log per-record cap `truncateResponse` is the only thing bounding a single activity record. It now goes through `truncateRuneSafe`, the helper the per-server tool-call records in the *same package* (`internal/storage/tool_call_bounds.go`) already use for exactly this reason. **Reachability.** An upstream tool response arrives as the `response` payload of an activity event, and `ActivityService.handleEvent` → `truncateForStorage` → `storage.TruncateActivityResponse` → `truncateResponse` applies `activity_max_response_size` (default 64 KB) on the write path (`internal/runtime/activity_service.go:267`). Any response over the cap whose byte at the cut offset is a UTF-8 continuation byte — ordinary CJK, Cyrillic, or emoji text — lands in `config.db` malformed, and from there in `GET /api/v1/activities`, `GET /api/v1/activities/{id}` and the CSV export. With a 3-byte rune, 2 of every 3 cut offsets are mid-rune. ### 2. `internal/outputvalidation` — the 500-byte violation `Reason` A `jsonschema` message quotes the offending instance location, so a non-ASCII property name or value sits astride the cut. **Reachability.** `Validator.Validate` calls `truncate(err.Error(), 500)` (`validator.go:157`) on any structured tool output that fails its `outputSchema`; the `Reason` goes back to the agent and into the audit line. Measured on the pre-fix code with a 200-rune non-ASCII property name, shifting the cut by one byte at a time: offsets 0 and 2 produce invalid UTF-8, offset 1 is valid — i.e. 2 of 3. ### 3. `internal/registries` — the 300-byte Pulse description cap Registry descriptions are upstream-authored free text. **Reachability.** `parsePulseWithoutGuesser` fills `ServerEntry.Description` from `short_description` / `EXPERIMENTAL_ai_generated_description` for every result of a Pulse registry search, which the REST API and the Web UI catalog then JSON-encode. Same 2-of-3 pattern in the test. The 300 literal is now a named constant (`pulseDescriptionMaxBytes`) since it appeared twice. ASCII behaviour is unchanged at all three sites, and the retained prefix never exceeds the cap. **Out of scope,** deliberately, so this stays reviewable: - The CLI table cells in `cmd/mcpproxy` that #1664 also left alone — sent separately. - `cmd/mcpproxy/security_cmd.go` and `cmd/mcpproxy/main.go` have the same shape but are under open PRs (#1714, #1720, #1725), so I have not touched them. - `internal/configimport.SanitizeServerName` and `internal/dockernaming.SanitizeServerName` also cut at a byte offset, but both strip everything outside `[A-Za-z0-9._-]` first, so no multi-byte rune can reach the cut. Left as they are. ## Testing - [x] I have tested these changes locally - [x] I have added/updated tests that prove my fix is effective or my feature works - [x] All existing tests pass New tests, each failing by name at the base SHA (`14cb288`) and passing after: | Test | Package | |---|---| | `TestTruncateResponseKeepsRuneBoundary`, `TestTruncateResponseASCIIUnchanged` | `internal/storage` | | `TestViolationReasonIsValidUTF8`, `TestTruncateASCIIUnchanged` | `internal/outputvalidation` | | `TestPulseDescriptionTruncationIsValidUTF8` | `internal/registries` | Each boundary test sweeps every offset inside a 3-byte rune rather than asserting one fixed cap, so it cannot pass by luck. Commands run (exit status in brackets): ``` git diff --check [0] gofmt -l internal/storage internal/outputvalidation internal/registries [0, no output] go vet ./internal/storage ./internal/outputvalidation ./internal/registries [0] go test ./internal/storage ./internal/outputvalidation ./internal/registries [0] go test ./internal/runtime -run 'TestToolCallResponseTruncatedForStorage|TestInternalToolCallResponseTruncatedForStorage|TestStorageTruncatedBuiltinStillCountsDeliveredBytes|TestPromptGetResponseTruncatedForStorage|TestDefaultResponseCapAppliesWithoutConfig|TestEmitterTruncationFlagSurvivesUntruncatedRecord' [0, 6/6 pass] ``` The `internal/runtime` selection is the end-to-end activity-truncation suite that drives the storage cut through the real event path, included to show the write-path behaviour and the `ResponseTruncated` flag semantics are unchanged. Not run: `./scripts/run-all-tests.sh` and `-race` (local disk constraints on this machine). Nothing here is concurrent.
Dumbris
pushed a commit
that referenced
this pull request
Oct 22, 2026
# Pull Request ## Description Finishes the UTF-8-safe truncation sweep of #1647 / #1664 / #1665 in the part of `cmd/mcpproxy` those PRs explicitly set aside ("I left the CLI table description truncations in `cmd/mcpproxy` alone to keep this focused" — #1664). Companion to #1726, which covers the remaining `internal/` sites. Four table cuts existed as independent copies of the same arithmetic, and all four cut at a raw byte offset: | Site | Column | Data | |---|---|---| | `shorten` (`profile_cmd.go`) | 30 | `EffectiveServers`, comma-joined | | `truncateStr` (`registry_cmd.go`, `catalog_cmd.go`) | 60 / 45 / 40 | registry `description`, catalog `Title` | | inline `desc[:57] + "..."` (`upstream_cmd.go`) | 60 | upstream tool `Description` | | inline `result[:maxLen-3] + "..."` (`token_cmd.go`) | 23 | `allowed_servers`, comma-joined | **Reachability.** Every one of these cells is upstream- or operator-authored text: - `mcpproxy registry list` / `registry search` print descriptions straight from the registry API response (`mapString(r, "description")`), and `catalog list` prints `r.Title`. Both are free text from a public registry. - `mcpproxy upstream tools` prints the tool `Description` advertised by the upstream server. - `allowed_servers` and `EffectiveServers` are server names, which the config only forbids from being empty or containing `:` (`internal/config/server_edition_config.go:176`) — a non-ASCII name is legal. A value longer than its column whose byte at the cut offset is a UTF-8 continuation byte ends in a partial rune on the terminal. With a 3-byte rune, 2 of every 3 offsets are mid-rune; the test sweeps all three at each of the six real column widths. All four now go through one `truncateCell` built on `stringutil.UTF8Prefix`, in a file of its own so a fifth table does not grow a fifth copy. `joinInterfaceSlice` keeps `maxLen == 0` meaning "do not truncate". ASCII output is byte-for-byte unchanged and the result still never exceeds the column width — both asserted. **Left alone, deliberately:** - `cmd/mcpproxy/security_cmd.go` and `cmd/mcpproxy/main.go` have the same shape but are touched by open PRs (#1714, #1720, #1725), so I have not gone near them. Happy to follow up once those land. - `activity_cmd.go`'s `actType[:12]` and `upstream_cmd.go`'s `hash[:12]` cut an internal activity-type enum and a hex digest, where no multi-byte rune can reach the boundary. Not touched — a guard there would protect against a state the code cannot produce. ## Testing - [x] I have tested these changes locally - [x] I have added/updated tests that prove my fix is effective or my feature works - [x] All existing tests pass New tests in `cmd/mcpproxy/table_cell_utf8_test.go`: - `TestTruncateCellKeepsRuneBoundary` — 6 real column widths x 3 byte offsets. - `TestTruncateCellASCIIUnchanged` — pins the ASCII output of all four removed helpers, including the `maxLen <= 3` edge. - `TestJoinInterfaceSliceTruncationIsRuneSafe` — drives the composition (`joinInterfaceSlice` -> `truncateCell`), not just the helper, and pins `maxLen == 0`. **Mutation check.** With `truncateCell` reverted to the raw byte cut (`s[:maxLen-3] + "..."`) the two boundary tests fail by name — 12 sub-cases in `TestTruncateCellKeepsRuneBoundary`, plus `TestJoinInterfaceSliceTruncationIsRuneSafe` — while `TestTruncateCellASCIIUnchanged` still passes, which is what shows the change is behaviour-preserving for ASCII. Restoring the fix returns the package to green. Commands run (exit status in brackets): ``` gofmt -l cmd/mcpproxy [0, no output] git diff --check [0] CGO_ENABLED=0 go vet ./cmd/mcpproxy [0] CGO_ENABLED=0 go build ./cmd/mcpproxy [0] CGO_ENABLED=0 go test -count=1 ./cmd/mcpproxy [0, whole package] ``` `CGO_ENABLED=0` because this machine's Command Line Tools SDK is currently broken for cgo linking (`arm64e.x1-macos: unknown architecture` out of `CoreFoundation.tbd`), which is unrelated to this change. Nothing here is cgo-dependent. I did not run `-race` or `./scripts/run-all-tests.sh` (local disk constraints); none of this code is concurrent.
This branch has not been deployed
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.
Summary
Round-2 TPA red team (2026-10-20) against the hardened detect engine and tool-level quarantine: every finding was reproduced on this machine first, then closed with one commit-sized change per component, each verified end to end on the local instance and cross-reviewed. Report:
docs/research/2026-10-11-tpa-attack-lab/round2-2026-10-20.md(spec, evidence, post-fix results, review rounds).R2-1 HOLE(22 approved rows, poisoned tool called)internal/runtime/tool_quarantine.go: the first-seen toolset is scanned once withscanner.ScanToolMetadataVerdict(peers included, the same fail-closed seamtrust_mode: scanuses); verdict read per tool — clean →auto-baseline, hard/soft finding → pending with hold evidence, degraded scan → every tool pending. Follow-ups: the stranded-pending promotion (MCP-2931 #4) is gated on the same scan (an all-held toolset writes no marker, so the next identical list used to promote it); a clean verdict never erases existing hold evidenceR2-1 HOLDS(both poisoned vectors pending, benign controls still auto-baseline, call BLOCKED)phrase_injection_i18n.go:elverb-first family (final-sigma-insensitive);trSOV override + verb-first + system-prompt disclosure families, imperative suffixes only, consumed trailing boundary (phraseFamily.trailingBoundary) so suffix negation stays clean; corpus now de fr es it pt nl pl ru uk el tr zh ja kodeobfuscate.go: NFD → homoglyph fold → strip Mn on Latin bases → NFC, on the phrase view and the mixed-script view; other scripts' marks untouchedshadowing.go: SOFT shape 1b — distinctive shared name, genuinely different description, no own-server provenance in the text; MCP-3520 pair unchangedOffline harness: 79 blocked / 40 downgraded / 8 BYPASS → 83 / 44 / 0 BYPASS, every class change upward.
Verification (all on this machine)
go build ./...,go vet,go test ./internal/runtime ./internal/qapilot/... ./internal/security/... ./internal/storage/... ./tests/tpa-attack-labgreen; CI-configgolangci-lint(bare and-tags server) clean except the pre-existingevent_bus.go:1020reflect.Ptr nit (untouched).scripts/tpa-lab-redteam-experiments.sh(fresh state, full rebuild):suite result: PASS (every experiment holds)— EXP-4 (TOCTOU) now holds at discovery and was re-premised accordingly (an approved poisoned first-seen contract is scored HOLE).SKIP_BUILD=1 scripts/tpa-lab-redteam-round2.sh:R2-1 HOLDS,R2-2 HOLDS,round-2 result: PASS.go run ./cmd/tpa-redteam:0 BYPASS, 0 baseline-miss.Review
gpt-6.1-solread-only cross-review, 3 rounds:REQUEST_CHANGES(7, 6 fixed + 1 refuted with a pinning test) →REQUEST_CHANGES(1, fixed) →VERDICT: SHIP. Verbatim in the report.Documented, not implemented (maintainer decisions)
llm_judgebackstop for languages the corpus does not cover (own spec).list_modelsdescriptions name no server (both would be soft-flagged once). Keep the exemption, drop it, or demote the shape toinfois a product call; the shape is a review hint, not a boundary.Rebased onto current
main(Spec 118 auto-rescan, #1689): the only conflict was an adjacent insertion intool_quarantine.go(kept both); one TOCTOU test assertion now pins Spec 118's evidence semantics (a moved pending definition is re-described asawaiting_review), and the lab'sscan_gate_loggedprobe accepts #1689's reworded scan-hold log line (EXP-6b/6c were INCONCLUSIVE on currentmainfor that reason alone). The dockerized suites were re-run on this branch after the rebase.Lab/tests: new unit tests per protection, EXP-4
held/yes/noprecondition tables, round-2 runner guard tests (env scrub, pinned dirs, image, fresh run dir, fail-closed exit).🤖 Generated with Claude Code