A defensive range for MCP and AI-agent attacks.
Reproduce a published insecure default (ATTACK-OK) and ship a detection. The detection is the
deliverable, not the exploit.
Design · Attack catalog · Quick start · Adding an attack
Every module opens with its own recording, driven from a real browser against the actual affected SDK release, and ending in command output the attacker received.
| Attack 01, CORS session hijack CVE-2026-34237. An MCP server on the developer's own machine is assumed private because "only localhost can reach it." That assumption rests entirely on the browser. The affected transport answers every request with Access-Control-Allow-Origin: * and discloses the session id on the SSE endpoint event, so a page opened once can read that session, replay it, and drive run_command. The clip ends with the canary output at the attacker's collector, under the same session id. |
Attack 02, DNS rebinding CVE-2025-66414. The attack that survives the fix for 01. Checking Origin stops a cross-origin page; it does not stop the attacker's own page from becoming same-origin with the target. One DNS answer flips and the same tab, at the same URL, with no second click, is talking to the MCP server on the developer's machine. The clip ends with the command output printed in the page itself. |
Reading a CVE tells you a flaw exists. It does not tell you what it looks like in your telemetry at 03:00, which is the only form of that knowledge a defender can act on. So each module takes one published CVE, reproduces it end to end until the attack actually lands, and then answers the question that matters: what signal did that leave, and what rule fires on it? A module is finished when both halves exist and the isolated VM has run them green.
A module is a directory. Everything one attack owns lives in modules/<NN-slug>/: its manifest,
its scenario, its compose file, its detection, its evidence, its writeup. There is no central catalog
to edit and no registry to update, because range discovers modules by globbing
modules/*/module.yml. Adding an attack means adding one directory and changing nothing else.
A shared engine does the rest: engine/harness/ holds the scenario SDK and the transport clients,
engine/cli/ implements the ./range command, and engine/compose.yml is the sealed base every
module is merged on top of.
| Defensive by construction | Every module ships the full loop: reproduce, then detect. An exploit without a detection is not a module here. |
| Anchored to real CVEs | Each attack maps to published CVEs (CVSS per anchor) and to the OWASP LLM Top 10 plus the emerging MCP Top 10. |
| Manifest-driven | Each module.yml is the source of truth for its own attack. The catalog table and the OWASP map are generated from them, never hand-edited. |
| Detections you can take with you | Machine-readable ATR rules carry their own test_cases, run offline as unit tests, and export to Sigma or Elastic with ./range export. |
| Contained by default | An isolated VM, a no-egress Docker network, one module deployed at a time, benign canaries only. |
Built from the module manifests. CVSS shown per anchoring CVE. See the
OWASP map for the full LLM / MCP / ASI / CWE breakdown, and
docs/BACKLOG.md for what is queued next.
| # | Attack | OWASP | Anchor CVE (CVSS) | Repro | Detect | Blog |
|---|---|---|---|---|---|---|
| 01 | CORS Session Hijack | LLM06 | CVE-2026-34237 (6.1) | ✅ | ✅ | soon |
| 02 | DNS Rebinding | LLM06 | CVE-2025-66414 (7.6) | ✅ | ✅ | soon |
Legend: ✅ shipped and VM-verified · 🧪 authored, pending VM verification (./range verify).
Everything below runs on the lab VM and nowhere else. Mark the VM once with
sudo touch /etc/meridian-vm, or prefix a single command with MERIDIAN_ON_VM=1.
./range list # what modules exist
./range up 01 # sealed tier: internal network, no published ports
./range run 01 # reproduce -> [ATTACK-OK]
./range verify 01 # the gate; writes modules/01-*/evidence/vuln.txt
./range matrix 01 # the same attack against every SDK version it declares
./range down # tear down everything on this hostOn the authoring host, nothing starts and everything is offline:
./range check # manifests, module structure, detections, the no-ports gate
./range detect-test # detection rules against their own test cases
./range export --format sigma --out /tmp/pack
./range plan 01 --tier split-host --side victim # print the deploy command, run nothing
./range sync --build 01 # push to the VM and rebuild that module's server thereFull walkthrough: docs/getting-started.md. Every command carries a
[dev] or [VM] marking in ./range --help: [VM] refuses to run anywhere else.
./range new 03 tool-poisoning --name "Tool Description Poisoning"That scaffolds the whole module directory from the template, and ./range check then enforces that
shape on every commit. The ordered checklist and the contribution mechanics are in
docs/CONTRIBUTING.md.
The two modules that exist are worth reading end to end: module 01, CORS session hijack is the full study, from the transport defect to the detection and the one-line fix. Module 02, DNS rebinding is a separate attack that defeats the Origin control which stops module 01.