Security tooling

Security Scan

Active vulnerability scan for any MCP proxy you've created. The scanner pushes payloads through the gateway with an internal trust path, captures every response, and emits findings tagged by severity, OWASP category, CWE, and a copy-pasteable rule the operator can apply.

What the scan does#

When you launch a scan against an MCP proxy, the platform runs a curated set of probes against every tool the proxy exposes. Each probe targets a specific weakness class — SQL injection, command injection, SSRF, broken authorisation, tool poisoning, blind RCE via DNS callback, and so on. The scanner sends payloads, watches the response for known fingerprints, and rolls up confirmed hits into findings.

The scan is always run throughthe AIronClaw gateway — the same path real callers take in production. That means rules, DLP, rate limits and prompt guards you already have configured are also applied to probe traffic. A "clean" scan tells you the firewall is doing its job for the patterns the scanner can simulate.

What it is NOT

The scan is not a passive linter and not a static analyzer. It generates real traffic against the real upstream over your proxy. Use a test environment if your upstream has side effects (writes records, sends emails, charges money). The target profile lets you opt into the heavier probes only when you know the upstream is safe.

How to trigger a scan#

From the dashboard, open Security Scan in the sidebar. Pick the MCP proxy you want to test and click Run new scan. The run is queued immediately and progress streams into the dashboard in real time — you can watch probes start, requests fly, and findings land as they're confirmed.

Each user has a hard cap of one concurrent run at a time. Daily budget defaults to 50 scans / 24h and 500 upstream requests per scan. Both caps are configurable per account.

What you control at run time

MCP target
required
Which of your MCP proxies to scan. The scanner enumerates the proxy's tools via tools/list and probes each one.
Profile
standard | aggressive | spec-only | cve-only
Standard runs the curated probe set. Aggressive adds invasive variants. Spec-only stays at protocol level (no fuzzing). CVE-only replays the per-CVE PoCs only.
Cancel
anytime
Long-running scans can be stopped from the run page. The scanner stops at the next probe boundary and emits a 'cancelled' status.

Target profile#

For each MCP you can pre-declare a target profile so the scanner narrows its payload packs and toggles heavy probes appropriately. The profile lives on the MCP, not on the individual run — you set it once.

Profile fields

Database family
auto / postgres / mysql / sqlite / mssql / oracle / mongo / redis / dynamodb / none
When set, SQL injection payloads are narrowed to the declared engine (Postgres-specific error tokens only, no MySQL noise).
Backend runtime
auto / node / python / go / java / ruby / php / rust
Runtime fingerprint helps the scanner choose error-token packs and skip CVE replays that don't apply.
Hosting
auto / aws / gcp / azure / on_prem / other
Cloud-specific SSRF markers are enabled when the hosting matches (e.g. AWS metadata endpoint detection).
Tool surfaces
filesystem / shell / db_query / http_client / mailer / browser / k8s
When you declare surfaces, agent-specific canaries are boosted (mailer enables the Bcc-smuggler canary, browser enables the rug-pull canary).
Disable 5xx abort guard
boolean (default off)
By default the scan aborts when more than half the upstream calls return 5xx (broken upstream). Some MCPs (n8n, web-search) return 5xx legitimately on fuzz input — enable this flag for those.
Enable billing-bomb probe
boolean (default off)
Opt-in to the rate-limit-stress probe. Fires 20 parallel calls against tools whose name suggests an expensive backend (search, generate, embed, llm). Keep it off unless the backing tools are safe to burst against — paid third-party APIs will get charged.
Auto everywhere is fine

Profile is optional. Leaving everything on auto triggers a lightweight fingerprinting probe at the start of the scan that infers most of these values from the upstream's responses. Declaring the profile up front just saves a handful of requests.

Probe families#

The scanner ships with ~40 probes organised in families. Families that mostly run silently (protocol, transport, auth) are always on; heavier ones (CVE replays, behavioural, OOB) are conditionally on based on target profile and account caps.

Coverage matrix

