rampart
Offline, dependency-free transport-layer hardening auditor for MCP Streamable HTTP endpoints, probing for DNS rebinding, CORS, session-ID, cleartext, and protocol conformance defects with a severity-weighted score and CI gate.
README
Development Experiment 023 — rampart
Offline, dependency-free transport-layer hardening auditor for the MCP Streamable HTTP transport.
rampart probes (or audits a recorded probe of) an MCP Streamable HTTP endpoint for the transport-perimeter defects the MCP spec's three-line "Security Warning" exists to prevent — and that half a dozen 2026 CVEs prove SDKs keep getting wrong:
- DNS rebinding via missing/naive
OriginandHostvalidation - permissive / credentialed CORS
- weak, guessable, reused or state-leaking
Mcp-Session-Idvalues, and session fixation - cleartext / NeighborJack exposure and missing auth on non-loopback binds
MCP-Protocol-Versionand endpoint/method conformance
It ships two byte-stable reference servers — a hardened one that scores a clean 100/A, and a vulnerable one that demonstrates 15 ground-truth defects (three CRITICAL) — plus a severity-weighted score, letter grade and a CI exit-code gate.
Pure Python standard library. No third-party runtime dependencies. pytest only for tests.
Thesis
Experiments #011–#022 in this series each audited one message/content channel of MCP
(tools, prompts, resources, roots, sampling, elicitation, completion, tasks, the stateless
lifecycle, and cross-channel taint flow). #016 deputy covered OAuth authorization.
But underneath every one of those sits the raw HTTP transport, and that is where MCP
is actually bleeding in 2026: the most-CVE'd real MCP vulnerability class of the year is
DNS rebinding against local MCP servers — the Python, Java, Go and Ruby SDKs and the
official MCP Inspector (CVSS 9.4) all shipped without the Origin validation the spec
marks MUST. Backslash's "NeighborJack" scan found most public MCP servers bind
0.0.0.0 by default; BlueRock found 36.7% of 7,000 servers SSRF-exposed.
The MCP spec's transport hardening requirements are three sentences long. rampart turns those three sentences (plus the session-ID contract and the protocol-version rule) into a battery of active HTTP probes and a deterministic rule engine, so a developer can answer "is my MCP endpoint rebinding-safe?" in one command — the smallest thing that proves the transport perimeter can be checked mechanically, offline, with no LLM and no dependencies.
This is the series' second dynamic entry (after #011 gauntlet, which drove stdio)
and its first on the HTTP transport / perimeter layer.
How to run
# 1) Audit the bundled reference servers end-to-end (spins up loopback servers, probes them)
python main.py demo --kind both
make run # equivalent
# 2) Probe a live MCP endpoint you own
python main.py probe http://127.0.0.1:8000/mcp
python main.py probe https://my-mcp.example/mcp --fail-on high --min-score 90 --json
# 3) Audit a previously recorded probe report, fully offline
python main.py audit captures/vulnerable.probe.json
# 4) Print the rule catalog
python main.py catalog
# 5) Regenerate the shipped captures
python main.py save-captures captures
# Tests
python -m pytest # 60 tests
make test
No installation is required (zero runtime deps). Optionally pip install -e . to get a
rampart console script.
Exit codes (CI gate)
0 pass · 2 gate failed (a finding at/above --fail-on, default high, or score below
--min-score) · 1 usage/runtime error. Drop rampart probe … --fail-on high into a CI
step to block a rebinding-vulnerable server from shipping.
Architecture
touches network pure & deterministic
┌───────────────────────────┐ ┌────────────────────────────────────┐
│ prober.py │ Probe │ analyzer.py catalog.py │
│ crafts a fixed battery │ Report │ ProbeReport -> [Finding] │
│ of HTTP probes, each ├───────►│ 6 family analyzers, 24 rules │
│ isolating ONE property │ (JSON)│ no clock, no randomness, no net │
└───────────────────────────┘ └───────────────┬────────────────────┘
▲ │
│ drives ▼
┌────────┴───────────┐ scoring.py (weight→score→grade→gate)
│ refserver.py │ report.py (text / JSON)
│ hardened | vulnerable│ cli.py (probe|audit|demo|catalog)
│ one handler, a Policy│
└─────────────────────┘
Separation of observation from judgement is the core design choice. The prober is the
only module that touches the network; it emits a normalised ProbeReport (status, headers,
a small body excerpt per probe — plain data, no interpretation). The analyzer consumes a
ProbeReport and is pure: no network, no clock, no randomness, so the same report always
yields the same findings. Two consequences fall out for free:
- rampart grades a live probe and a recorded report through the identical code
path (
probevsaudit), so evidence can be captured once and re-audited forever. - every one of the 24 rules is unit-testable with a hand-written
ProbeReport— no sockets in the rule tests.
Each probe isolates exactly one property. The origin_foreign probe sends a foreign
Origin but a legitimate Host, version and session, so a finding points at Origin
validation and nothing else. This "one variable per probe" design is what lets the analyzer
map an outcome straight to a rule.
The reference servers share every line of transport code and differ only in a Policy
dataclass (validate Origin? exact vs substring match? enforce sessions? secure vs sequential
session factory? …). That is precisely rampart's thesis rendered executable: the gap between
a safe and a catastrophic MCP server is configuration/defaults, not code.
Rule families (24 rules)
| Family | What it checks | Flagship |
|---|---|---|
| RBND | DNS rebinding: Origin/Host validation (spec MUST) |
RBND01 foreign Origin accepted · RBND02 foreign Host accepted (rebinding) — CRITICAL |
| CORS | Cross-origin sharing posture | CORS03 credentialed wildcard — CRITICAL |
| SESS | Mcp-Session-Id entropy, format (0x21–0x7E), uniqueness, fixation, enforcement |
SESS05 client-supplied session accepted (fixation) |
| XPORT | Cleartext, NeighborJack bind, missing auth, verbose errors | XPORT01 cleartext to remote host |
| VER | MCP-Protocol-Version conformance (MUST 400 on invalid) |
VER01 |
| CONF | Streamable-HTTP endpoint/method conformance (GET→405, notify→202) | CONF01 / CONF02 |
Severity weights: CRITICAL 40 · HIGH 15 · MEDIUM 6 · LOW 2 · INFO 0. Score = max(0, 100 − Σweights). Grade: A≥90, B≥75, C≥60, D≥40, else F.
Trade-offs & design decisions
- Dynamic prober vs passive log auditor. A passive auditor (like #013–#022) can only
see what a capture happens to contain; transport hardening lives in how a server responds
to a hostile request, which a benign trace never exercises. So rampart is an active
prober — but it stores its evidence as a
ProbeReportand analyses that, keeping the offline-auditable, deterministic, LLM-free character of the series (and enablingsave-captures→ commit →auditin CI without a live server). - Loopback-only reference servers. The vulnerable server is a teaching artefact; it
binds
127.0.0.1:0and derives its weak session IDs from a counter + fixed epoch base, so captures are byte-stable and nothing insecure is ever exposed. - Heuristic session analysis. Entropy (
SESS02) and sequential-ID (SESS01) detection are heuristics over a small sample, not a statistical randomness proof — deliberately conservative (64-bit floor; boundary-anchored epoch match) to avoid flagging real cryptographic IDs. This matches the "cheap, offline, high-signal" posture of the series. Origin: absentis INFO, not a defect. Browsers always attachOriginon cross-origin requests and legit CLI clients omit it, so missing-Origin acceptance is not itself exploitable; it is recorded (RBND05) but never lowers the score. This is why the hardened server scores a true 100/A while remaining CLI-compatible.http.serverover a framework. Zero dependencies keeps the prototype auditable and portable; the cost is a hand-rolled minimal MCP endpoint, which is fine for a prober target.
Assumptions & limitations
- rampart audits the transport perimeter, not application logic or the message channels (those are experiments #011–#022). A 100/A means the HTTP front door is hardened, not that the tools behind it are safe.
- The prober assumes a single MCP endpoint path (default derived from the URL, e.g.
/mcp) that speaks the Streamable HTTP transport. Old HTTP+SSE-only servers will read as conformance findings, not crashes. - Some checks are posture-conditional:
XPORT01/02/03(cleartext, NeighborJack, missing-auth) only fire for a non-loopback target, because they are not risks on127.0.0.1. Audit a remote deployment to exercise them (unit tests cover them synthetically). - rampart sends a small, benign battery (initialize / tools/list / notifications / a malformed body / a CORS preflight). It is a hardening check for servers you own or are authorised to test, not an internet scanner.
- Session entropy/prediction is heuristic (see trade-offs). A server using a novel-but-secure
scheme could in principle draw a false
SESS02; the 64-bit floor is set well below a UUIDv4.
Sample run
python main.py demo --kind both (loopback ports vary per run):
### reference server: hardened ###
======================================================================
rampart - MCP Streamable HTTP transport hardening audit
======================================================================
target : http://127.0.0.1:64673/mcp
host : 127.0.0.1 (loopback=True, scheme=http)
note : probed 14 vectors, 4 session IDs sampled
SCORE : 100/100 GRADE: A
FINDINGS: 1 [critical=0 high=0 medium=0 low=0 info=1]
----------------------------------------------------------------------
[RBND]
INFO RBND05 Missing Origin accepted on a state-changing request
A state-changing request with no Origin header was accepted (HTTP 200).
----------------------------------------------------------------------
### reference server: vulnerable ###
======================================================================
rampart - MCP Streamable HTTP transport hardening audit
======================================================================
target : http://127.0.0.1:64692/mcp
host : 127.0.0.1 (loopback=True, scheme=http)
note : probed 14 vectors, 4 session IDs sampled
SCORE : 0/100 GRADE: F
FINDINGS: 15 [critical=3 high=4 medium=2 low=5 info=1]
----------------------------------------------------------------------
[RBND]
CRITICAL RBND01 Foreign Origin accepted
Request with Origin 'https://attacker.example' was accepted (HTTP 200); the server does not validate Origin.
CRITICAL RBND02 Foreign Host accepted (DNS rebinding)
Request with a foreign Host header 'attacker.example' was accepted (HTTP 200) over a loopback connection.
HIGH RBND03 Origin allowlist bypass via suffix/substring match
Suffix-bypass Origin 'http://127.0.0.1:64692.attacker.example' was accepted (HTTP 200); validation likely uses substring matching.
HIGH RBND04 Origin: null accepted
Origin: null was accepted (HTTP 200).
INFO RBND05 Missing Origin accepted on a state-changing request
[CORS]
CRITICAL CORS03 Credentialed CORS for a foreign origin
Access-Control-Allow-Origin: * is combined with Access-Control-Allow-Credentials: true, exposing authenticated responses to a foreign origin.
LOW CORS04 Overly permissive CORS methods/headers
[SESS]
HIGH SESS01 Predictable / sequential session IDs
Issued session IDs are sequential: ['sess-1-1700000001', 'sess-2-1700000002', ...].
HIGH SESS05 Client-supplied (unissued) session ID accepted (session fixation)
MEDIUM SESS06 Request without session ID accepted where a session is required
MEDIUM SESS07 Session ID embeds decodable / sensitive data
Session ID leaks internal state (embeds an epoch-like timestamp 1700000001): 'sess-1-1700000001'.
[XPORT]
LOW XPORT04 Verbose error / stack-trace disclosure
[VER]
LOW VER01 Invalid MCP-Protocol-Version not rejected with 400
[CONF]
LOW CONF01 GET without SSE support does not return 405
LOW CONF02 Notification/response not acknowledged with 202
----------------------------------------------------------------------
The demo --kind both exits 0 because the hardened server passes the gate; the
vulnerable server's expected failure is reported but does not fail the run. demo --kind vulnerable exits 2.
Layout
rampart/
model.py ProbeResult / ProbeReport / Finding / Severity
catalog.py 24 rules across 6 families (identity & description)
analyzer.py pure ProbeReport -> [Finding] engine (per-family)
scoring.py severity weights -> score/grade + CI gate
report.py text and JSON renderers
prober.py active HTTP probe battery (the only networked module)
refserver.py hardened + vulnerable reference servers (one handler, a Policy)
cli.py probe | audit | demo | catalog | save-captures
captures/ hardened.probe.json (100/A) · vulnerable.probe.json (0/F)
tests/ 60 tests: per-rule analyzer, scoring, catalog, e2e loopback, CLI
main.py convenience entry (== python -m rampart)
See RESEARCH.md for the 2026 signals — CVEs, incidents and spec text — that motivated it.
License
MIT — see LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.