EasyHunt-AI
MCP server for AI-driven VAPT orchestration, enabling agents to plan and execute authorized security scans through a control plane that enforces scope, sanitization, budget, rate limits, human approval, and audit logging.
README
EasyHunt AI
Agentic VAPT orchestrator. Drives open-source security engines through a custom MCP server, runs inside the Claude CLI, and routes model traffic through OpenRouter.
Authorized testing only. Owned assets, an in-scope bug bounty program, or org assets with documented written approval. The
scope.yamlyou write is the authorization boundary, and the server refuses every request that falls outside it.
What it actually is
A control plane that sits between an AI agent and ~50 security tools. The agent plans; the control plane decides what is permitted. Every capability — engine, wrapper, or user-supplied plugin — passes through one fixed sequence:
scope → sanitize → budget → rate-limit → approval → sandbox exec
→ parse/normalize → audit → structured return
There is no flag, argument, or debug mode that skips a step. That is the whole design: adding the fiftieth tool cannot accidentally add the first unguarded one.
The five rules it enforces in code
| Rule | Where it lives |
|---|---|
| Denylist beats allowlist; unparseable input fails closed | control_plane/scope.py |
| Arguments are rejected, never sanitized-and-run | control_plane/sanitize.py |
| Aggressive and exploit actions stop for a human | control_plane/approval.py |
| No PoC, no finding — only a reproducible proof confirms | knowledge/findings.py |
| No scope, rate-limit, or attribution evasion capability, ever | sanitize.GLOBAL_DENIED_FLAGS |
The fourth is worth spelling out: Finding.confirm() requires a PoC with both
reproduction steps and an observed result. AI triage can rank, downgrade, and
drop — a taskflow that declares a confirm verdict is rejected at load time.
Install
git clone <your-fork> EasyHunt-AI && cd EasyHunt-AI
./install.sh # package, skills, MCP registration
easyhunt install # the ~20-tool core pipeline
easyhunt install --all # all 64, including cloud and LLM red-team
easyhunt install is idempotent, dependency-ordered, and verifies every tool
after installing it — a successful go install does not mean a working tool.
It reports per-tool failures with the command and stderr and keeps going, so one
broken recipe costs you one tool rather than the run.
easyhunt install --dry-run # show the plan, change nothing
easyhunt install --category dns # one group at a time
easyhunt doctor --fix # repair what is already here
It never installs into EasyHunt's own environment — Python tools go through
pipx, isolated. That is enforced with a guard, after pip install semgrep once
pulled in fastmcp-slim and silently broke the application's MCP client.
Then, before anything else:
cp scope.example.yaml scope.yaml
$EDITOR scope.yaml # fill in from the program's policy page
easyhunt doctor # what is installed, configured, and missing
easyhunt scope example.com # confirm a target resolves the way you expect
In Claude Code: /easyhunt.
What "installed" means here
doctor and the installer both resolve tools by identity, not PATH order.
httpx is both ProjectDiscovery's prober and the Python HTTP library's CLI; if
the wrong one is first on your PATH it exits zero, prints nothing, and looks
exactly like a target with no live hosts. EasyHunt runs each candidate once and
picks the one that identifies itself, so a shadowed install is reported and used
correctly rather than silently producing empty scans.
Architecture
L5 STRATEGY Claude CLI — plans and decides. No network access of its own.
L4 METHOD 8 Claude Skills, one per VAPT phase.
L3 CONTROL MCP server — scope, sanitize, rate-limit, approve, sandbox, audit.
L2 EXECUTION Engines (BBOT · Nuclei · Osmedeus · Strix) + ~30 atomic wrappers.
L1 KNOWLEDGE Rule packs · task graph · findings store · evidence · PoC memory.
LLM traffic ──▶ OpenRouter (3 tiers, fallbacks, price ceilings)
Engines over wrappers. BBOT already orchestrates 80+ recon modules, so EasyHunt drives it through its Python API rather than wrapping each one. Atomic wrappers exist where surgical control matters.
Scope is enforced twice. BBOT's own whitelist/blacklist are populated from
scope.yaml, and every emitted event is re-checked before storage — a module
that resolves outward cannot smuggle a host into the findings store.
Extending it
Drop a YAML file into rules/ and you have a new detection. No code change.
| Directory | Format | Run by |
|---|---|---|
rules/nuclei/ |
Nuclei templates + workflows | Nuclei engine |
rules/easyhunt/ |
native matcher/extractor packs | built-in matcher engine |
rules/jaeles/, rules/semgrep/ |
those tools' native formats | their engines |
rules/bbot/ |
BBOT presets | BBOT |
rules/easyhunt/*.py + manifest |
Python plugin | the EasyHunt runner |
Every rule must declare a verify block saying how a hit gets confirmed, and a
Python plugin claiming mode: passive while its source reaches for an aggressive
primitive is refused at load time — that check exists so the approval gate
cannot be bypassed by mislabeling.
easyhunt rules # what loaded, and what was rejected and why
A rejected rule is a detection you think you have and don't, so rules_list()
surfaces rejections to the agent too.
Cost control
Three tiers, configured in config.yaml, never hardcoded:
- T0 — dedupe, classify, bulk-summarize recon.
- T1 — correlation, candidate ranking, false-positive triage.
- T2 — exploit reasoning and the final report.
With models[] fallbacks (billed only for the one that runs, openrouter/auto
last so a renamed slug degrades instead of failing), a max_price ceiling per
tier, per-phase token budgets, and rate-limit demotion to a cheaper tier rather
than failing a phase.
Raw tool output never reaches a model. It is parsed, deduplicated, and filtered in code first, then map-reduced on the cheap tier. Everything except AI triage and report synthesis works with no API key at all.
Remote access (OAuth 2.1 + PKCE)
stdio — the normal Claude CLI setup — is a pipe to the parent process and needs
no auth. The remote transport is different: a network-reachable EasyHunt runs
scanners and exploitation tools on request, so binding a non-loopback address
without auth is refused outright, not warned about.
auth:
mode: jwt # or oauth_proxy
base_url: https://easyhunt.internal.example.com
jwks_uri: https://idp.example.com/.well-known/jwks.json
issuer: https://idp.example.com/
authorization_servers: [https://idp.example.com]
EasyHunt acts as an OAuth 2.1 Resource Server: it publishes RFC 9728
protected-resource metadata, answers unauthenticated calls with
WWW-Authenticate: Bearer resource_metadata="…", and verifies bearer tokens
against your IdP. PKCE is S256-only — the MCP SDK types the challenge method as
Literal["S256"], so plain cannot be negotiated. Tokens are audience-bound to
base_url (RFC 8707), so one minted for another service that trusts the same IdP
is rejected here.
Scopes map onto EasyHunt's risk tiers, which is where this earns its keep:
| Scope | Unlocks |
|---|---|
easyhunt:read |
status, findings, scope checks — no target contact |
easyhunt:recon |
passive tools |
easyhunt:scan |
aggressive tools (ports, nuclei, brute force) |
easyhunt:exploit |
exploit tools (PoC validation, takeover confirmation) |
easyhunt:approve |
answering approval prompts |
easyhunt:admin |
loading a scope, reloading rules |
A token is a ceiling, checked before the human approval gate rather than
instead of it — a CI token holding only easyhunt:recon cannot invoke
exploitation even if a human would have approved it. Scope filtering applies to
discovery as well as invocation, so an unauthenticated caller cannot even
enumerate the tooling.
easyhunt:approve is deliberately separate from every operational scope. If the
agent's token could satisfy it, the agent could answer its own approval prompts
and human-in-the-loop would be decorative. Issue it to an operator's token and
nothing else.
For an IdP without Dynamic Client Registration (GitHub, Google), use
mode: oauth_proxy; FastMCP fronts it and forwards PKCE and the resource
indicator upstream. Credentials come from the environment, never config.yaml.
Safety properties worth knowing
- Audit log is hash-chained. Every attempt — including refusals — is one JSONL line carrying the previous line's digest. Deletions and edits are detectable. Credential-shaped values are redacted on the way in.
- Tool output is treated as untrusted input. Prompt-injection markers in
target-controlled text are stripped before it reaches a context window, with a
visible
[stripped: ...]marker left behind. - Tool definitions are cryptographically pinned. A passive tool that becomes an exploit tool between runs is reported as a privilege escalation.
- Budget ceilings abort cleanly. Report generation is deliberately exempt from the spend gate, so a stopped run still produces a report — labelled PARTIAL on its first page.
- Canary defense in triage. Fabricated findings on
.invalidhosts are mixed into every batch; a pass that "confirms" one has its verdicts weighted down, and the measurement is reported.
Reasoning across an engagement
Attack paths. cloud_attack_paths turns posture findings into reachability:
which internet-facing entry point reaches which valuable resource, in how many
hops. Paths are ranked by what the destination is worth against how exposed the
entry is — a two-hop path to customer data outranks a one-hop path to an empty
dev bucket. With Cartography + Neo4j the edges are observed; without it they
are inferred from control failures and labelled as such.
Graph memory. Every asset, finding, and relationship is indexed as the run
proceeds, so graph_recall("api.example.com") answers "what do I already know
about this host" without a re-scan. Native by default — Neo4j is optional and
only adds cross-engagement persistence. Stores the index, never the loot.
Rendered graphs. Reports get taskgraph.svg and attack-paths.svg (plus
.dot, and .png when a converter is installed). SVG is generated by a
dependency-free layered-DAG layout, because a report artifact that only exists
when Graphviz happens to be installed is one you cannot rely on.
Prompt caching. Stable instructions carry an explicit cache_control
breakpoint with volatile data after them, so a triage phase pays for its rubric
once instead of twenty times. Cached tokens and the resulting savings are
recorded per call in the audit log.
Layout
easyhunt/
├── mcp_server.py MCP entrypoint — the only door
├── control_plane/ scope · sanitize · ratelimit · approval
│ sandbox · audit · budget · jobs · pins
├── engines/ bbot · nuclei · osmedeus · strix
├── tools/ base.py (the decorator) + wrappers by phase
├── plugins/ manifest schema · loader · matcher engine
├── knowledge/ findings · taskgraph · memory
├── llm/ openrouter · summarize · triage
└── report/ synthesize.py
rules/ · taskflows/ · skills/ · tests/
docs/ · scripts/ · CLAUDE.md · bootstrap.sh
Documentation
CLAUDE.md is loaded automatically by Claude CLI and carries the hard
invariants — read it first. Full index in docs/README.md.
| Document | Answers |
|---|---|
CLAUDE.md |
The invariants, working rhythm, and known traps. |
docs/ARCHITECTURE.md |
Mind map, control-plane sequence, engagement flow, module map. |
docs/BUILD_STATUS.md |
What works, what does not, what is left. |
docs/BOOTSTRAP.md |
New-machine setup and troubleshooting. |
docs/PAYLOADS.md |
Vetted payload store: tiers, quarantine, tool mapping. |
New machine? ./bootstrap.sh — idempotent, safe to re-run.
Development
.venv/bin/python -m pytest tests/ -q # 601 tests
.venv/bin/python -m pytest tests/test_security.py -q # adversarial suite
.venv/bin/ruff check easyhunt/ tests/
tests/test_security.py tries to defeat each control — scope bypass via
homoglyphs, IP encodings and list smuggling; sanitizer fuzzing; approval bypass;
prompt injection; tool-definition tampering. tests/test_e2e.py runs the full
pipeline against a deliberately misconfigured server it starts on loopback.
License
MIT. Note that several wrapped tools carry stronger licenses — TruffleHog and
masscan are AGPL-3.0, nmap is NPSL. Each tool's license is recorded in its
ToolSpec and printed in the report's tool inventory, which matters the moment
anyone redistributes a bundle.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。