Protocol
always on
Spec compliance + protocol resilience. Bogus method, bogus tool name, malformed arguments, invalid session id, notification with id, truncated JSON envelopes, 256-byte binary garbage, 10k-deep nested JSON (parser stress). Mostly low/medium severity hygiene findings; resilience probes flag 5xx-with-stacktrace, connection resets, or hangs past budget.
Transport
always on
CORS wildcard, CORS preflight open, server banner verbose. Maps to OWASP MCP10 (Misconfigurations).
Authentication
always on
No-credentials accepted, empty/bogus bearer, common weak bearer wordlist, JWT alg=none, BFLA on admin-named tools. Maps to OWASP MCP06.
Fuzz
always on
SQL injection (error-based + boolean-blind), command injection (with output-fingerprint detection and canary-echo deterministic marker), path traversal, XSS, format string, integer overflow, header injection, open redirect, mass assignment, SSRF with internal-target detection.
BOLA-lite
always on
Broken Object-Level Authorisation. Detects tools that accept arbitrary object IDs without authorisation. Maps to OWASP API1.
Canaries
always on
Tool-poisoning regex, hidden-instructions (zero-width / bidi / ANSI / base64 in description), rug-pull (tools/list diff), AIronClaw-smuggler test, resources/list URI audit.
CVE replays
always on
Targeted PoCs for known MCP-server CVEs. Each replay confirms the exploit live, not just on version-string match. Includes LangGhost / CVE-2026-45134 (unsafe LangChain manifest deserialization) replayed against any tool that fingerprints as LangChain-shaped. Adds new CVEs from the daily feed automatically.
Content (response-side DLP)
always on
Calls each tool with safe baseline inputs and scans the response for: secret leaks (AWS / GitHub / Slack / Stripe / OpenAI / Google / LangSmith / JWT / PEM), credential blobs, PII (Luhn-valid credit cards, MOD-97 IBANs, SSNs), prompt-injection echoes, unsafe HTML/JS output, and executable manifests in responses (LangChain `lc:1` constructor blobs, pickle protocol-2/4 headers, Python marshal headers).
Spec audit
always on
Static audit of every tool's inputSchema. Flags unconstrained risky-named string fields, open objects, missing descriptions, unbounded numerics, and LangGhost-class dangerous fields (prompt_manifest / chain_config / pickled_blob / payload_b64 / etc.) that accept executable manifests. Maps to OWASP MCP04 (Insecure Tool Definitions). Excessive-agency map advisory lists every tool's capability class.
Behavioural
race always on, billing-bomb opt-in
Race condition on create-like tools (parallel envelopes detect missing idempotency). Billing-bomb fires 20 parallel calls against expensive-looking tools to detect missing rate limits — gated behind the target profile flag.
OOB-bound (blind detection)
always on when available
Blind RCE (DNS/HTTP callback via curl/wget/nslookup), SSRF OOB, blind SQLi (Postgres COPY TO PROGRAM, MySQL LOAD_FILE UNC, MSSQL xp_dirtree, Oracle UTL_HTTP), XXE OOB. Detection is asynchronous: any callback at the OOB sink during the run binds back to the probe that fired it.

Findings & severity#

Each confirmed finding carries: severity, title, affected tool and field, OWASP / CWE / CVE references, description, evidence (response excerpt + signal breakdown), a copy-pasteable proof-of-concept (curl + JSON-RPC envelope), and a remediation paragraph. When the finding maps to a known mitigation rule, you also get a Suggested rule block ready to paste into MCP rules.

Severity scale

Critical
confirmed exploit
Direct evidence of compromise — command output reflected, OOB callback received, secret returned from a tool. Production-blocking; treat as incident.
High
strong evidence
Reflection + error leak, status delta + corroborating signal. Exploitable under realistic conditions. Fix before exposing to untrusted callers.
Medium
probable
One strong signal without corroboration, or behavioural finding (e.g. race-able past an idempotency check). Worth investigating.
Low
hardening
Posture deviation: server banner verbose, missing schema constraint, agency surface present. No exploit on its own but reduces defence in depth.
Info
observational
Advisory only. Used for agency-surface listing, missing descriptions, and similar passive observations.
Triage

Each finding can be tagged with a status on the run page: open · triaged · false_positive · wont_fix · fixed. The status persists with the finding so you can come back later. There are no free-text triage notes — only the status pill.

Printable report#

Every completed scan can be exported as an A4 print-ready report. Click Generate report on a completed run. You'll get a multi-page document covering:

  • Cover page with a severity sunburst summarising the distribution of findings.
  • Executive summary with a posture pill (clean / attention / at-risk), four KPI cards (total findings, probes run, requests sent, scan duration), severity breakdown tiles, top three findings, and an auto-generated narrative paragraph.
  • Findings index table sorted by severity.
  • One section per finding with description, evidence, proof of concept, remediation paragraph, and references list.
  • Methodology appendix with scan parameters, per-probe request/finding counts, severity definitions, and report metadata.

