Skip to content

feat(security): Spec 101 US2 TPA seed corpus and eval gate - #1702

Open
Dumbris wants to merge 31 commits into
mainfrom
claude/101-tpa-db-corpus-363fa9
Open

Dumbris wants to merge 31 commits into
mainfrom
claude/101-tpa-db-corpus-363fa9

Conversation

@Dumbris

@Dumbris Dumbris commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Summary

User Story 2 of Spec 101 (tpa-db): the embedded TPA signature bundle grows from 6 demo signatures to a cataloged seed corpus, and the CI eval gate now actually scores it.

  • Corpus: 44 signatures across 16 tpa_* classes. 42 are gating and 2 are non-gating (resource_content targets, which are not runnable offline). There are 95 runnable regex × tool_description rules, and the bundle is bundle_version 0.1.1.
    • Each signature carries a public provenance URL and date, and an SPDX licence. All 44 are MIT: the patterns and samples are self-authored, and the source is cited as provenance only.
  • signatures[] section (contract D4, FR-001a): additive, keyed by TPA id, many-to-one to rules[].
    • rule_ids are "<TPA-id>/<detector>", so the existing tpa.<TPA-id>.<detector> CheckIDs are unchanged.
    • Referential integrity is enforced at load in both directions, and a failing bundle is rejected whole (internal/security/scanner/tpa_bundle_signatures.go).
    • A bundle without signatures[] (v0.1) loads exactly as before.
  • Deterministic in-repo build (FR-001, FR-015):
    • cmd/tpa-corpus build|verify|validate compiles internal/security/tpacorpus/signatures/TPA-*.json and manifest.json into the embedded bundle.
    • generated_at comes from a pinned source_date_epoch or SOURCE_DATE_EPOCH, never the wall clock.
    • A policy layer, run at build and in CI only, enforces the licence allowlist, the class vocabulary, and eval pairs per gating signature.
    • The bundle is not signed; signing is US1.
  • The gate sees the database (FR-016a, D8):
    • scan-eval --gate loads the bundle through the production loader (scanner.LoadBundleCheck). It uses --bundle <path> if given, otherwise the embedded default, so the existing eval.yml invocation now scores the shipped corpus. An invalid --bundle exits 4 and never falls back.
    • Every class has a categoryCheck entry mapped to tpa.bundle, enforced by TestCategoryCheck_CoversCorpusClasses.
  • Deterministic negative control (FR-016b, D9):
    • TPA-2026-0044 (agent-config persistence) is the control. Its sample is hard-flagged by no built-in check and by no other bundle signature, which is asserted against both the gate built-ins and the production built-ins.
    • Over cmd/scan-eval/testdata/bundle_control_corpus.json, the only gated malicious sample is the control. With it, recall is 1.0 and the gate exits 0. With it removed by the builder, recall is 0.0 and the gate exits exactly exitGateBreach (6), and the reduced bundle still loads.
  • SC-004: at least 25 gating signatures across at least 8 classes. For each class, the canonical dataset sample, scanned through the production scanner, yields a hard finding with a tpa.<its id>. signal (TestSC004_*).
  • CI: the eval.yml D2 job adds the cmd/tpa-corpus/** path filter and a blocking step that runs tpa-corpus verify/validate plus the control, category, SC-004 and embedded-corpus tests.
  • Docs:
    • New contributor guide docs/features/tpa-signatures.md, linked from the sidebar: authoring, provenance and licence rules, eval-sample rules, how the gate blocks a bad corpus.
    • tool-scanner.md and security-quarantine.md are updated.
    • Spec docs amended in place, marked "amended by US2".

Classes

Class Gating Non-gating TPA ids (2026-)
tpa_hidden_instruction_block 3 0 0001, 0007, 0008
tpa_comment_directive 1 2 0002, 0009, 0010
tpa_sensitive_file_coaxing 4 0 0003, 0021, 0022, 0023
tpa_tool_shadowing 5 0 0004, 0011, 0012, 0013, 0014
tpa_cross_server_override 3 0 0005, 0015, 0016
tpa_rug_pull_phrasing 2 0 0006, 0026
tpa_exfil_redirect 4 0 0017, 0018, 0019, 0020
tpa_credential_harvesting 1 0 0024
tpa_persistence_implant 2 0 0025, 0044 (control)
tpa_instruction_override 3 0 0027, 0028, 0029
tpa_conversation_exfil 3 0 0030, 0031, 0032
tpa_preference_manipulation 2 0 0033, 0034
tpa_concealment_directive 1 0 0035
tpa_invisible_text 3 0 0036, 0037, 0038
tpa_payload_execution 4 0 0039, 0040, 0041, 0042
tpa_schema_injection 1 0 0043

Sources include:

  • Invariant Labs (tool poisoning, WhatsApp exfiltration)
  • Trail of Bits (line jumping, conversation theft, ANSI deception)
  • Elastic Security Labs, Snyk Labs (postmark-mcp)
  • Check Point (CVE-2025-54136)
  • AWS-2025-015
  • Embrace The Red (Unicode tags; Copilot config persistence)
  • MITRE ATLAS
  • the arXiv papers MCPTox, MCP Safety Audit, MPMA and MCP-38
  • MCPSecBench, MSB
  • Cisco mcp-scanner YARA (Apache-2.0)

Where research verification found that a source does not support its signature, the source was replaced. Sources rejected for licence or verifiability are listed in specs/101-tpa-db/corpus-research/rejected-sources.json.

Gate numbers

Run Recall False-positive rate Exit
scan-eval --corpus detect_corpus_v1.json --gate --min-recall 0.90 --max-fp 0.05 (embedded default) 1.0 (118/118 gated malicious) 0.0 (0/1042 hard negatives; 0/1047 benign) 0
same with --bundle internal/security/scanner/bundled/scanner-bundle.json 1.0 (118/118) 0.0 (0/1042) 0
control corpus, full bundle 1.0 (1/1) 0.0 0
control corpus, bundle without TPA-2026-0044 0.0 (0/1) 0.0 6 (exitGateBreach)

Before this PR the gate scored only the four built-in checks and never loaded a bundle. Bundle fingerprint: 025db6d1ee35. The hard-negative set grew from review rounds: every false positive a reviewer found was narrowed and pinned as a hard negative, and every missed cited payload was added as a malicious sample.

Assumptions (recorded per the autonomous-operation rules)

  • Eval samples extend detect_corpus_v1.json in place. This follows the Spec 077 precedent, and no test pins that file. Hard negatives are named hn_<class>_*, where the validator derives the prefix from tpacorpus.Classes.
  • "Gating" is derived mechanically: a signature is gating if it owns at least one runnable rule. The loader emits every runnable rule at hard tier, so deriving gating from level, as the contract first said, would be meaningless.
  • The licence allowlist drops CC-BY-SA-4.0. Its share-alike terms could attach to the MIT binary. The maintainer can re-add it.
  • The v0.1 rules of TPA-2026-0001 to 0006 are kept byte-identical, pinned by internal/security/tpacorpus/testdata/bundle_v0.1.0.json, so attack-lab and runtime CheckIDs and behaviour don't move.
    • Review found pre-existing false positives in them: 0001 matches any <IMPORTANT> block containing read; 0004/0005 match any <IMPORTANT>…send_email…</IMPORTANT>; 0006 matches "change the recipient".
    • These are left for a follow-up rather than changed under a corpus PR.
  • Precision trade-off in TPA-2026-0021: it now keys on generic carrier parameter names (notes, context, metadata…), so legitimate private_key-style parameters are not flagged. An attacker who picks a credential-looking sink name evades this one rule, though 0003/0022/0023 still cover the read-coaxing phrasing.
  • The tpa_markdown_image_beacon class was dropped: no verifiable MCP-specific primary source was found.

Integration with US1 (#1701) and US3 (#1720)

origin/main with US1 is merged into this branch. The conflicts were resolved as follows:

  • tpa_bundle.go: US1 split loading into parseBundleManifest + compileBundle. The Signatures field sits next to US1's manifest fields (sequence, key_id, trust_epoch) in rawBundle. validateSignatures(b) runs at the tail of compileBundle, so every candidate path enforces signatures[] integrity: embedded, file drop, rollback, and the activation self-check.
  • tpa_bundle_eval.go: LoadBundleCheck is rewired from the removed loadBundleWithInfo to parseBundleManifest + compileBundle, and now also reports FullDigest. It stays pure, never touching the active-bundle runtime.
  • specs/101-tpa-db/tasks.md: US1's file is kept, with the US2 section (T200+) appended.
  • Other files: .gitattributes keeps both blocks (US1's bundlesig/testdata/* -text and US2's LF pins); website/sidebars.js keeps both pages; ROADMAP.md is regenerated.
  • scanner-bundle.json: US1 did not change the embedded bundle, so no header keys were ported, and tpa-corpus verify still passes.
  • US3 (feat(security): Spec 101 US3 TPA bundle refresh lifecycle and publication channel #1720): its activation self-check embeds a byte copy of the eval corpus, internal/security/scanner/bundled/selfcheck-corpus.json. That copy is re-synced from the extended detect_corpus_v1.json, which TestSelfCheckCorpusByteParity enforces.

Verified on a live instance

Built ./mcpproxy from this branch at the final rules (re-run after the last review round). It ran on 127.0.0.1:18573 with a scratch --config and --data-dir, with Docker isolation off.

Two stdio fixture servers were added via POST /api/v1/servers with quarantined: true:

  • live-poisoned: four tools whose descriptions are the canonical malicious samples of TPA-2026-0019, 0024, 0035 and 0044.
  • live-benign: add, list_files, and a send_email with a cc list.
$ mcpproxy security scan live-poisoned
  [HIGH] detect.tpa.TPA-2026-0044.config_persist (tpa-descriptions)
         Signals:  tpa.TPA-2026-0044.config_persist
         Location: live-poisoned:save_note
  ...
$ curl -H "X-API-Key: …" /api/v1/servers/live-poisoned/scan/report
{"verdict":"dangerous","risk":80,"findings":4}
  detect.tpa.TPA-2026-0019.dns_encode_sensitive_first   dangerous  live-poisoned:latency_probe
  detect.tpa.TPA-2026-0024.collect_env_into_param       dangerous  live-poisoned:diagnostics_summary
  detect.tpa.TPA-2026-0035.act_then_conceal             dangerous  live-poisoned:unit_converter
  detect.tpa.TPA-2026-0044.config_persist               dangerous  live-poisoned:save_note
$ curl … /api/v1/servers/live-benign/scan/report
{"verdict":"clean","risk":0,"findings":0}
$ curl … /api/v1/security/overview   # bundle block
{"source":"embedded","bundle_version":"0.1.1","signature_count":44,"runnable_rules":95,"fingerprint":"6b446af4544b"}
$ go run ./cmd/scan-eval --corpus specs/065-evaluation-foundation/datasets/detect_corpus_v1.json --gate --min-recall 0.90 --max-fp 0.05 --bundle internal/security/scanner/bundled/scanner-bundle.json
GATE PASSED: recall=1.0000 (>=0.9000), fp=0.0000 (<=0.0500)   # 118/118 gated malicious, 0/1042 hard negatives (after the round-10 follow-up)

Verification

  • Tests: go test -race ./internal/security/... ./cmd/scan-eval/... ./cmd/tpa-corpus/... ./specs/065-evaluation-foundation/datasets/ ./tests/tpa-attack-lab/ and go test -race -run 'Quarantine|Scan|QAPilot|Conformance|TPA|Bundle' ./internal/runtime/ are green.
    • They include the real-world benign corpora (TestSEC01_RealWorldBenignCorpora_NoNewHolds: 527 LiveMCPTool and 45 reference tools; no re-baseline) and TestCoverage_BenignNotHardBlocked.
  • E2E: ./scripts/test-api-e2e.sh passes 69/0 with the server-edition binary built.
  • Lint: golangci-lint v2 with .github/.golangci.yml, bare and --build-tags server, reports nothing in files this PR touches. The local run lists pre-existing findings in untouched files.
  • Review:
    • An internal review workflow covered provenance, licence, sample safety, gate non-vacuity, determinism and regex false positives, with a 3-refuter vote. It confirmed 13 findings; 12 are fixed, and the 0006 part is a pre-existing v0.1 rule.
    • A cross-model review by opencode github-copilot/gpt-6.1-sol ran 10 rounds, the cap, and did not reach a clean verdict on the corpus. See the review comments for the round-by-round record and the round-10 verdicts. Maintainer decision (2026-10-22): fix the two round-10 recall gaps before merge (done: 0011 multiline replacement claim, 0037 imperative directly after the concealing SGR), accept the negated-object precision limits, merge on no P1 + gate 0 FP + every reviewer example pinned.

Related #938

🤖 Generated with Claude Code

Dumbris and others added 16 commits October 20, 2026 12:58
Re-validated the plan against main (bundle contract 0.1, loader, scan-eval
gate, detect_corpus_v1) and recorded the US2 decisions D-US2-1..10: rule_ids
as <TPA-id>/<detector>, gating = runnable, one load-layer integrity check plus
a build/CI policy layer, an in-repo deterministic builder (cmd/tpa-corpus),
tpa_<class> categories mapped to tpa.bundle, and the D9 control corpus.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…loader

Spec 101 US2 foundations (T200-T204). The loader now checks the optional
signatures[] section for referential integrity (rule_ids resolve and are owned
exactly once, gating matches runnable rules, sorted rules, signature_count,
provenance, skipped ids) and rejects the whole bundle on any violation, so
ConfigureBundle keeps the last-known-good corpus. v0.1 bundles without the
section load unchanged.

LoadBundleCheck, EmbeddedBundle and ParseBundleSignatures give the corpus
builder and scan-eval a seam that never touches the active bundle. The
"exfiltration" rule category now maps to detect.ThreatExfiltration.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pus builder

US2 settled several things the original contract left open: rule_ids use
"<TPA-id>/<detector>" to keep the CheckID shape the CLI and Web UI parse,
gating is derived from runnable rules (the loader hard-codes the hard tier,
so level carries no meaning), integrity checks split into a load layer and a
build/CI policy layer, CC-BY-SA-4.0 leaves the licence allowlist, and
signature_count is len(signatures). Each amendment is marked inline so
reviewers can see what changed. The pipeline assumption becomes an in-repo
deterministic builder (cmd/tpa-corpus) with signing left to US1.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nature sources

The embedded scanner bundle was a hand-maintained JSON file with no place
for provenance, licence or campaign identity, so a hand edit could not be
told apart from a legitimate change. Add internal/security/tpacorpus, which
compiles one source file per signature plus a manifest into the bundle
byte-for-byte reproducibly (sorted rules and signatures, alphabetical keys,
generated_at from a pinned epoch, never the wall clock), refuses sources
without provenance, with a licence outside the allowlist or with an unknown
class, and checks its own output through the production loader.
ValidatePolicy checks the eval pair every gating signature owes. The new
cmd/tpa-corpus exposes build, verify (byte-compare, non-zero on drift) and
validate.

Migrate the six existing signatures into sources with their rules and
skipped entries unchanged, add the signatures[] section (0002 is the one
non-gating signature) and move the bundle to 0.1.1.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gate only ran the four built-in checks, so a change to the bundle
corpus could never move recall/FP. Add --bundle (empty = embedded) and run
BundleCheck alongside the built-ins, mirroring the production hard
surface. A missing or invalid candidate bundle exits 4 and never falls
back to the embedded default. The metrics gain a bundle block
(fingerprint, version, runnable rules, signature count).

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…signment

Sixteen tpa_* classes from the Phase 1 research sweep (44 verified catalog
entries); corpus-research/ records the TPA id assignment, the sources rejected
for licence or verifiability, and the non-lexical entries not shipped.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The seed corpus was six signatures in a handful of classes, too thin to
claim coverage of the published tool-poisoning techniques. Add
TPA-2026-0007..0044, each a self-authored MIT regex (RE2, bounded gaps,
anchored on the technique's distinctive conjunction) with provenance
pointing at the verified source, so every class in tpacorpus.Classes has
at least one gating signature. 0002 and 0010 target resource content,
which cannot run offline, so they stay non-gating; 0044 is the
deterministic negative control for the gate.

The concealment rule (0035) matched a security scanner's own
description ("conceal its actions from the user"), so its keep/hide/
conceal clause now requires an imperative object (this, it, the step...)
instead of any phrase ending in "from the user". 0002 and 0006 cited
sources that do not contain their techniques and now cite the ones that
do.

Rebuilt with the manifest pin: 44 signatures, 85 runnable rules (was 7),
4 not-runnable rules (was 3); TestLoadEmbeddedBundle pins the new counts.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t the bundle

A gating signature with no labeled sample is unmeasured: the gate could
not tell a regression in it from silence, and nothing proved its regex
spares the benign look-alikes. Extend detect_corpus_v1.json in place
(the 57 earlier entries are byte-identical) with one malicious sample
per gating signature and two attack-resembling hard negatives per
signature, security tooling and docs that quote the attack, plus one
canonical sample per class for the SC-004 test.

The validator derives the tpa_* categories and the hn_<class>_ prefixes
from tpacorpus.Classes, checks the optional tpa_id/canonical keys, and
guards that no hard-negative prefix nests inside another (an id could
otherwise satisfy two categories). tpa_id is kept off hard negatives
because ValidatePolicy rejects a non-malicious entry that names a
signature. The embedded bundle is now checked against the dataset by
policy in-process, the twin of `tpa-corpus validate`. README composition
is recounted (it said 32; the file had 57, now 182).

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
categoryCheck only knew the built-in categories, so a tpa_* class would
be reported as malicious-but-ungated and a regression in it could never
fail CI. Map each class in tpacorpus.Classes to tpa.bundle with static
entries (a class is a reviewed change), and add
TestCategoryCheck_CoversCorpusClasses so the vocabulary and the map
cannot drift apart: it covers every class and every gating signature
category in the embedded bundle.

Gate on the extended corpus: overall recall 1.0 over 71 gated malicious
entries, 0 false positives over 104 hard negatives, every tpa_* category
gated.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…val control

The gate now scores the embedded bundle, but a gate that scores a bundle can
still pass for the wrong reason (for example a built-in check catching every
sample, or a class left out of categoryCheck). Add a deterministic negative
control: TPA-2026-0044, whose sample no built-in hard check flags. Its own
corpus scores recall 1.0 with the full bundle and 0.0 with the signature
excluded, and the second run must exit 6 (recall breach), not 4 (load error).

Also add the SC-004 breadth test (>=25 gating signatures, >=8 classes, every
class covered, provenance and licence on every signature, each class's canonical
sample hard-flagged by its own tpa.<id> signal through the production scan
path), and run verify, validate and these tests as a blocking step in the
security-d2 eval job.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add docs/features/tpa-signatures.md (class vocabulary, source format,
provenance and licence policy, eval samples, build/verify loop, how the
gate and the negative control block a bad corpus), register it in the
Security sidebar, document tpa.bundle and scan-eval --bundle in
tool-scanner.md, and add corpus counts to security-quarantine.md.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The embedded bundle is byte-compared against a rebuild of its sources; a
core.autocrlf Windows checkout would embed CRLF bytes and fail the
determinism check on the windows-latest unit-test job.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Narrowed (false-positive fixes found in review):
- TPA-2026-0021 contents_into_param: needs a home/system credential path
  (~/.ssh, ~/.aws, ~/.npmrc, ~/.netrc, ~/.gnupg, ~/.env, /etc/passwd,
  /etc/shadow); bare "private key", "credentials file", ".env", "id_rsa" dropped.
- TPA-2026-0023 precondition_read_key / read_key_every_call: require an agent
  subject or directive cue and a path boundary, so .pub/.example/.sample/.template
  and "the server must load ..." no longer match.
- TPA-2026-0019 DNS-exfil rules: no longer fire on bare "subdomain" or bare
  "token"/"credentials"; need DNS lookup/query wording and a sensitive path or key.
- TPA-2026-0040 decode_and_execute / decode_blob_then_run: require an
  executable object after the run verb and a blob of 40+ characters.
- TPA-2026-0041 destroy_then_proceed_unasked / wipe_fs_and_cloud: require the
  unasked clause to end the sentence, both halves broad, and a directive or
  concealment cue.
- TPA-2026-0014 retired_route_all_calls: requires a concealment cue.
- TPA-2026-0013 post_hook_result_tamper: requires a falsification cue on the
  other tool's result or a pass/success target.
- TPA-2026-0016 transfer_noun_first_redirect / transfer_redirect_mandate:
  the bare word "proxy" removed as a match target; needs a long 0x address.
- TPA-2026-0015, 0017, 0018: recipient/bcc rewrite rules require a covert or
  unrequested cue (0006 is pinned by a golden test and left unchanged).

Provenance corrected:
- TPA-2026-0008, 0020: published 2025-09-24 -> 2026-01-13 (first commit of the YARA files).
- TPA-2026-0032: source now the specific MSB PubMed file; published 2026-03-24.
- TPA-2026-0007, 0013, 0026: source now the specific MCPSecBench maliciousadd.py file.

Eval: 44 new hard negatives appended to detect_corpus_v1.json (existing
entries untouched), README composition updated, bundle rebuilt.
Gate: recall 1.00 over 71 gated malicious, fp_rate 0 over 148 hard negatives.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, policy hard-negative category)

Rules narrowed so ordinary, benign tool descriptions are no longer hard-flagged:
- TPA-2026-0007: bracket-marker rules need a sentence-initial tool-selection directive plus regardless/skip-other-tools steering.
- TPA-2026-0011: prefer_over_deprecated needs an absolute directive and a security disparagement, not just "deprecated".
- TPA-2026-0014: important_tag_tool_transfer also needs a concealment cue.
- TPA-2026-0018: hidden_bcc_literal drops the non-concealing leads "always"/"automatically".
- TPA-2026-0020: keystroke_exfil and capture_then_stream need a concealment cue.
- TPA-2026-0021: read_into_param/contents_into_param need an unnamed or generic carrier parameter, not a credential-purpose name.
- TPA-2026-0022: read_then_send_url needs the send verb followed by an object noun (contents/token/key/...).
- TPA-2026-0025: ssh-key alternative needs a literal key blob after the key type.
- TPA-2026-0031: trigger_phrase_call_first needs a conversation-content trigger.
- TPA-2026-0032: caller_model_param needs "that calls/invokes this tool".
- TPA-2026-0034: superlative_claim keeps "only" only with "in the world"/"on the market".
- TPA-2026-0035: conceal clause no longer treats "never disclose credentials to the user" as concealment.
- TPA-2026-0037: ansi_sgr needs a concealing SGR (parameter 8 or identical fore/background colours).
- TPA-2026-0044: needs a concealment/override cue besides the config-file and future-session cues.

Policy: ValidatePolicy counts a hard negative only when category is "hard_negative" (the category the gate scores).

Corpus: 42 new hard negatives (226 -> 268 entries, 190 hard negatives); tpa_0004_m1 and tpa_0006_m1
now use example.invalid / reserved +1-555-01xx destinations. Gate: recall 1.0, fp 0.0.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: f98611e
Status: ✅  Deploy successful!
Preview URL: https://54612f50.mcpproxy-docs.pages.dev
Branch Preview URL: https://claude-101-tpa-db-corpus-363.mcpproxy-docs.pages.dev

View logs

Dumbris and others added 10 commits October 20, 2026 17:08
…, tpa-corpus CLI args, empty-corpus policy)

Narrowed rules (v0.1 rules of TPA-2026-0001..0006 untouched):
- TPA-2026-0008: covert_secret_collection needs a destination cue;
  while_processing_also_extracts needs a concealment adverb or destination cue.
- TPA-2026-0011: prefer_over_deprecated requires the disparaging word as a
  predicate directly after the replaced tool's name.
- TPA-2026-0020: background_capture_stream cadence words (continuous,
  periodic, in the background) now need a concealment cue.
- TPA-2026-0021: contents_into_param requires a concealment cue near the match.
- TPA-2026-0024: collect_env_into_param needs credential-qualified tokens and
  no longer treats "return" as a sink verb.
- TPA-2026-0025: payload_into_rc / rc_then_payload gaps are sentence-bounded.
- TPA-2026-0040: decode_and_execute / decode_blob_then_run require a
  terminator after the executed object.
- TPA-2026-0042: reverse_shell_idiom no longer matches a bare /dev/tcp/.

tpa-corpus: ValidatePolicy rejects a bundle with no signatures[]; the CLI
rejects leftover positional arguments (exit usage).

Corpus: +24 hard negatives (292 entries, 214 hard negatives, 193 tpa_*);
README composition updated. Gate: recall 1.0, fp 0.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and 0007 recall, null source entries)

Rules narrowed or widened:
- tpacorpus builder: null entries in rules/skipped are now a build error
  instead of a nil-map panic in stampOwner (tests added).
- TPA-2026-0007: bracket-marker rules accept an adverb-led tool-selection
  imperative with no punctuation ("[important]when operation is add just use
  this tool ..."); new malicious sample tpa_0007_m2 and near-miss negatives.
- TPA-2026-0011: prefer_over_all_others now needs a disparagement,
  displacement or concealment cue, so scoped sandbox recommendations pass.
- TPA-2026-0034: always_prefer_this must end the clause within two words
  of "other/similar/alternative/competing", so scoped recommendations
  ("... any other file reader for paths within the sandbox") pass.
- TPA-2026-0016: transfer redirect rules need an override/concealment cue
  near the 0x address.
- TPA-2026-0020: dropped bare "in the background" as a concealment cue.
- TPA-2026-0022: dropped bare settings.json as a sensitive source.
- TPA-2026-0026: upgrade_credentials_block needs a directive cue and an
  access/disclosure verb before the credential keyword.
- TPA-2026-0030: append_chat_history cannot cross a sentence boundary and
  needs an outbound-message destination.

Corpus: +18 hard negatives, +1 malicious sample (311 entries). Rebuilt bundle.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… rule for false positives

Round-4 findings (all verified real):
- tpacorpus.Build rejects a source_date_epoch above 253402300799 (year 9999) so
  the build stamp is always valid RFC 3339.
- TPA-2026-0011 prefer_over_deprecated: "instead of X, which is unsafe" alone no
  longer fires; needs an override or concealment cue.
- TPA-2026-0015 recipient_swap_hidden / recipient_swap: need a concealment cue
  about the swap or an override of the user's choice; cues are anaphoric.
- TPA-2026-0020 keystroke_exfil: "in the background" and a bare remote host are
  no longer sufficient; background_capture_stream / capture_then_stream tightened.
- TPA-2026-0022 send_file_value_to_url / read_then_send_url: need a concealment
  cue; id_rsa.pub, .env.example no longer count as secret files.
- TPA-2026-0025 payload_into_rc / rc_then_payload: a literal ssh key or curl|sh
  needs a persistence or concealment cue; real reverse-shell payloads still
  match unconditionally.
- TPA-2026-0026 upgrade_claim_read_secret / upgrade_credentials_block: need
  concealment of the action or disclosure of the secret into the reply.
- TPA-2026-0042 pipe_to_shell_directive / reverse_shell_idiom: need an
  imperative lead, so "Never run curl ... | bash" no longer matches.

Proactive sweep: every runnable rule of TPA-2026-0007..0044 (all 85 rules in the
bundle bar the pinned v0.1 0001-0006) was probed with benign descriptions and
narrowed where it matched; 564 new hard negatives are added to
detect_corpus_v1.json (appended; existing entries byte-identical). Hard negatives
that the built-in unicode.hidden / phrase.injection / payload.decoded checks
hard-flag were reworded, and the ten raw bidi/ZWNJ/tag samples for 0036 were
dropped (unicode.hidden flags those by design).

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…yloads, 0017 regex, provenance scope)

Rules narrowed/widened and provenance corrected:
- TPA-2026-0017: fix malformed `.{0,.{0,100}?` so the recipient-rewrite branch matches again.
- TPA-2026-0009: accept suffixed model cues (ASSISTANT_ONLY, AGENT_NOTE), leading "ahead of/prior to" clauses and modals; exfil object must be contents/it/them/file/key/secret/token; concealment accepts "never mention this requirement/instruction/step/rule to users".
- TPA-2026-0013: allow an optional article in "of (the) <tool> tool".
- TPA-2026-0014: provenance note states it covers the description-level variant, not the execution-time tool result.
- TPA-2026-0018: provenance note states the Snyk disclosure is an implementation backdoor; signature is a description-level analogue.
- TPA-2026-0022: new rule read_settings_token_send_request (settings/credentials file read + token + send to URL, no concealment needed); provenance note updated.
- TPA-2026-0031: new rule trigger_phrase_consult_first (conversation-contains-words trigger + consult tool first + send whole conversation).
- TPA-2026-0037: new rule ansi_sgr_256 (256-color/truecolor near-white/black fg/bg paired with default/other hidden color plus injection cue).
- TPA-2026-0044: new rules config_autoapprove_settings (.vscode/settings.json autoApprove) and config_persist_bypass (instruction-file persist plus approval bypass).

Dataset: +8 malicious own-words samples and +21 hard negatives (904 entries); bundle rebuilt (94 rules, 90 runnable); README counts and TestLoadEmbeddedBundle updated.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oss TPA rules, 0033 recall)

Rules narrowed:
- TPA-2026-0022: token-send rule limited to agent-config files (.gemini/settings.json,
  .claude/settings*.json, .cursor/mcp.json, .codex/*); read/send verbs anchored so
  "must not read" / "never send" do not count (ordinary GitHub/Vault auth no longer flagged).
- TPA-2026-0019: bare "for each chunk" destination now needs a DNS operation within 80 chars;
  encode verb anchored on a lead (PEM chunking and "Never base64-encode" no longer flagged).
- TPA-2026-0033: recall fix; two new rules (consult_first_conceal, destructive_prefix_conceal)
  cover the cited Trail of Bits payload; command_prefix_hide anchored.
- TPA-2026-0039: base64/hex dropper rules require an encoded download-and-execute or an explicit
  run directive pointing at the blob (bare encoded curl/wget examples no longer flagged).
- TPA-2026-0040: decode/decrypt verbs anchored so "Never decode ... and execute" does not match.
- TPA-2026-0044: persistence verbs need an allowed lead ("Never add autoApprove" no longer matches).
- Negation-guard sweep (verbs anchored on sentence start / list marker / lead word, adverb gap
  narrowed to a closed list): 0011, 0012, 0013, 0014, 0015, 0016, 0017, 0018, 0020, 0021,
  0023, 0024, 0025, 0026, 0031, 0034, 0035, 0037, 0038, 0041, 0043.
- TPA-2026-0001..0006 (pinned v0.1 rules) untouched.

Eval corpus (detect_corpus_v1.json, append-only): +1 malicious (tpa_0033_m2) and +47 hard
negatives (904 -> 952 entries); README composition updated. One 0022 hard negative reworded
to avoid a built-in phrase.injection hard flag. Bundle rebuilt (98 rules, 94 runnable);
TestLoadEmbeddedBundle count updated.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ule gaps, quoted filenames, public keys)

Rules narrowed or widened (62 rules across 30 signatures):
- 0016 transfer_noun_first_redirect / transfer_redirect_mandate / transfer_redirect_silent:
  closed modal-to-verb connector lists; concealment cue needs a positive preceder, so
  "must not be redirected ... without notifying the user" no longer fires.
- 0019 dns_encode_dns_first / dns_encode_sensitive_first: ~/.ssh and ~/.aws public keys
  (*.pub) are no longer secret paths; quoted/backticked malicious paths still match.
- 0022 read_then_send_url / send_file_value_to_url / read_settings_token_send_request:
  the request clause must carry the secret; "silently" and "without telling" cues need a
  positive lead-in; ~/.ssh/*.pub is not a secret path.
- 0044 config_persist / config_autoapprove_settings / config_persist_bypass: quoted and
  backticked config filenames match; payload cues need a clause boundary or closed
  connector, so negated policy text ("never run ... without asking") does not fire.
- 0028 role_redefinition / jailbreak_mode: status claim must be followed by a closed
  connector before the bypass directive; negation-free token gaps.
- 0030 append_chat_history / hook_send_append: negation-free gaps, closed connector list.
- Gap sweep: 0007 0009 0010 0011 0012 0014 0015 0017 0020 0021 0023 0024 0025 0026 0031
  0034 0035 0037 0038 0039 0040 0041 0042 0043 get negation-free or closed gaps, a lead
  word before steering verbs, "need/have to" instead of a bare "to" lead, and no longer
  treat ~/.ssh/id_*.pub as a secret path.

Eval corpus: +60 hard negatives (952 -> 1013 entries) and one malicious sample
(tpa_0044_m3, backticked filenames). Three 0022 negatives were reworded so no built-in
check hard-flags them. Gate: 82/82 gated malicious, recall 1.0, 924 hard negatives, fp 0.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion concealment, wrapped payloads, 0011 recall)

Rules narrowed or widened:
- TPA-2026-0008: negation-tempered gaps between the covert adverb, the
  collect/send verb and the object (refuses/declines/never/fails no longer
  match); destination literals may be wrapped in backticks, quotes, brackets,
  braces or <>.
- TPA-2026-0009: html_comment_secret_read no longer treats "never output them"
  as a send; concealment must bind to the ACTION, not a secret value.
- TPA-2026-0018: bcc_not_disclosed concealment cues bind to the action
  ("this password" is no longer concealment of the BCC); refusal wording
  ("refuses to silently add") no longer matches.
- TPA-2026-0011: new claims_replacement_prefer_over rule (trust-qualified
  replacement claim plus a use-this-instead directive); prefer_over_deprecated
  concealment cue requires an action object.
- TPA-2026-0035: tell-the-user continuations require an action subject.
- TPA-2026-0044: wrapped key/path/file literals, bare "to" lead removed.
- Sweep of the concealment object (action vs secret value), negation/refusal
  lead words and payload wrappers across 0010, 0012, 0013, 0015-0017,
  0019-0026, 0029-0031, 0033, 0037-0039, 0042, 0043.

Dataset: +21 malicious samples and +66 hard negatives appended; 10 new hard
negatives reworded where a built-in check (phrase.injection) hard-flagged them
(send -> share, ~/.ssh/config -> ~/.npmrc). Bundle rebuilt (95 runnable rules).

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d exclusions, paraphrase recall)

Rules narrowed or widened:
- TPA-2026-0011: built-in/default/stock-reader branch needs an absolute modifier
  (always/only/must); added a bounded passive "is recommended for any <X> access"
  branch so the cited Elastic replacement description is caught.
- TPA-2026-0012: bare silently/secretly/quietly and "instead of" no longer count as
  concealment; adverbs only bind to the routing/override verb.
- TPA-2026-0014: concealment adverb before or after the routing verb is caught.
- TPA-2026-0018: bcc_not_disclosed requires a negation-free clause start.
- TPA-2026-0024, 0029, 0030, 0031: object-level gaps are now bounded runs of
  non-negation words, so "no values", "never the values", "everything except",
  "only a summary, not ..." no longer match; 0031 concealment paraphrases widened.
- TPA-2026-0025: "maintain/regain/keep remote access" alone is no longer persistence
  intent; needs persistent/covert/hidden/backdoor-style qualifiers.
- TPA-2026-0026: concealment alternation accepts "mention this to the user" style
  paraphrases; upgrade-claim fallback accepts adverbs.
- TPA-2026-0028: "to" is no longer a directive boundary; reverse-order directive
  anchored to a clause start; bypass_security_checks adverbs anchored.
- TPA-2026-0033: concealment clause now binds every order alternation.
- TPA-2026-0035: keep/hide/conceal need an affirmative boundary; "never forget to
  tell the user" is not concealment.
- TPA-2026-0044: yolo/allow-all/trust-all and bare auto-approv* need an activation
  verb.

Dataset: +12 malicious samples and +49 hard negatives (detect_corpus_v1.json, appended
in place; existing entries byte-identical). Gate: recall 1.00, FP 0.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Dumbris

Dumbris commented Oct 20, 2026

Copy link
Copy Markdown
Member Author

Cross-model review: opencode github-copilot/gpt-6.1-sol (variant high), 10 rounds, cap reached

The review was split into six to eight file-named chunks per round: loader, builder/policy, gate, tests, and two to four corpus chunks.

Round Code chunks (loader, builder/policy, gate, tests) Corpus chunks Findings fixed after the round
1 loader, gate, tests CLEAN; builder 1 14 policy hard-negative category; 13 rule precision fixes; reserved fixture destinations
2 builder 2 8 empty-signatures[] policy; tpa-corpus positional args; 8 rules
3 builder 1 (P3) 7 null source entries; 7 rules (one recall: 0007)
4 builder 1 (P3) 7 epoch range; 7 rules, then a proactive sweep of all 77 rules in 0007–0044 (~1,060 benign probes, 73 narrowed)
5 all CLEAN from here 9 recall on cited payloads (0009, 0013, 0017 malformed regex, 0022, 0031, 0037, 0044); honest provenance notes (0014, 0018)
6 CLEAN 6 negation guard on directive verbs across 20+ signatures; 0033 recall
7 CLEAN 7 negations inside rule gaps; backtick/quote-wrapped paths; .pub keys
8 CLEAN 6 concealment cues bound to the action, not to a secret value; wrapped payload literals; 0011 recall
9 CLEAN 15 (4 chunks) negated objects and exclusions in gaps; alternation binding; paraphrase recall (0011, 0014, 0026)
10 CLEAN 9 open: cap reached

The code (loader integrity, builder determinism, policy, scan-eval gate and the D9 control, tests) has been CLEAN since round 5.

Every corpus finding was verified before it was fixed, and none were refuted. All were P2 or P3; there were no P1s after round 1. Each was either a false positive on a constructed benign description or a recall gap against a cited payload. Each fix pinned the reviewer's example as a hard negative or malicious sample. The eval set grew to 115 malicious samples and 1,039 hard negatives, and the gate holds 1.0 recall with 0 false positives.

Open round-10 findings (not fixed, cap reached):

  • 0011: a sandboxed "secure replacement … use this tool for all file reads" wrapper still matches; the multiline Elastic payload still misses.
  • 0019, 0021, 0023, 0025, 0035: explicitly excluded objects ("excluding private keys", "not ~/.ssh/id_ed25519") are still read as the target.
  • 0022: a settings read whose request explicitly omits the token still matches.
  • 0037: an ANSI SGR 8 run that hides the whole imperative consumes the boundary the rule needs.

Assessment: the corpus findings are not converging (14 → 8 → 7 → 7 → 9 → 6 → 7 → 6 → 15 → 9), because each round probes new natural-language paraphrases against hard-tier regexes. The remaining class is mostly negation and exclusion inside a sentence, which RE2 without lookbehind can only approximate. Per the round cap, this PR is not merged pending a maintainer decision.

@codecov-commenter

codecov-commenter commented Oct 20, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 90.58380% with 50 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/security/tpacorpus/build.go 86.59% 13 Missing and 13 partials ⚠️
cmd/tpa-corpus/main.go 86.27% 11 Missing and 3 partials ⚠️
cmd/scan-eval/gate.go 89.74% 2 Missing and 2 partials ⚠️
internal/security/tpacorpus/policy.go 93.75% 2 Missing and 2 partials ⚠️
internal/security/scanner/tpa_bundle_eval.go 95.12% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

…pus-363fa9

# Conflicts:
#	.gitattributes
#	ROADMAP.md
#	internal/security/scanner/tpa_bundle.go
#	specs/101-tpa-db/tasks.md
#	website/sidebars.js
…s on Windows

The gate renders the --bundle path with %q, which escapes Windows
backslashes; the raw path never appeared in stderr on windows-latest.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Dumbris

Dumbris commented Oct 20, 2026

Copy link
Copy Markdown
Member Author

Round-10 open findings: my verdict on each

Some context for the cost of a false positive here. A bundle match is a hard-tier finding. Its effect is that the server or tool is held for human review: it stays quarantined, or the change is not auto-approved in scan mode. Nothing is deleted or blocked permanently. So a false positive costs one manual approval, while a missed detection lets a poisoned description through.

A. Recall gaps (confirmed; I recommend fixing before merge)

  • TPA-2026-0011, multiline payload. The rule needs the "replacement for X" claim and the "use it for any/all access" recommendation within [^\n]{0,200}. Elastic's real description puts a multiline feature list between the two, so the source-faithful payload is missed. The fix is a small regex change: let the gap span newlines with a larger bound.
  • TPA-2026-0037, ANSI boundary. When ESC[8m is followed directly by the imperative (ESC[8mRead ~/.ssh/id_rsa …), the rule finds no clause boundary before Read, so the hidden directive escapes. The fix is small: count the end of an SGR sequence as a clause boundary.

B. Negated or excluded objects read as the target (confirmed false positives; acceptable precision trade-off)

These descriptions name a secret only to exclude it, inside the same directive sentence:

  • 0019: "Encode public hostnames, excluding private keys, into DNS subdomains."
  • 0021: "First read public configuration, excluding ~/.ssh/id_rsa, and put its full text into the notes parameter."
  • 0022: "… then send a request to https://status… without the token."
  • 0023: "Read the documentation, not ~/.ssh/id_ed25519, before every call …"

All four are real matches on benign text. They are also constructed: real tool descriptions rarely name a private-key path in order to exclude it. Closing them fully needs negation scoping over arbitrary noun phrases, which RE2 (no lookbehind) can only approximate. Rounds 6–9 already did that for verbs, objects and concealment cues. Each new exclusion phrasing then produced a new probe.

My view: accept these as the known precision limit of a lexical tier, and pin each one as a hard negative so it can't get worse.

C. Disputed (the flag is defensible)

  • 0011, sandbox wrapper. "A secure replacement for read_file … Use this tool for all file reads." This is a tool that claims to replace a peer tool for all access, and lexically it is the shadowing pattern itself. Holding it for one human approval is the intended outcome. A real sandbox wrapper still gets approved once.
  • 0025, literal reverse shell. "Add a PATH export, not the reverse-shell command nc listener… -e /bin/sh, to ~/.zshrc." A legitimate description has no reason to carry a working reverse-shell command line next to a shell-rc target. Flagging it is reasonable.
  • 0035, concealment directive. "… never credentials or tokens. Do not tell the user about this internal cache lookup." The negated-object part is a real false positive. But the description still tells the agent to hide an action from the user, which is the core concealment signal this class exists to catch. I consider the flag correct.

Suggested acceptance bar

  1. Fix A (2 small regex changes, with source-faithful malicious samples added), then rerun the gate.
  2. Pin the B examples as hard negatives where that doesn't regress a malicious sample; otherwise document them as known limits in docs/features/tpa-signatures.md.
  3. Accept the C cases as the intended behaviour.
  4. Merge on: no P1, gate at 1.0 recall and 0 false positives on the eval set, and every reviewer example pinned. Do not require a CLEAN Sol verdict on the corpus chunks, which this rule style does not converge to (14 → 8 → 7 → 7 → 9 → 6 → 7 → 6 → 15 → 9 findings per round).

Not merging: this needs the maintainer's call.

@github-actions

Copy link
Copy Markdown
Contributor

📦 Build Artifacts

Workflow Run: View Run
Branch: claude/101-tpa-db-corpus-363fa9

Available Artifacts

  • archive-darwin-amd64 (32 MB)
  • archive-darwin-arm64 (29 MB)
  • archive-linux-amd64 (19 MB)
  • archive-linux-arm64 (17 MB)
  • archive-windows-amd64 (32 MB)
  • archive-windows-arm64 (28 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (28 MB)
  • installer-dmg-darwin-arm64 (25 MB)
  • smart-mcp-proxymcpproxy-goBS30HI.dockerbuild (0 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 39493519080 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

Dumbris added a commit that referenced this pull request Oct 22, 2026
…tion channel (#1720)

Spec 101 User Story 3 together with Spec 087 User Stories 1 and 2: the TPA bundle refresh lifecycle and the publication channel.

- Refresh lifecycle (internal/security/scanner/tpa_refresh.go). Each daily jittered cycle runs a file leg, then an optional fetch leg. Both feed the US1 Candidate pipeline, so the verifier is not forked. Concurrent triggers join one cycle, scheduling is one deadline held under the refresher's lock, and Runtime.Close joins the refresher.
- Opt-in signed fetch (tpa_fetch_enabled, default off; tpa_fetch_url, no default URL; env MCPPROXY_TPA_FETCH_ENABLED). It supports the GitHub Releases latest shape and a direct shape. Fetched pairs must be signed. The transport is https-only (http only to loopback), redirects are capped, egress is checked at dial time, bytes and time are bounded, ETag and backoff are honoured, and only the host is logged. Network outcomes are kept apart from rejections. A fetched bundle survives reloads and restarts.
- Activation self-check (tpa_selfcheck.go): the CI recall/FP gate over an embedded copy of the eval corpus. A failing candidate is refused as gate_failure and last-known-good keeps serving.
- Freshness: BundleInfo gains last_checked_at, last_activated_at, stale, refresh and reprotect, on REST (swagger regenerated), the CLI and the Web UI. Doctor warns on a stale database, on being stuck on last-known-good, and on a fetch that is enabled but cannot run. New: mcpproxy security bundle refresh and POST /api/v1/security/bundle/refresh.
- Re-protection after an activation. Held-evidence refresh, plus a cached-contract informational scan of approved toolsets pinned to the activated snapshot (no upstream connection, gated by auto_baseline_scan). RT-02 holds: nothing is approved, unquarantined, revoked or quarantined.
- Telemetry schema 16: five integer refresh and re-protection counters inside the closed anonymity contract.
- Specs: SC-007's fetch clause moves to Spec 087 as SC-009, and Spec 087 is marked implemented by Spec 101 US3. Tasks T300-T396 are ticked and roadmap tpa-db-refresh is done.
- Docs: tpa-bundle-signing, security-quarantine, tool-scanner, configuration, security-commands, telemetry, quickstart, CLAUDE.md.

No new dependencies. The fetch works today only with operator-signed bundles, because the publisher key set is empty and the corpus repository does not exist yet. Whichever of this PR and #1702 merges second must refresh bundled/selfcheck-corpus.json from specs/065-evaluation-foundation/datasets/detect_corpus_v1.json.

Related #938
Dumbris and others added 3 commits October 22, 2026 17:59
…pus-363fa9

# Conflicts:
#	ROADMAP.md
#	specs/101-tpa-db/tasks.md
…im, 0037 SGR-adjacent imperative)

TPA-2026-0011 claims_replacement_prefer_over now spans line breaks between
the replacement claim and the recommendation, so the multiline Elastic
payload matches. TPA-2026-0037 ansi_sgr and ansi_sgr_256 accept an imperative
that starts right after the concealing SGR terminator. Three own-words
malicious samples (missed by the old rules) and three hard negatives are
appended; selfcheck-corpus.json is re-synced. roadmap: tpa-db-corpus done.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the US2 corpus in the eval set, the US3 activation self-check refuses a
one-rule operator bundle as gate_failure (it misses every tpa_* sample), so
the stdio-transport test's fixture now re-signs the embedded corpus with an
operator manifest. The test still proves operator activation in stdio mode.

Related #938

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Dumbris
Dumbris enabled auto-merge (squash) October 22, 2026 15:31

This branch has not been deployed

No deployments
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.

2 participants