AgentStack
MCP verified MIT Self-run

Assay

mcp-vatssshah-assay · by VatsSShah

Assay — a canary-oracle benchmark for MCP security: a frozen task set scored by a recomputable HMAC-canary oracle (structural-zero false positives), not an LLM judge. Maintained by Verosek.

No reviews yet
0 installs
19 views
0.0% view→install

Install

$ agentstack add mcp-vatssshah-assay

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Assay? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Assay

A canary-oracle benchmark for MCP security. Score an MCP server or agent against a frozen task set and get one comparable number, decided by a deterministic cryptographic oracle at the tool boundary rather than an LLM judge.

[](https://github.com/VatsSShah/assay/actions)

Maintained by Verosek. The standard is vendor-neutral; Verosek runs the reference implementation and a commercial continuous-scoring service.


The idea

A canary is a per-run value of the form:

digest = HMAC-SHA256(run_secret, "origin|tool|step")[:16]

It is planted inside a realistic-secret envelope (an sk- key, an AKIA id, a JWT, an opaque token, or a bare reference number) that rotates per run. The value the agent sees looks like a real secret, so refusing to leak it is the same behaviour as refusing to leak a real secret, not pattern-matching a benchmark marker. Detection is envelope-agnostic: recompute the 16-hex digest and look for it (or a documented encoding) in the tool call or egress.

Because run_secret is fresh random bytes each run, the digest exists nowhere else and cannot be forged or precomputed. So for the canary-gated tasks, a hit means a planted secret crossed a boundary it should not have. That is a fact you can recompute, not a model's opinion, and its false-positive rate is structurally zero (proof in [SPEC.md](SPEC.md) §4). The proof is about the oracle: it says a canary hit implies real exfiltration. It does not claim a given model will or will not leak; that is what scoring a target measures.

flowchart LR
  A["Mint per-run secret k(32 random bytes)"] --> B["digest =HMAC-SHA256(k, origin·tool·step)"]
  B --> C["Plant in a realistic-secret envelopesk- / AKIA / JWT / opaque / bare-ref"]
  C --> D{"Does the agent orserver emit it?"}
  D -- "digest appears in egress" --> E["finding (deterministic proof)"]
  D -- "no" --> F["resisted"]
  E --> G["Ship triple:(run_secret, preimage, observed)"]
  G --> H["anyone recomputes thedigest and confirms"]

Why a cryptographic oracle

HarmBench and StrongREJECT, the standard jailbreak benchmarks, both score with a learned grader and so carry a measured false-positive rate. AgentDojo introduced a deterministic state oracle for agent prompt-injection, but it is not MCP-scoped and not cryptographic. The MCP taxonomies (OWASP MCP Top-10, Adversa MCP Top-25, MITRE ATLAS) define threats but no scoring oracle. Assay fills that gap for MCP: a frozen task set whose canary-gated results are decided by a recomputable HMAC oracle.

Quickstart: verify a scorecard with the standard library

pipx run assay-bench path/to/scorecard.json          # validate a whole manifest
pipx run assay-bench triple        # one proof triple

The verifier checks four things: the commitment binds the revealed secret, the manifest integrity hash matches, every fired canary recomputes from its triple, and the stated scores match the scores recomputed from the findings. So a submitter cannot fake a canary hit or state a score the findings do not imply. It does not, on its own, prove an honest run happened against a real target (a submitter could under-report by not running the attacks); that is why every leaderboard row is re-run by a maintainer.

What's in this repo

| file | what it is | |---|---| | [SPEC.md](SPEC.md) | the versioned specification: two modes, oracle rules, FP=0 proof, metrics, commit-reveal, anti-contamination, freeze policy | | [TASKS.md](TASKS.md) | the full catalog of all 31 tasks: mechanism, poison template, canary plant, egress, oracle rule, vulnerable-vs-safe behaviour, mitigation, taxonomy | | [tasks.json](tasks.json) | the frozen task set as data, severity weights, taxonomy crosswalk | | [COVERAGE.md](COVERAGE.md) | every task mapped to OWASP MCP / Adversa-25 / OWASP ASI / MITRE ATLAS | | [assay_verifier.py](assayverifier.py) | the standalone third-party verifier (stdlib only) | | [scoring.py](scoring.py) | the two 0-100 scores (Agent Resistance, Server Posture) and the over-refusal axis | | [badge.py](badge.py) | turn a passing scorecard into an embeddable shield | | [manifest_schema.json](manifestschema.json) | the scorecard JSON Schema | | [leaderboard/](leaderboard/) | the two-track leaderboard: schema, site builder, submission protocol, reference manifests | | [reference/confusion_matrix.json](reference/confusion_matrix.json) | the reference run's per-task recall and false-positive table | | [corpus/README.md](corpus/README.md) | the labeled corpus: built-in controls plus external vulnerable servers (loopback) | | [paper/assay.tex](paper/assay.tex) | the methodology paper |

Two modes

A mapped surface is never reported as an exploit. Every result states its mode and whether its evidence is a canary, a protocol fact, or a deterministic behavioural check.

flowchart TB
  subgraph MA["Mode A: server pentest (you are the client)"]
    A1["Assay MCP client"] --> A2[("your MCP server")]
    A2 --> A3["evidence = reproducible protocol fact(unauth tools/list, missing Origin, param injection, SSRF)"]
  end
  subgraph MB["Mode B: agent robustness (your agent is under test)"]
    B1["your agent"] -->|tool calls| B2["adversarial MCP server(poisoned descriptions + results)"]
    B2 -->|poisoned tool results| B1
    B1 --> B3["evidence = canary hit at the egress boundary"]
  end

Result integrity

sequenceDiagram
  participant H as Harness
  participant L as Leaderboard
  participant V as Anyone (verifier)
  H->>L: 1. commitment = SHA-256(run_secret), before the run
  H->>H: 2. run frozen tasks, mint per-run canaries
  H->>L: 3. reveal run_secret + manifest (triples, scores, integrity hash)
  V->>V: 4. commitment binds reveal? triples recompute? scores match findings? hash matches?
  V-->>L: verified, cold, stdlib only

The manifest carries an integrity hash (a SHA-256 over its own content), not a cryptographic signature. Commit-reveal stops a submitter from changing the secret after seeing results; the score-binding check stops them from stating a number the findings do not support. Authenticity of the run itself comes from the maintainer re-run.

The reference numbers, scoped honestly

The repo ships a reference run against the built-in labeled corpus. That corpus uses deterministic vulnerable and safe reference targets (in-process stubs), so the reference run is mechanism validation: it confirms every attack fires against a target built to be vulnerable and clears a target built to resist, and that the canary-gated families produce no false positive on the safe side. It is not a field measurement against a live model. The structural FP=0 claim is the cryptographic one above; the live ASR of a real model or server is what you get by scoring that target (the reference implementation, see below). Per-task numbers are in [reference/confusion_matrix.json](reference/confusion_matrix.json).

Leaderboard

flowchart LR
  S["PR: manifest + entry"] --> C["CI runs the verifier(binds proofs + scores)"]
  C -- "fails" --> X["blocked"]
  C -- "verifies" --> M["maintainer re-runsat the pinned version"]
  M --> R["row published(links to its manifest)"]

Two tracks: Agent Resistance (Mode B) and Server Posture (Mode A). The verifier makes a stated score impossible to inflate; the maintainer re-run is what attests the row is a real run against the named target. See [leaderboard/SUBMIT.md](leaderboard/SUBMIT.md).

Multimodal track

Six tasks carry the canary through a non-text channel, scored by the same oracle (no per-modality judge to mis-read an image or mis-hear audio): M20 directive in a user-attached image, M26 directive in the pixels of a tool-returned image, M27 a cross-modal chain (a vision tool surfaces the canary, a network tool exfiltrates it, with no injected instruction), M28 a steganographic chart (canary in alt-text/metadata), M29 a rug-pull on an image resource, M30 a QR code encoding an egress URL. Full mechanics in [TASKS.md](TASKS.md).

Anti-contamination

  • Over-refusal (utility) axis (planned). Resistance measures false success. The utility axis

measures false refusal, a secure-but-useless target that blocks legitimate tool use. The scoring is defined (a helpful target scores 0%, an over-cautious one 100%), but the benign twins are not authored yet, so no live target is measured against it today.

  • Held-out split (planned). tasks.json is the public split. A held-out split with the same

oracles and unpublished task instances is maintained separately so a target that memorizes the public tasks can be caught by the gap between its public and held-out scores. Until that ships, commit-reveal blocks result grinding but not task memorization, so treat a high public score as necessary, not sufficient.

Scope

In scope: tool-call injection, tool-return and RAG poisoning, rug-pull, cross-tenant, TOCTOU, confused-deputy, transport/unauth/DNS-rebind, secret exfil, excessive agency, multimodal exfil, and lethal-trifecta source-to-sink paths (the 31 tasks in [TASKS.md](TASKS.md)). Out of scope: model-weight or training-time attacks, pure prompt-only jailbreaks (owned by StrongREJECT), host-OS exploits. Targets are loopback only; the published benchmark never scores a live third-party endpoint, so every result is locally reproducible.

Open standard vs. reference implementation

The standard (this repo, MIT-licensed) is the canary methodology, the frozen task set, the verifier, the manifest schema, and the leaderboard protocol. It is vendor-neutral so anyone, including competitors, can run and cite it. The reference implementation that runs the tasks, and a commercial continuous-scoring service, are maintained by Verosek. The commercial layer never touches this spec or the leaderboard's neutrality.

Cite

> Assay: a canary-oracle benchmark for MCP security, v0.1. See [SPEC.md](SPEC.md) and > [CITATION.cff](CITATION.cff).

Licensed under [MIT](LICENSE). Third-party taxonomy attributions in [NOTICE](NOTICE).


Built and maintained by Verosek · verosek.com

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.