Print via ⌘P / Ctrl+P — the page is laid out for A4 with print-color-adjust enabled so the dark cover and severity tiles preserve their styling in the PDF.

Out-of-band detection#

Some classes of vulnerability can't be detected from the response alone — the tool may exec a command and silently discard the output, or fetch an attacker URL and never echo it back. The OOB layer covers these cases by giving every probe a unique callback hostname. When the upstream (or a chain reachable from it) issues a DNS lookup or HTTP request to that hostname, the platform binds the callback back to the probe that armed it and emits a critical finding.

OOB detection runs automatically when the scanner is paired with an out-of-band sink (configured by the operator). When it isn't available, the OOB-bound probes skip themselves gracefully — the rest of the scan still runs.

OOB-bound probes

fuzz.blind_rce_oob
Blind RCE
Injects payloads that shell out to the callback (curl, wget, nslookup, dig, ping, certutil, Invoke-WebRequest). Any DNS or HTTP callback confirms execution even when the upstream returns no output.
fuzz.ssrf_oob
Server-Side Request Forgery
Sprays Interactsh-style callback URLs across every string field. Any tool that fetches the URL — directly or via an upstream chain — triggers a callback.
fuzz.blind_sqli_oob
Blind SQL injection
DBMS-specific payloads that make the database issue an outbound DNS/HTTP request: Postgres COPY TO PROGRAM, MySQL LOAD_FILE on UNC paths, MSSQL xp_dirtree, Oracle UTL_HTTP. Narrowed by the declared database family when known.
fuzz.xxe_oob
XML external entity
Targets string fields with XML-shaped names (xml / body / content / payload / template / data / soap / saml / svg). Injects external DTD references; a parser that fetches the DTD URL surfaces as an HTTP callback.
fuzz.lc_deserialization_oob
LangChain deserialization (LangGhost class)
Injects serialised LangChain manifests {lc:1, type:constructor, ...} for the top 5 provider chat-model constructors (OpenAI, Anthropic, Mistral, Cohere, Together). Each manifest carries a base_url pointing at the OOB sink. If the tool calls langchain_core.load.loads() on the field (the LangGhost / CVE-2026-45134 class), the deserialized model client phones home and surfaces a callback.
fuzz.pickle_oob
Python pickle deserialization
Sprays pickle bytes with __reduce__=(urllib.request.urlopen, (<oob-url>,)) in raw Latin-1 and base64 forms. A callback at unpickle time proves the tool ran pickle.loads() on attacker input. Broader than LangChain — catches any Python MCP that unpickles arguments.

REST API#

The full scan flow is also reachable over REST. Authentication uses the same personal access token described under Authentication.

Endpoints

POST /api/mcp/:id/scan
enqueue
Body: { profile?: 'standard'|'aggressive'|'spec-only'|'cve-only', invasive?: boolean, bypass_rules?: boolean }. Returns { runId } and queues the run. 409 if you already have a queued/running scan.
GET /api/mcp/:id/scan
list runs
Returns up to 50 most recent runs for that MCP with status, profile, counters and timestamps.
GET /api/mcp/:id/scan/:runId
run + findings
Returns the run row and the full findings list sorted by severity. Each finding has class, severity, title, description, evidence, references, poc, remediation, suggested rule, and replay envelope.
GET /api/mcp/:id/scan/:runId/events
SSE stream
Server-Sent Events of probe activity in real time. Use this to watch the run live. Supports Last-Event-ID resume.
DELETE /api/mcp/:id/scan/:runId
cancel or delete
Cancels a queued/running run, hard-deletes a terminal run (findings and events cascade).
GET / PUT /api/mcp/:id/scan/profile
target profile
Read and write the per-MCP scan target profile (database family, runtime, hosting, tool surfaces, 5xx-guard, billing-bomb opt-in).
POST /api/mcp/:id/scan/:runId/findings/:findingId
triage
Body: { status: 'open'|'triaged'|'false_positive'|'wont_fix'|'fixed' }. Updates the finding's status pill.
GET /api/scan/runs
cross-MCP listing
All recent runs across all your MCPs. Useful for dashboards and cron audits.
GET /api/scan/feed
CVE feed
Read-only browse of the curated MCP CVE feed. Refreshed daily from public advisory sources.
Programmatic & agent usage

For an end-to-end agent flow (enqueue → wait for completion → fetch findings → print posture), use the AIronClaw skill's aironclaw scan run --mcp <uuid> --wait command. It handles polling, output formatting, and exit codes so the agent can drive scans without re-implementing the orchestration.