Skip to main content
Glama

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 Origin and Host validation

  • permissive / credentialed CORS

  • weak, guessable, reused or state-leaking Mcp-Session-Id values, and session fixation

  • cleartext / NeighborJack exposure and missing auth on non-loopback binds

  • MCP-Protocol-Version and 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.


Related MCP server: mcp-security-audit

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:

  1. rampart grades a live probe and a recorded report through the identical code path (probe vs audit), so evidence can be captured once and re-audited forever.

  2. 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 ProbeReport and analyses that, keeping the offline-auditable, deterministic, LLM-free character of the series (and enabling save-captures → commit → audit in CI without a live server).

  • Loopback-only reference servers. The vulnerable server is a teaching artefact; it binds 127.0.0.1:0 and 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: absent is INFO, not a defect. Browsers always attach Origin on 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.server over 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 on 127.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.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    A stdio MCP server that audits other MCP servers over the live protocol. It connects to any MCP target (stdio or HTTP), lints every tool's schema for agent-usability, then actually calls the tools with deliberately broken inputs to see how the server handles them, and returns a 0–100 conformance score with a per-dimension breakdown rendered as Markdown.
    Last updated
    6
    6
    MIT
  • A
    license
    -
    quality
    C
    maintenance
    Security auditor for MCP servers that enumerates tools, resources, and prompts, scans for injection patterns, classifies risk levels, and produces a scored report (0-100, grades A-F).
    Last updated
    2
    MIT
  • A
    license
    -
    quality
    B
    maintenance
    Passive security scanner that audits a running MCP server against the OWASP MCP Top 10 and grades it A-F. Read-only static analysis of the advertised tools, prompts and resources with console/JSON/SARIF output, and it also runs as an MCP server itself.
    Last updated
    44
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/freyesperales/dev-experiment-023'

If you have feedback or need assistance with the MCP directory API, please join our Discord server