Reconnaissance and Enumeration for Agent Protocols
REAP is black-box reconnaissance for AI agent endpoints. Point it at a URL you're authorized to test and it identifies what agent protocol is running, enumerates the capability surface exposed to the caller, and reports the auth and transport posture around it, without ever invoking a single thing it discovers.
Use only against systems you own or are explicitly authorized to test. Unauthorized access to computer systems is illegal in most jurisdictions even when every request is read-only. See
SECURITY.md.
The MCP gateway your team shipped last sprint. The agent endpoint a bug bounty program just put in scope. The internal service someone stood up behind a load balancer with allowedOrigins: ["*"] and forgot about. That's what REAP does: external, unauthenticated, black-box recon against a live agent endpoint, from the position an actual attacker occupies.
It reads, it never invokes A probe is only ever handed a Session, and Session exposes no method to call a tool. See docs/ARCHITECTURE.md for the boundary.
It's agent-native Anyone can point nuclei at an endpoint and check TLS and CORS. REAP checks the things that only make sense once you know you're talking to an agent: whether the full tool inventory answers to an anonymous caller, whether the handshake instructions field leaks operator prompt material, whether a dynamic-dispatch tool is hiding a capability surface much larger than tools/list admits to.
It's built for pipelines and CI Text for humans, NDJSON for pipelines, SARIF 2.1.0 for GitHub Advanced Security and every other code-scanning ingestion point. Single static Go binary, zero third-party dependencies, no API key, no telemetry, nothing leaves your machine except the requests you asked for.
It's designed to outlive MCP MCP is the only supported protocol today, and it's the right first target. But the architecture (a protocol registry, per-protocol sessions, declarative checks) assumes there will be others.
go install github.com/hackwither/reap/cmd/reap@latestOr build from source:
git clone https://github.com/hackwither/reap
cd reap && go build -o bin/reap ./cmd/reapRequires Go 1.22+. No other dependencies.
Scan a single endpoint:
reap -t https://your-host/mcp --authorizedWith credentials, to see what an authenticated caller gets:
reap -t https://your-host/mcp --auth-header "Bearer $TOKEN" --authorizedMachine-readable, written to a file:
reap -t https://your-host/mcp --authorized --output json --out results/report.jsonA whole scope list, concurrently, as NDJSON:
cat scope.txt | reap --authorized --output json --concurrency 10In CI, as SARIF:
reap -t "$MCP_ENDPOINT" --authorized --output sarif --out reap.sarif# .github/workflows/agent-recon.yml
- name: Upload REAP findings
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: reap.sarifPoint -t at a URL without knowing the protocol, and --protocol auto figures out whether — and how — it speaks MCP before running a single security check:
reap -t https://maybe-an-mcp-host.example --protocol auto --authorizedThis is the fix for the single biggest trust-killer a recon tool can have: firing a stack of findings against a target that was never actually confirmed to speak the protocol being scanned (a plain web server returning 200/HTML on every path looks a lot like a listener if nothing checks). Every finding carries a confidence ("high"/"medium"/"low"), and if the target never completes a real protocol handshake, reap says so loudly and caps every finding at info/low-confidence rather than reporting them at face value:
⚠ TARGET NOT CONFIRMED AS AGENT ENDPOINT
mcp initialize handshake failed: decode initialize response: invalid character '<'
looking for beginning of value. Findings below are LOW confidence and likely
reflect a generic web server, not a real MCP handshake.
--mode=discover runs Discovery only (no enumeration/assessment) and prints the resolved Fingerprint — protocol, transport, confidence, server metadata — for every target. --list-detectors lists the registered detectors, the discovery-time sibling of --list-probes.
Discovered/assumed transport also picks which Session implementation actually runs the scan: streamable-HTTP, legacy pre-2025-03-26 HTTP+SSE, or a raw WebSocket (non-standard, but observed in some community gateways) — each behind the same Session interface and the same no-invoke boundary, so every existing probe and template runs unmodified regardless of which one it lands on.
Findings map to OWASP Agentic Security Initiative categories (ASI01-ASI10), and each carries a stable rule ID, a confidence level, and — where a single request/response produced it — a reproducible curl one-liner so you can verify it by hand rather than take the tool's word for it.
| Check | What it finds |
|---|---|
mcp-unauth-tools-list |
tools/list answering to unauthenticated callers |
mcp-tool-capability-surface |
Full reported tool inventory, for asset tracking and cross-run diffing |
mcp-resources-prompts-exposure |
Unauthenticated resources/list and prompts/list |
mcp-dynamic-dispatch |
Dispatch/search tool patterns implying a hidden capability surface larger than tools/list reports |
mcp-instructions-exposure |
Handshake instructions leaking operator prompt material |
| Check | What it finds |
|---|---|
mcp-oauth-metadata-posture |
OAuth metadata endpoints missing Bearer challenge or PKCE advertisement |
mcp-redirect-uri-laxity |
Overly broad or wildcard OAuth redirect URI registration |
mcp-session-id-entropy |
Weak or predictable session identifiers |
mcp-host-header-validation |
Servers not validating Host during initialize |
| Check | What it finds |
|---|---|
mcp-plaintext-transport |
Endpoints served over plaintext HTTP |
mcp-transport-downgrade |
The same host also accepting MCP traffic in the clear |
mcp-tls-cert-health |
Certificate validity, hostname mismatch, weak protocol and cipher selection |
mcp-cors-wildcard |
Wildcard CORS, and wildcard combined with credentialed cross-origin access |
mcp-rate-limit-absence |
Missing standard rate-limit headers on successful responses |
reap --list-probes prints the live set. Select with --include / --exclude.
Drop a JSON template in templates/, no Go, no rebuild:
{
"id": "mcp-tmpl-custom-header-leak",
"protocol": "mcp",
"info": {
"title": "Internal service header disclosed",
"severity": "medium",
"asi_refs": ["ASI09"],
"description": "tools/list responses disclose an internal-service header."
},
"request": { "method": "tools/list" },
"match_logic": "all",
"matchers": [
{ "type": "status_code", "equals": 200 },
{ "type": "header", "header": "X-Internal-Service" }
]
}Matchers: status_code, header, body_contains, json_path, combined with any (default) / all via match_logic. See docs/WRITING_PROBES.md for the full field reference, including info.references and how confidence/reproduction are derived automatically.
A template deliberately cannot chain requests, branch on response data, or invoke a discovered tool. The first two are a roadmap item. The third never will be. If your check needs real logic, write a Go probe in internal/probe/<protocol>/checks.go, see CONTRIBUTING.md.
Shipped: automatic protocol/transport discovery (--protocol auto, --mode discover), confidence-scored findings with a hard downgrade for unconfirmed targets, legacy HTTP+SSE and WebSocket transports alongside streamable-HTTP.
Planned, not yet shipped:
- stdio transport: a manually-specified local MCP server launch (
--target-stdio), behind the sameSessioninterface and the same no-invoke boundary. - Bounded range discovery: sweep an operator-supplied host and port list for agent endpoints.
- More protocols: A2A, and whatever else reaches deployment scale.
Issues and PRs welcome on any of these.
See CONTRIBUTING.md. The one non-negotiable: nothing merged into REAP invokes a discovered capability. Every new protocol, transport, probe, and template is held to that boundary, and docs/ARCHITECTURE.md explains how it's enforced in the type system rather than by review.
MIT. See LICENSE.
Built by @hackwither.