urcuqui/HexAgent
GitHub: urcuqui/HexAgent
HexAgent 是一个基于 LangGraph 的教育性质 AI agent 框架,通过迭代式规划-执行-评估工作流演示 Web 应用渗透测试侦察的自动化编排。
Stars: 1 | Forks: 1
# HexAgent
**HexAgent** 是一个教育性质的概念验证,展示了 AI agent 如何使用 [LangGraph](https://github.com/langchain-ai/langgraph) 和 [LangChain](https://github.com/langchain-ai/langchain) 来*编排* Web 应用侦察。它会进行规划、选择并运行工具,评估结果,在需要时重新规划,并最终生成一份 markdown 报告。
## 功能
- 🧠 **Agentic LangGraph 工作流** — 目标 → 规划 → 执行 → 评估 → (重新规划)→ 报告,带有迭代循环。
- 🎯 **响应式规划/重新规划,而非固定配方** — 计划从最小化(扫描开放端口)开始,并根据每次结果揭示的信息进行*扩展*:开放的 Web 端口会将 HTTP 层分析加入队列,`robots.txt` 的 disallow 会将定向 GET 请求加入队列,发现的登录 endpoint 会将受控的 POST 请求加入队列。
- 🧩 **领域专家 agent** — `ReconAgent` 和 `HttpAnalysisAgent` 各自拥有工具注册表的一个作用域切片,与 Planner/Evaluator/Reporter pipeline agent 并行。
- 🧷 **Human-in-the-loop,在*之前*和*之后*** — 敏感工具(`http_post`、`nmap_scan`)在运行*前*会暂停等待批准(如无响应则 fail-closed),此外在生成报告前还有一个可选的运行结束检查点。
- 🧩 **模块化、SOLID 架构** — 模型、工具、规划器、agent、prompt、graph 和实用程序的清晰分离。
- 🛠️ **可插拔的工具注册表** — 九个工具(八个确定性 mock 加上一个可选的真实 `nmap_scan`),返回结构化的 Pydantic 模型;无需触及编排层即可添加真实工具。
- 🔁 **迭代控制流** — 在目标达成、迭代预算耗尽或需要人工批准时停止。
- 🤖 **LLM 可选** — 完全离线运行,使用确定性启发式 agent;接入任何 OpenAI 兼容的 endpoint 即可启用 LLM 驱动的规划/评估。
- 📄 **Markdown 报告** — 包含目标、计划、执行步骤、工具输出、发现、建议的后续行动以及人工验证点。
- ✅ **经过测试和 Lint 检查** — pytest 测试套件和 ruff 配置。
## 架构
HexAgent 采用分层设计,使得每个关注点都可以独立测试和替换:
| 层级 | 包 | 职责 |
|-------|---------|----------------|
| 模型 | `app/models` | Pydantic 契约:`Plan`, `PlanStep`, `ReplanReason`, `ToolCall`, `ToolResult`, `Finding`, `Report`。 |
| 工具 | `app/tools` | `BaseTool` 抽象(带有 `sensitive` 标志)、`ToolRegistry` 以及各个工具。 |
| 规划器 | `app/planners` | `BasePlanner` 接口背后的 `HeuristicPlanner`(离线、响应式)和 `LLMPlanner`。 |
| Agent | `app/agents` | Planner / Executor / Evaluator / Reporter pipeline agent,以及 `ReconAgent` / `HttpAnalysisAgent` 领域专家(`app/agents/specialists.py`)。 |
| Prompt | `app/prompts` | Prompt **文本文件** + 加载器(无硬编码 prompt)。 |
| Graph | `app/graph` | `AgentState`、节点、路由器和已编译的 LangGraph 工作流。 |
| 实用程序 | `app/utils` | 日志记录、LLM 工厂、JSON 解析、报告持久化。 |
### Graph 工作流
```
flowchart TD
START([Start]) --> intake[Intake objective]
intake --> plan[Planner: build plan]
plan --> execute[Executor: select & run tool]
execute --> evaluate[Evaluator: observations & findings]
evaluate -->|more steps| execute
evaluate -->|needs replan| replan[Replan]
replan --> execute
evaluate -->|human approval required| human[Human checkpoint]
evaluate -->|done / max iterations| report[Reporter: render markdown]
human --> report
report --> END([End])
```
路由谓词(`app/graph/router.py`)在每次评估后按以下优先级顺序决定下一跳:
1. **迭代预算耗尽** → 生成报告。
2. Evaluator **请求重新规划** → 重新规划,然后继续执行。
3. **仍有可执行步骤** → 执行下一步。
4. **配置了人工批准** → 人工检查点(一个终端暂停,但仍会输出报告供审查)。
5. 否则 → 生成报告。
### 状态
`AgentState`(一个 Pydantic 模型)贯穿每个节点,并追踪 `objective`、`target`、当前 `plan`、`completed_step_ids`、累积的 `observations`、`findings`、`tool_results`、`executed_steps`、`reasoning_history`,以及控制流字段(`iterations`、`needs_replan`、`replans`、`awaiting_human`、`stopped_reason`)和输出(`report`、`report_markdown`)。
## 项目结构
```
HexAgent/
├── app/
│ ├── agents/ # planner / executor / evaluator / reporter agents
│ ├── graph/ # state, nodes, router, compiled workflow
│ ├── models/ # Pydantic models (plan, tool IO, findings, report)
│ ├── planners/ # heuristic + LLM planners behind a base interface
│ ├── prompts/ # prompt loader + templates/*.txt
│ ├── tools/ # BaseTool, registry, mock tools, deterministic fixtures
│ ├── utils/ # logging, LLM factory, parsing, report IO
│ ├── templates/ # Jinja2 templates for the web UI
│ ├── static/ # CSS/JS for the web UI
│ ├── cli.py # command-line interface
│ ├── web.py # Flask web UI (optional; `uv sync --extra web`)
│ └── config.py # pydantic-settings configuration
├── examples/ # runnable programmatic example
├── scripts/ # dev helper scripts (e.g. hexagent.sh)
├── tests/ # pytest suite
├── docs/ # architecture notes
├── main.py # thin launcher -> app.cli:main
├── pyproject.toml
└── .env.example
```
## 安装说明
要求 **Python 3.12+** 和 [`uv`](https://github.com/astral-sh/uv)。
```
uv sync --extra dev # create the venv and install all dependencies
cp .env.example .env # then paste your Vercel AI Gateway key (works without one)
```
在 `.env` 中设置 `AI_GATEWAY_API_KEY`,以通过 Vercel AI Gateway 启用 LLM 驱动的 agent;留空则完全在离线的确定性 mock 模式下运行。
## 配置
所有设置均为环境变量(见 `.env.example`)。HexAgent 已为 [Vercel AI Gateway](https://vercel.com/docs/ai-gateway)(一个 OpenAI 兼容的 endpoint)预配置。在**没有 API key** 的情况下,它将在确定性的 **mock 模式**下运行。
| 变量 | 默认值 | 描述 |
|----------|---------|-------------|
| `AI_GATEWAY_API_KEY` | _空_ | Vercel AI Gateway key;设置后启用 LLM agent。也接受 `OPENAI_API_KEY`。 |
| `OPENAI_BASE_URL` | `https://ai-gateway.vercel.sh/v1` | OpenAI 兼容的 base URL(可覆盖为其他主机)。 |
| `HEXAGENT_MODEL` | `openai/gpt-4o-mini` | `provider/model` 格式的模型 ID(例如 `anthropic/claude-sonnet-4.6`)。 |
| `HEXAGENT_MOCK_MODE` | `false` | 强制离线确定性运行。 |
| `HEXAGENT_ENABLE_NMAP` | `false` | 注册真实的 `nmap_scan` 工具(调用本地 `nmap` 二进制程序),并让启发式规划器在初始扫描步骤中优先使用它而不是 mock 的 `port_scan`。仅对明确授权的目标启用。 |
| `HEXAGENT_MAX_ITERATIONS` | `12` | 停止前的迭代预算。 |
| `HEXAGENT_REQUIRE_HUMAN_APPROVAL` | `false` | 在报告之前暂停以供人工审查(运行结束的关卡)。 |
| `HEXAGENT_REQUIRE_SENSITIVE_APPROVAL` | `false` | 在运行任何标记为 `sensitive`(`http_post`, `nmap_scan`)的工具*之前*暂停。如果未连接批准回调,则 Fail closed(拒绝)。 |
| `HEXAGENT_LOG_LEVEL` | `INFO` | 日志记录级别。 |
| `HEXAGENT_REPORT_DIR` | `reports` | 报告的输出目录。 |
## 用法
### CLI
```
# 离线、确定性运行;将报告打印到 stdout
uv run hexagent --objective "Recon the lab box" --target demo.thm.local --mock --print
# 限制迭代次数并要求人工批准检查点
uv run hexagent -o "Map the attack surface" -t example.thm --max-iterations 6 --human-approval
```
#### 标志
| 标志 | 默认值 | 作用 |
|---|---|---|
| `--objective` / `-o` | _必填_ | 传递给规划器的自然语言目标(例如 `"Map the attack surface"`)。 |
| `--target` / `-t` | _必填_ | 要评估的主机或 URL。只能是经过授权的实验室/测试目标——请参阅免责声明。 |
| `--mock` | off | 无论 `.env`/API key 如何,强制为本次运行设置 `mock_mode=True`:无 LLM 调用,仅使用启发式规划器/评估器。**不会禁用 `nmap_scan`** ——如果设置了 `HEXAGENT_ENABLE_NMAP=true`,`--mock` 正是让你在完全确定性的规划/评估下进行真实扫描的方法(见 `docs/nmap-testing.md`)。为了确保绝对不运行任何真实操作,还需设置 `HEXAGENT_ENABLE_NMAP=false`。 |
| `--max-iterations N` | `12` (或 `HEXAGENT_MAX_ITERATIONS`) | 在运行强制停止并显示 `stopped_reason = "maximum iterations reached"` 之前,限制 `execute` 的步骤数。在进行快速冒烟测试或限制 LLM 驱动的运行成本/时间时可调低此值。 |
| `--human-approval` | off | 运行结束关卡:一旦计划中没有剩余可运行的内容,在结束前于 `human` 检查点暂停(`stopped_reason = "awaiting human approval"`)。无论哪种情况都会生成报告——这仅影响运行是否在最后“等待”。 |
| `--require-sensitive-approval` | off | 操作前关卡:在运行任何标记为 `sensitive`(`http_post`, `nmap_scan`)的工具之前,打印待处理的调用并提示 `Approve this action? [y/N]`。回答除 `y`/`yes` 以外的任何内容——或者根本不输入任何内容——将跳过该操作,而不是永远阻塞。 |
| `--no-save` | off(报告*会*被保存) | 跳过将 markdown 报告写入 `HEXAGENT_REPORT_DIR`(默认为 `reports/`)。用于一次性/重复的测试运行,以免 `reports/` 被填满。 |
| `--print` | off | 将渲染的 markdown 报告打印到 stdout。独立于 `--no-save` ——可以组合使用以查看报告而不将其持久化到任何地方。 |
#### 常见组合
```
# 快速、完全 mock、无副作用 — 适合在开发/演示时进行迭代。
# 这里显式设置 HEXAGENT_ENABLE_NMAP=false,因此它永远不会依赖于你的 .env。
HEXAGENT_ENABLE_NMAP=false uv run hexagent -o "Recon" -t demo.thm.local --mock --no-save --print
# 确定性地运行真实的 nmap 工具(参见 docs/nmap-testing.md)—
# 这里的 --mock 意味着“没有 LLM”,而不是“没有真实的 nmap”。
HEXAGENT_ENABLE_NMAP=true uv run hexagent -o "Map the attack surface" -t 127.0.0.1 --mock --print --no-save
# 完整的“教学”运行:mock 侦察 + 两个人工检查点
HEXAGENT_ENABLE_NMAP=false uv run hexagent -o "Recon" -t demo.thm.local --mock --human-approval --require-sensitive-approval --print
```
### 编程方式
```
from app.graph.workflow import run_workflow
state = run_workflow("Passive reconnaissance", "demo.thm.local")
print(state.report_markdown)
```
### 示例脚本
```
uv run python examples/basic_recon.py
```
### Web UI
一个小型的 Flask 前端允许你从表单启动运行,并在浏览器中实时观看整个 agent pipeline(规划 → 执行 → 评估 → 重新规划 → 报告)的流式输出——包括当 `--require-sensitive-approval` 在 `http_post`/`nmap_scan` 之前暂停时,出现的一个真实的、实时的批准/拒绝提示。
```
uv sync --extra web # installs Flask + markdown/nh3 (kept optional; the CLI stays dependency-light)
./scripts/hexagent-web.sh # or: uv run python -m app.web
# 然后打开 http://127.0.0.1:5000
```
### Docker 实验环境
对于具有 Docker 和 Docker Compose 的 VM 或主机:
```
./run_lab.sh
# 然后打开 http://localhost:8000
```
设置 `HEXAGENT_WEB_PORT=8080 ./run_lab.sh` 以暴露不同的主机端口。
启动表单公开了与 CLI 标志相同的控制项(`--mock`、`--enable-nmap`、`--max-iterations`、`--human-approval`、`--require-sensitive-approval`);实时控制台为每个 graph 节点显示一行日志,显示发现的结果,以及渲染为格式化 HTML(标题、表格、代码块)的最终报告,并带有“查看原始 markdown”的切换开关。批准暂停是真实的:后台运行线程会真正阻塞,直到你点击批准或拒绝,这与 CLI 的 `input()` 提示的工作方式完全相同。
**报告渲染是经过净化的,而不仅仅是转义的。** `objective`/`target` 和工具参数由用户提供并原封不动地流入报告,因此服务器使用 `python-markdown` 渲染 markdown,然后在发送到浏览器之前,使用 `nh3`(一个基于 Rust 的 HTML 净化器)将生成的 HTML 剥离为显式的标签白名单——你目标中的 `