Repository navigation
Conversation
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>
Deploying mcpproxy-docs with
|
| 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 |
…, 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>
…pus-363fa9 # Conflicts: # CLAUDE.md
Cross-model review: opencode
|
| 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 Report❌ Patch coverage is 📢 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>
Round-10 open findings: my verdict on eachSome 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 A. Recall gaps (confirmed; I recommend fixing before merge)
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:
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)
Suggested acceptance bar
Not merging: this needs the maintainer's call. |
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 39493519080 --repo smart-mcp-proxy/mcpproxy-go
|
…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
…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>
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.
tpa_*classes. 42 are gating and 2 are non-gating (resource_contenttargets, which are not runnable offline). There are 95 runnable regex ×tool_descriptionrules, and the bundle isbundle_version0.1.1.signatures[]section (contract D4, FR-001a): additive, keyed by TPA id, many-to-one torules[].rule_idsare"<TPA-id>/<detector>", so the existingtpa.<TPA-id>.<detector>CheckIDs are unchanged.internal/security/scanner/tpa_bundle_signatures.go).signatures[](v0.1) loads exactly as before.cmd/tpa-corpus build|verify|validatecompilesinternal/security/tpacorpus/signatures/TPA-*.jsonandmanifest.jsoninto the embedded bundle.generated_atcomes from a pinnedsource_date_epochorSOURCE_DATE_EPOCH, never the wall clock.scan-eval --gateloads the bundle through the production loader (scanner.LoadBundleCheck). It uses--bundle <path>if given, otherwise the embedded default, so the existingeval.ymlinvocation now scores the shipped corpus. An invalid--bundleexits 4 and never falls back.categoryCheckentry mapped totpa.bundle, enforced byTestCategoryCheck_CoversCorpusClasses.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 exactlyexitGateBreach(6), and the reduced bundle still loads.tpa.<its id>.signal (TestSC004_*).eval.ymlD2 job adds thecmd/tpa-corpus/**path filter and a blocking step that runstpa-corpus verify/validateplus the control, category, SC-004 and embedded-corpus tests.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.mdandsecurity-quarantine.mdare updated.Classes
tpa_hidden_instruction_blocktpa_comment_directivetpa_sensitive_file_coaxingtpa_tool_shadowingtpa_cross_server_overridetpa_rug_pull_phrasingtpa_exfil_redirecttpa_credential_harvestingtpa_persistence_implanttpa_instruction_overridetpa_conversation_exfiltpa_preference_manipulationtpa_concealment_directivetpa_invisible_texttpa_payload_executiontpa_schema_injectionSources include:
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
scan-eval --corpus detect_corpus_v1.json --gate --min-recall 0.90 --max-fp 0.05(embedded default)--bundle internal/security/scanner/bundled/scanner-bundle.jsonexitGateBreach)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)
detect_corpus_v1.jsonin place. This follows the Spec 077 precedent, and no test pins that file. Hard negatives are namedhn_<class>_*, where the validator derives the prefix fromtpacorpus.Classes.level, as the contract first said, would be meaningless.internal/security/tpacorpus/testdata/bundle_v0.1.0.json, so attack-lab and runtime CheckIDs and behaviour don't move.0001matches any<IMPORTANT>block containingread;0004/0005match any<IMPORTANT>…send_email…</IMPORTANT>;0006matches "change the recipient".notes,context,metadata…), so legitimateprivate_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.tpa_markdown_image_beaconclass was dropped: no verifiable MCP-specific primary source was found.Integration with US1 (#1701) and US3 (#1720)
origin/mainwith US1 is merged into this branch. The conflicts were resolved as follows:tpa_bundle.go: US1 split loading intoparseBundleManifest+compileBundle. TheSignaturesfield sits next to US1's manifest fields (sequence,key_id,trust_epoch) inrawBundle.validateSignatures(b)runs at the tail ofcompileBundle, so every candidate path enforcessignatures[]integrity: embedded, file drop, rollback, and the activation self-check.tpa_bundle_eval.go:LoadBundleCheckis rewired from the removedloadBundleWithInfotoparseBundleManifest+compileBundle, and now also reportsFullDigest. 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..gitattributeskeeps both blocks (US1'sbundlesig/testdata/* -textand US2's LF pins);website/sidebars.jskeeps both pages;ROADMAP.mdis regenerated.scanner-bundle.json: US1 did not change the embedded bundle, so no header keys were ported, andtpa-corpus verifystill passes.internal/security/scanner/bundled/selfcheck-corpus.json. That copy is re-synced from the extendeddetect_corpus_v1.json, whichTestSelfCheckCorpusByteParityenforces.Verified on a live instance
Built
./mcpproxyfrom this branch at the final rules (re-run after the last review round). It ran on127.0.0.1:18573with a scratch--configand--data-dir, with Docker isolation off.Two stdio fixture servers were added via
POST /api/v1/serverswithquarantined: 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 asend_emailwith a cc list.Verification
go test -race ./internal/security/... ./cmd/scan-eval/... ./cmd/tpa-corpus/... ./specs/065-evaluation-foundation/datasets/ ./tests/tpa-attack-lab/andgo test -race -run 'Quarantine|Scan|QAPilot|Conformance|TPA|Bundle' ./internal/runtime/are green.TestSEC01_RealWorldBenignCorpora_NoNewHolds: 527 LiveMCPTool and 45 reference tools; no re-baseline) andTestCoverage_BenignNotHardBlocked../scripts/test-api-e2e.shpasses 69/0 with the server-edition binary built.golangci-lintv2 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.github-copilot/gpt-6.1-solran 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