Skip to main content
Glama
README.md
# mcp-vulbox-agent

> **面向合法授权众测、自有资产、靶场或明确书面授权测试的「人工主导、AI 辅助」安全测试工作台。**
> 不是自动化挖洞机器人,不是无人值守扫描器,不是自动提交系统。

Agent 通过 MCP Server 调用一组**合规、非破坏性、可审计**的工具,完成项目接入 → 范围读取 →
资产发现 → 非破坏性安全验证 → 证据收集 → 报告草稿生成 → 人工提交跟踪。

项目定位声明是硬编码常量 `PROJECT_POSITIONING`(见 `src/mcp_vulbox_agent/__init__.py`),
CLI `version` 子命令与 MCP Server 启动日志都会原样打印它——它不是文档里的形容词,是代码里的字符串。

---

## ⚠️ 这不是什么

请先读这一段,再决定要不要继续。

| 它不是 | 依据 |
| --- | --- |
| **不是自动化挖洞机器人** | 没有扫描调度器,没有资产遍历循环。每一个出网动作都由 Agent 单次调用触发,且必须携带一次性人工确认令牌。 |
| **不是无人值守扫描器** | 主动工具默认全关,需要在 `config/config.yaml` 与环境变量**同时**列入白名单才会被启用;`MCP_VULBOX_ACTIVE_TOOLS` 留空 = 全部关闭。没有定时任务、没有批量目标参数。 |
| **没有自动提交能力** | 适配层基类的 `submit_report` 永远抛错(`src/mcp_vulbox_agent/vulbox/base.py`),`report.auto_submit` 被硬约束禁止开启。`report_mark_submitted` 是**回填**工具:要求草稿已通过人工复核,且它不发起任何平台请求。 |
| **不是绕过平台风控的工具** | `vulbox.bypass_platform_controls` 禁止开启;live 适配器只发 GET、不重试 4xx、不模拟浏览器登录态。 |
| **不提供自定义注入载荷** | 高风险工具只接受 `config/safe_checks.yaml` 中登记的 `check_id`,自定义载荷一律 `PAYLOAD_NOT_ALLOWED`。 |
| **资源层不是第二条能力通道** | MCP 资源只暴露 Agent 本来就能通过被动工具拿到的数据(`vulbox://scope` 就是经 `auth_list` 取的),技能文档是仓库里的静态文件。任何需要出网、需要人工确认或含密钥的内容都不做成资源——那等于给管线的约束开旁路。 |

如果你要找的是「输入域名 → 自动出报告」,这个项目帮不上忙,而且它存在的意义恰恰是拒绝这种用法。

---

## 为什么需要它

AI 在安全测试里的价值在于**体力活**:读响应、抄字段、整理时间线、把碎片拼成一份结构完整的草稿。
AI 的危险也在于此:它不承担法律责任,却可能替你把请求打到不该打的地方,或者把一份没验证过的
结论写进提交给平台的报告。

这个项目的边界设计就是把这两件事分开:

- **AI 负责**:调用工具、读取结果、结构化整理、生成草稿、跟踪状态。
- **人负责**:登记授权、批准每一次出网、复核每一条结论、在平台网页上点击提交。
- **代码负责**:让上面这条分工无法被配置绕过。范围校验器没有 `force` 参数,确认令牌是一次性的,
  审计记录不允许静默删除。

换句话说:**合规不写在文档里,写在执行管线里。**

---

## 核心安全设计

16 条约束,以及它们在代码中的**唯一落地点**:

| # | 约束 | 落地位置 |
| --- | --- | --- |
| 1 | **默认拒绝、无强制通过**:没有匹配到授权就是 DENY;不存在 `force` / `skip` / `override` 参数 | `src/mcp_vulbox_agent/security/scope.py`;`config/config.yaml` 的 `authorization.default_decision: deny`、`allow_force_pass: false`(后者在 `config.py` 中被硬约束禁止开启) |
| 2 | **全局拒绝优先**:云元数据网段、组播、保留地址在代码中写死,与 `scope.yaml` 取并集,任何配置都覆盖不掉 | `security/scope.py` 的 `BUILTIN_GLOBAL_DENY` + `config/scope.yaml` 的 `global_deny` |
| 3 | **授权过期即失效**:`valid_to` 之后自动 DENY,无需人工干预 | `models.py` 的 `Authorization.is_valid_at()` / `.expired` |
| 4 | **先规范化再比对**:`http://EXAMPLE.com:80/../x` 这类写法不可能绕过字符串比对 | `security/target.py`,网络层只接受 `NormalizedTarget` |
| 5 | **出网模式硬闸**:只有 `live` 模式允许出网 | `tools/context.py` 的 `NETWORK_MODES = frozenset({RunMode.LIVE})` |
| 6 | **主动工具默认全关、双白名单**:配置与 `MCP_VULBOX_*_TOOLS` 环境变量必须**同时**列名 | `tools/registry.py` 的 `_is_enabled()` |
| 7 | **人工确认令牌绑定「工具 + 规范化目标 + 参数哈希」且只能消费一次** | `security/confirmation.py`(HMAC-SHA256,密钥只从环境变量读) |
| 8 | **确认密钥缺失即 fail-closed**:没配 `MCP_VULBOX_CONFIRMATION_SECRET` 时所有主动工具无法启用 | `config.py` 的 `Secrets.confirmation_secret` |
| 9 | **三道限流闸门同时生效**:令牌桶(速率)+ 并发闸门 + 请求预算,触发即拒绝、不排队不重试。限流由出网 handler 在构造网络客户端时注入,因此只有真正要发请求的工具才占用配额 | `security/ratelimit.py`;`tools/active.py` / `tools/high_risk.py` 的 `ctx.new_limiter()` |
| 10 | **硬上限只能更保守**:配置超过 `HARD_LIMITS` 直接报错,不静默夹取 | `config.py` 的 `HARD_LIMITS` 与 `_within_hard_limits()` |
| 11 | **唯一网络出口 + 不跟随重定向 + 强制 robots.txt** | `net.py`(`follow_redirects=False` 硬编码;`respect_robots_txt` 为硬约束禁止关闭) |
| 12 | **载荷白名单**:只接受内置安全库中的 `check_id`,且 24 类禁止检查类型写死在配置里 | `tools/high_risk.py` 的 `load_safe_checks()`;`config/safe_checks.yaml` 的 `forbidden_check_types` |
| 13 | **证据落盘前强制脱敏 + 三重限额** | `security/redaction.py`;`storage/evidence_store.py`(类型白名单 → 单文件/单发现数量/单发现总量 → 脱敏 → 落盘) |
| 14 | **提示词注入检测与隔离**:工具输出一律视为不可信数据,命中即整段丢弃并隔离到人类专属通道 | `security/injection.py` 的 `guard_result()`;`security/quarantine.py` |
| 15 | **审计 append-only、不可静默删除**:只有 `AuditLog.purge()` 一条删除路径,且要求显式确认 + 操作者 + 先留痕 | `security/audit.py`;`config.audit.allow_silent_delete: false`(禁止开启) |
| 16 | **不存在自动提交能力**:这是设计约束而非配置项 | `vulbox/base.py` 的 `submit_report` 永远抛错;`config.report.auto_submit: false`(禁止开启) |

违规调用(范围拒绝、确认缺失、注入命中、模式越界)会自动打 `alert=True`,
错误码集合见 `errors.py` 的 `ALERTING_ERROR_CODES`,可用 `mcp-vulbox-agent audit query --alerts` 过滤。

---

## 架构

```mermaid
flowchart TD
    A["Agent(MCP 客户端)"] -->|"stdio / MCP 协议"| S["MCP Server<br/>src/mcp_vulbox_agent/server.py"]

    S --> R["ToolRegistry.execute()<br/>tools/registry.py<br/>(唯一调用入口,无旁路)"]
    S --> CAT["catalog.py<br/>resources + prompts<br/>(只读文本,无执行路径)"]

    R --> P["执行管线"]
    P --> P1["1 启用校验<br/>_is_enabled"]
    P1 --> P2["2 模式校验<br/>NETWORK_MODES"]
    P2 --> P3["3 范围校验<br/>ScopeGuard.check"]
    P3 --> P4["4 人工确认<br/>HMAC 一次性令牌"]
    P4 --> P5["5 执行 handler<br/>出网工具在此内部限流"]
    P5 --> P6["6 输出清洗<br/>guard_result"]
    P6 --> P7["7 审计落盘<br/>append-only JSONL"]

    P5 --> T1["被动层 16 个工具<br/>tools/passive.py<br/>不出网 / 不需确认"]
    P5 --> T2["主动层 3 个工具<br/>tools/active.py<br/>出网 / 需确认 / 默认关闭"]
    P5 --> T3["高风险层 3 个工具<br/>tools/high_risk.py<br/>出网 / 需确认 / 默认关闭"]

    CAT -.->|"vulbox://scope 经 auth_list 取数<br/>(同一路径,同样进审计)"| T1
    CAT -.->|"vulbox://skills/{name} 读 skills/"| SK["skills/*/SKILL.md"]

    T1 --> ST["存储层"]
    T2 --> ST
    T3 --> ST
    ST --> D1["SQLite 状态库<br/>data/state.db"]
    ST --> D2["证据库<br/>data/evidence/"]
    ST --> D3["审计日志<br/>data/audit/*.jsonl"]
    ST --> D4["注入隔离区<br/>data/quarantine/"]

    T1 -.-> V["Vulbox 适配层<br/>vulbox/mock | manual | live<br/>只读 + 人工导出"]
    V -.->|"旁挂:仅被动工具调用"| S

    NET["net.py<br/>唯一 HTTP 出口<br/>robots.txt / 不跟随重定向 / 响应体硬上限"] --- T2
    NET --- T3
```

管线顺序在 `tools/registry.py` 的 `_run_pipeline()` 中按注释编号固定,
`tools/__init__.py` 的 docstring 也重复声明了「没有旁路」——任何绕过安全内核直连网络的代码路径都属于缺陷。

---

## 目录结构

```
mcp-vulbox-agent/
├── .dockerignore                    # 构建上下文排除(.env 绝不进镜像)
├── .env.example                     # 环境变量模板(只允许占位符)
├── .gitignore
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   ├── workflows/
│   │   ├── ci.yml                   # ruff + mypy + pytest + stdio 验收(3.11/3.12/3.13)
│   │   ├── security.yml             # bandit + pip-audit(每周)
│   │   └── release.yml              # tag v* → 构建 + GitHub Release
│   └── PULL_REQUEST_TEMPLATE.md
├── Dockerfile                       # 两阶段构建,非 root,默认 offline
├── docker-compose.yml               # stdio 服务 + 可选 sse profile(只绑 127.0.0.1)
├── pyproject.toml                   # 依赖 / ruff / mypy / pytest 配置
├── README.md
├── SECURITY.md
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── config/
│   ├── config.yaml                  # 主配置:限额、证据、审计、报告、授权、工具白名单
│   ├── safe_checks.yaml             # 载荷白名单(payloads)+ 被动检查目录 + 禁止检查类型
│   └── scope.yaml                   # 授权范围定义(唯一真相源)
├── docs/
│   ├── COMPLIANCE.md                # 合规声明
│   ├── AI-USAGE.md                  # AI 使用声明
│   ├── DEPLOYMENT.md                # 部署与运维
│   └── DISCLAIMER.md                # 免责声明
├── skills/                          # Agent 技能层(操作规程,非代码)
│   ├── README.md                    # 技能索引 + 四条铁律
│   ├── authorization_check/SKILL.md # 开工前授权检查
│   ├── scope_parse/SKILL.md         # 范围解析成允许/禁止清单
│   ├── asset_discovery/SKILL.md     # 被动优先建资产清单
│   ├── fingerprint/SKILL.md         # 指纹识别
│   ├── safe_vuln_verify/SKILL.md    # 非破坏性验证
│   ├── evidence/SKILL.md            # 脱敏证据管理
│   ├── report/SKILL.md              # 报告草稿与人工提交登记
│   └── human_review/SKILL.md        # 只能由人类完成的动作
├── src/mcp_vulbox_agent/
│   ├── __init__.py                  # PROJECT_POSITIONING / __version__
│   ├── __main__.py                  # python -m mcp_vulbox_agent → MCP Server (stdio)
│   ├── catalog.py                   # MCP resources + prompts(服务面的另一半)
│   ├── cli.py                       # CLI 全部子命令(人类操作面唯一入口)
│   ├── config.py                    # 配置加载 + HARD_LIMITS 硬约束校验
│   ├── errors.py                    # 错误码 + 合规替代方案
│   ├── models.py                    # 数据模型(目标、授权、证据、草稿、审计记录)
│   ├── net.py                       # 唯一 HTTP 出口(robots / 限流 / 预算都在这里)
│   ├── server.py                    # MCP Server 入口与工具注册
│   ├── security/                    # 安全内核:所有约束的唯一落地点
│   │   ├── __init__.py
│   │   ├── audit.py                 #   不可静默删除的审计
│   │   ├── confirmation.py          #   一次性 HMAC 确认令牌
│   │   ├── injection.py             #   提示词注入检测 / 输出清洗
│   │   ├── quarantine.py            #   注入隔离区(人类专属读)
│   │   ├── ratelimit.py             #   令牌桶 + 并发闸门 + 请求预算
│   │   ├── redaction.py             #   证据与日志脱敏
│   │   ├── scope.py                 #   范围校验(无强制通过)
│   │   └── target.py                #   目标规范化
│   ├── storage/
│   │   ├── __init__.py
│   │   ├── db.py                    # SQLite 连接与建表
│   │   ├── evidence_store.py        # 证据落盘(脱敏 + 限额 + 保留期)
│   │   └── repository.py            # 实体读写
│   ├── tools/
│   │   ├── __init__.py
│   │   ├── active.py                # 3 个主动工具
│   │   ├── context.py               # ToolContext 装配
│   │   ├── high_risk.py             # 3 个高风险工具
│   │   ├── passive.py               # 16 个被动工具
│   │   └── registry.py              # 注册表 + 唯一执行管线
│   └── vulbox/
│       ├── __init__.py              # build_adapter 工厂
│       ├── base.py                  # 适配器接口(submit_report 永远抛错)
│       ├── live.py                  # 只读公开 API
│       ├── manual.py                # 人工导入/导出,零网络
│       └── mock.py                  # 本地 mock,默认
├── tests/
│   ├── conftest.py                  # 共享 fixture(数据根一律 tmp_path)
│   ├── test_catalog.py              # MCP 资源与提示词(含路径穿越拒绝)
│   ├── test_config_discovery.py     # 配置发现与 PROJECT_ROOT 推导
│   ├── test_mcp_protocol.py         # MCP 协议层 schema 与工具层模型一致
│   ├── test_net_query.py            # 查询串传递与网络出口约束
│   ├── test_redaction.py            # 脱敏规则
│   ├── test_registry_pipeline.py    # 执行管线与各拒绝路径
│   ├── test_report_workflow.py      # 发现→证据→草稿→复核→登记 全链路
│   ├── test_scope_guard.py          # 范围校验
│   └── test_vulbox_adapters.py      # 三种适配器与能力声明
└── stdio_acceptance.py              # 真实 MCP 客户端 stdio 端到端验收脚本
```

---

## 安装

要求 **Python 3.11+**(`pyproject.toml` 的 `requires-python = ">=3.11"`)。

```bash
git clone https://github.com/luo173176/mcp-vulbox-agent.git
cd mcp-vulbox-agent

python -m venv .venv
# Windows: .venv\Scripts\activate
source .venv/bin/activate

pip install -e ".[dev]"
```

### 配置 `.env`

```bash
cp .env.example .env
```

然后**必须**生成确认密钥,否则所有主动工具无法启用(fail-closed,这是设计行为不是 bug):

```bash
python -c "import secrets;print(secrets.token_urlsafe(48))"
```

把输出填进 `.env` 的 `MCP_VULBOX_CONFIRMATION_SECRET=`。

`.env` 已被 `.gitignore` 忽略。`.env.example` 中只允许出现占位符——
任何真实凭证被提交都按安全事故处理。

### 自检

```bash
mcp-vulbox-agent doctor
```

会逐项检查 Python 版本、依赖、运行模式、适配器、数据目录、确认密钥、授权条目数、
审计开关、主动工具启用数、外部工具路径与硬上限。有 `fail` 项时退出码为 1。

> 当前仓库的 `.venv` 里尚未执行过 `pip install -e`,直接 `python -m mcp_vulbox_agent` 会报
> `No module named mcp_vulbox_agent`(src 布局)。先安装,或临时用 `PYTHONPATH=src`。

---

## 快速开始

### 1. 用 MCP Inspector 连上(stdio 形式)

```bash
npx @modelcontextprotocol/inspector \
  mcp-vulbox-agent serve
```

或者用模块入口(等价):

```bash
npx @modelcontextprotocol/inspector \
  python -m mcp_vulbox_agent
```

两个入口都启动同一个 stdio 传输的 MCP Server。启动成功后 stderr 会打印一行:

```
[mcp-vulbox-agent] 启动:模式=offline 适配器=mock 工具=16被动+6主动 确认密钥=未配置
```

看到 `确认密钥=未配置` 时,主动工具调用会被拒绝。

### 2. 用 CLI 看工具清单

CLI 是人类操作面的**唯一**入口。先看清单:

```bash
mcp-vulbox-agent tools list
mcp-vulbox-agent tools describe http_probe
```

`tools list` 会列出每个工具的名字、类别、启用状态、是否出网、是否需要人工确认、是否需要目标参数。
`tools describe <name>` 会额外打印参数的 JSON Schema。

只读校验一个目标:

```bash
mcp-vulbox-agent scope check https://demo.example.invalid/
mcp-vulbox-agent scope list
```

---

## 12 步典型工作流

每一步都标了是否需要人类介入。**标了 👤 的步骤 Agent 做不了**,这不是权限问题,是没有对应的工具。

| 步 | 动作 | 执行者 |
| --- | --- | --- |
| 1 | 在 `config/scope.yaml` 登记授权:`authorization_id` / `program_id` / `source` / **`evidence_ref`(书面授权引用)** / `valid_from` / `valid_to` / `scope` / `deny` | 👤 人工 |
| 2 | 生成 `MCP_VULBOX_CONFIRMATION_SECRET` 并写入 `.env`;`mcp-vulbox-agent doctor` 确认无 fail 项 | 👤 人工 |
| 3 | 调用 `scope_check` 校验目标,拿到 `allow` / `deny`、命中的授权 ID、生效速率上限。deny 时不要换工具重试 | Agent |
| 4 | 调用 `auth_list` 读取已登记授权;调用 `vulbox_programs` 读取平台侧项目(mock/manual 适配器零网络) | Agent |
| 5 | 调用 `asset_list` / `asset_add` 建立本地资产清单;`vulbox_assets` 逐条标注是否在授权范围内 | Agent |
| 6 | 调用被动工具做离线整理:`finding_create` 建草案、`evidence_save` 存证据(落盘前强制脱敏)、`finding_list` / `evidence_list` 复核 | Agent |
| 7 | 若要出网:在 `config/config.yaml` 的 `tools.enabled_active` **与环境变量** `MCP_VULBOX_ACTIVE_TOOLS` 中**同时**列名(高风险工具同理走 `enabled_high_risk` / `MCP_VULBOX_HIGH_RISK_TOOLS`),并把 `MCP_VULBOX_MODE` 切到 `live` | 👤 人工 |
| 8 | 调用主动工具(如 `http_probe`)。首次不带令牌 → 返回 `CONFIRMATION_REQUIRED` 与一个 `confirmation_id`,附带完整的绑定摘要(工具、目标、参数哈希、范围校验结果、限流、影响面) | Agent |
| 9 | 用 `mcp-vulbox-agent confirm show <id>` 看清影响面,再 `confirm approve <id> --actor <你的名字>` 批准,拿到一次性令牌 | 👤 人工 |
| 10 | 带着令牌重试第 8 步的调用 → 真正出网执行(受限流与请求预算约束),结果写入审计 | Agent |
| 11 | 调用 `report_generate_draft` 生成草稿:带固定水印、6 项人工复核清单、4 条提交指引 | Agent |
| 12 | `mcp-vulbox-agent report show <draft_id>` 逐项复核 → 调 `report_set_review`(status=approved,需填 actor)→ 👤 在平台网页提交 → 调 `report_mark_submitted` 回填平台报告编号 → 调 `report_platform_status` 跟踪状态 | 👤 + Agent |

第 12 步的最后一段值得再强调一次:**提交动作永远由人类在平台网页上完成**,
`report_mark_submitted` 只是把「已经提交了、编号是 X」这件事记进本地状态库。

---

## 工具清单(22 个)

**被动 16 个**:不出网(`requires_network=false`)、不需要人工确认。带 `target` 参数的工具
(`scope_check` 与 `finding_create`)仍会走管线第 3 步范围校验——**范围判定只有一处实现**,
漏一个工具就漏掉一整条越界痕迹。`scope_check` 是唯一例外,因为它本身**就是**范围查询。

| 工具名 | 类别 | 出网 | 需确认 | 说明 |
| --- | --- | --- | --- | --- |
| `scope_check` | passive | ✗ | ✗ | 校验目标是否在已登记授权范围内。**所有主动操作前必须先调用。**返回 allow/deny、命中的授权 ID、生效速率上限。可选 `tool_name` 参数会一并按该授权的 `allowed_tools` 判定,用于在发起主动调用前预判工具是否被放行 |
| `auth_list` | passive | ✗ | ✗ | 列出已登记的授权(含有效期、范围规则数、速率上限) |
| `asset_list` | passive | ✗ | ✗ | 列出本地资产清单。资产来自人工录入或平台导出,不来自主动扫描 |
| `asset_add` | passive | ✗ | ✗ | 录入一条资产,自动做范围标注(标注 ≠ 授权) |
| `finding_create` | passive | ✗ | ✗ | 创建发现草案。`target` 走管线范围校验,命中的授权必须与 `authorization_id` 一致;`severity_hint` 只是提示,不自动定级 |
| `finding_list` | passive | ✗ | ✗ | 列出发现草案 |
| `evidence_save` | passive | ✗ | ✗ | 保存证据。**落盘前强制脱敏**;单发现数量与总量有限额 |
| `evidence_list` | passive | ✗ | ✗ | 列出某发现下的证据及其脱敏命中统计 |
| `report_generate_draft` | passive | ✗ | ✗ | 生成报告草稿(本地文件 + 状态库)。**这不是提交动作** |
| `report_get_draft` | passive | ✗ | ✗ | 读取报告草稿全文 |
| `report_set_review` | passive | ✗ | ✗ | 记录人工复核结论(pending / approved / rejected) |
| `report_mark_submitted` | passive | ✗ | ✗ | **回填**人工在平台网页完成的提交结果。要求草稿已 approved |
| `report_platform_status` | passive | ✗ | ✗ | 查询本地登记的平台报告处置状态(只读跟踪) |
| `vulbox_programs` | passive | ✗ | ✗ | 列出平台侧授权项目(mock/manual 适配器零网络) |
| `vulbox_assets` | passive | ✗ | ✗ | 列出平台侧公开资产并逐条标注是否在授权范围内 |
| `audit_query` | passive | ✗ | ✗ | 查询审计记录。`alerts_only=true` 只返回违规调用告警 |

**主动 3 个**:出网、需要一次性人工确认令牌、**默认关闭**。

| 工具名 | 类别 | 出网 | 需确认 | 说明 | 影响面 |
| --- | --- | --- | --- | --- | --- |
| `http_probe` | active | ✓ | ✓ | 对**单个**目标发起一次只读请求(GET/HEAD),返回状态码、安全化响应头、Cookie 名称(不含值)与正文预览。不做路径爆破、不做参数变异、不跟随重定向、遵守 robots.txt | 1 次只读请求,等价于一次普通浏览器访问 |
| `tech_detect` | active | ✓ | ✓ | 被动指纹识别:读取一次响应,按内置规则推断技术栈。不发送探测载荷、不枚举路径、不做版本爆破 | 1 次 GET,无载荷、无枚举、无写入 |
| `tls_check` | active | ✓ | ✓ | 读取 https 目标的 TLS 证书公开字段(颁发者、到期时间、剩余天数、TLS 版本)。不保存证书链、不尝试绕过校验 | 1 次 TLS 握手 |

**高风险 3 个**:出网、需要一次性人工确认令牌、**默认关闭**,且额外受载荷白名单约束。

| 工具名 | 类别 | 出网 | 需确认 | 说明 | 影响面 |
| --- | --- | --- | --- | --- | --- |
| `injection_verify` | high_risk | ✓ | ✓ | 注入类发现的**存在性验证**(不是利用)。用内置载荷白名单中的成对布尔载荷比较响应**形状**差异(状态码/长度/词数/错误关键词,不留正文),输出 `suspicious` / `not_reproduced`。**不接受自定义载荷**(`PAYLOAD_NOT_ALLOWED`) | 2 次只读请求;不提取数据、不落文件、不建会话;请求数硬上限 8 |
| `xss_probe` | high_risk | ✓ | ✓ | XSS 反射**验证**:发送无执行能力的标记字符串,检查是否原样出现在响应中并给出上下文提示。载荷不含 `<script>`、不含事件属性 | 1 次只读请求;不会在目标浏览器中执行任何脚本 |
| `access_control_probe` | high_risk | ✓ | ✓ | 越权**判定**验证:用基线身份与对照身份各发一次只读 GET,比较响应可见性差异。不修改资源、不遍历用户 ID、不提权 | 2 次只读 GET(不同身份凭证);无写入、无枚举 |

---

## Agent 技能层(`skills/`)

工具层的每道闸门(范围校验、人工确认、限流、脱敏)都是**单次调用**级别的约束。
它们拦得住「这一次调用越界了」,拦不住「这个 Agent 的整体打法越界了」——
比如逐个子域去试、把一次验证拆成两百次请求、把 `suspicious` 直接写成 confirmed。

`skills/` 补的就是这一层:8 份写给 Agent 的操作规程,每份都写了**做什么**、**按什么顺序**、
**绝不能做什么**。

| 技能 | 何时用 | 用的工具 | 出网 | 需确认 |
| --- | --- | --- | --- | --- |
| [`authorization_check`](skills/authorization_check/SKILL.md) | 开工前第一件事 | `vulbox_programs` `auth_list` `scope_check` | ✗ | ✗ |
| [`scope_parse`](skills/scope_parse/SKILL.md) | 把范围翻成允许/禁止清单 | `auth_list` | ✗ | ✗ |
| [`asset_discovery`](skills/asset_discovery/SKILL.md) | 建资产清单(被动优先) | `vulbox_assets` `asset_add` `asset_list` | ✗ | ✗ |
| [`fingerprint`](skills/fingerprint/SKILL.md) | 识别技术栈,为验证选 check | `tech_detect` `http_probe` `tls_check` | ✓ | ✓ |
| [`safe_vuln_verify`](skills/safe_vuln_verify/SKILL.md) | 非破坏性验证 | `injection_verify` `xss_probe` `access_control_probe` | ✓ | ✓ |
| [`evidence`](skills/evidence/SKILL.md) | 存脱敏证据 | `evidence_save` `evidence_list` | ✗ | ✗ |
| [`report`](skills/report/SKILL.md) | 生成草稿、跟踪状态 | `report_*` `finding_create` | ✗ | ✗ |
| [`human_review`](skills/human_review/SKILL.md) | 把该人做的事交回给人 | 无(CLI 动作) | ✗ | 人做 |

技能是纯 Markdown,自足且无依赖。宿主支持技能目录就加进搜索路径,否则按需把
`SKILL.md` 喂进上下文即可。索引与四条铁律见 [`skills/README.md`](skills/README.md)。

---

## MCP 服务面:资源与提示词

MCP 的服务面由三部分组成。本项目三样都提供,且后两者刻意做得很窄。

### 资源(resources)

资源让 Agent **不必调工具**就能知道"现在被允许测什么"。共 3 个静态资源 + 1 个模板:

| URI | 内容 |
| --- | --- |
| `vulbox://scope` | 已登记的授权条目(项目、有效期、范围规则、deny 规则、速率上限) |
| `vulbox://tools` | 22 个工具的类别、是否出网、是否需要人工确认、影响面 |
| `vulbox://skills` | 8 份技能的索引(用途、涉及工具、是否出网、是否需确认) |
| `vulbox://skills/{name}` | 单份技能的完整规程正文 |

两条硬约束:

1. **资源不引入新能力。** `vulbox://scope` 的数据是经 `registry.execute("auth_list", …)`
   取的——与工具调用**同一条路径**。所以资源里看到的内容与工具返回的逐字节相同
   (不会出现"资源说可以、工具说不行"这种最坏的分歧),且这次读取**照样进审计**。
   资源是第二条读取路径,但不是第二条不受审计的路径。
2. **`{name}` 走白名单查表,不拼路径。** URI 模板参数是调用方可控输入;实现里先在
   已知技能中查一次,查不到直接拒绝。`vulbox://skills/../../etc/passwd` 在 SDK 的
   URI 路由层就被挡下,我们的白名单是第二道——两道都要在。

URI 用自定义 `vulbox://` scheme 而非 `file://`:后者会让客户端以为能读任意路径。

### 提示词(prompts)

提示词只产出**文本**,不触发任何工具调用。因此这里写的是"按什么顺序做、在哪一步
停下来找人",而不是"替我做完"。

| 提示词 | 参数 | 作用 |
| --- | --- | --- |
| `start_authorized_test` | `program_id` `target` | 开工五步:读规程 → 查授权 → `scope_check` → 确认工具放行 → 才继续。点名 `force_pass` 这类**不存在**的旁路参数 |
| `verify_finding_safely` | `target` `param` `check_id` | 非破坏性验证:只许用内置载荷库、需人工确认、`suspicious` 不等于漏洞成立 |
| `draft_report` | `program_id` `title` | 整理草稿:登记发现 → 存脱敏证据 → 生成草稿;明写**不存在自动提交** |
| `review_before_submit` | `draft_id` | 把复核清单原样交给人;未 approved 时不许 `report_mark_submitted` |

复核清单与提交指引来自 `tools/passive.py` 的 `REVIEW_CHECKLIST` / `SUBMIT_INSTRUCTIONS`
常量——**唯一副本**,报告草稿与提示词都引用它。分成几份写迟早会漂移,而漂移的那
一份就是复核时被漏掉的那一条。

---

## 常见拒绝场景与合规替代方案

系统被拒绝时,`ToolResult.alternative` 字段会给出可执行路径(来自 `errors.py` 的 `COMPLIANT_ALTERNATIVES`)。
下面是几个最容易踩到的:

### 1. 目标越界

**会看到**:`SCOPE_DENIED` / `GLOBAL_DENY_HIT` / `AUTHORIZATION_EXPIRED` / `AUTHORIZATION_MISSING`

```
目标未通过范围校验:目标不在任何已登记授权范围内
```

**正确做法**:先在 `config/scope.yaml` 登记授权(`evidence_ref` 必填书面授权引用),
或改用已授权项目内的等价目标,或仅对该目标做完全离线的文档/配置审查。
命中 `GLOBAL_DENY_HIT`(云元数据网段、组播地址)时**没有任何替代路径**——该列表不可被配置覆盖。

### 2. 未确认的主动调用

**会看到**:`CONFIRMATION_REQUIRED`

```
工具 http_probe 需人工确认。已生成确认请求 cnf_xxxxxxxx。
请由人类执行:mcp-vulbox-agent confirm approve cnf_xxxxxxxx --actor <你的名字>
```

**正确做法**:把 `confirmation_id` 交给人类,由人类在 CLI 中 approve。
令牌与「工具 + 规范化目标 + 参数哈希」绑定,换目标或换参数即失效,且只能消费一次。
不要尝试复用旧令牌(`CONFIRMATION_MISMATCH`)。

### 3. 要求自动提交

**会看到**:注册表中**没有**提交类工具,调用会得到 `UNKNOWN_TOOL`;
若草稿未通过复核就调 `report_mark_submitted`,会得到 `REPORT_INCOMPLETE`:

```
草稿尚未通过人工复核(review_status != approved),不能登记为已提交。请先完成逐项复核。
```

**正确做法**:`report_generate_draft` 生成草稿 → 人类逐项复核 → `report_set_review` 标 approved →
**人类在平台网页上提交** → `report_mark_submitted` 回填编号。

### 4. 要求自定义注入载荷

**会看到**:`PAYLOAD_NOT_ALLOWED`

```
本系统只接受内置安全载荷库中的非破坏性验证载荷,不接受自定义注入串或武器化 EXP。
```

**正确做法**:用 `config/safe_checks.yaml` 中已登记的 `check_id` 重试;
若确有新的验证需求,先由人类评审并把载荷写入该文件(含影响说明);
或改用离线样本在隔离环境分析。

### 5. 要求无人值守

**会看到**:`TOOL_DISABLED`(工具未启用)或 `MODE_NOT_ALLOWED`(offline 模式不允许出网)

```
工具 http_probe 未启用(类别:active)。主动工具需要在 config.yaml 与环境变量中**同时**列入白名单。
```

**正确做法**:没有无人值守模式,也没有批量目标参数——这是设计而非缺陷。
需要跑量时,由人类逐次批准,或把目标交给平台的官方扫描能力,而不是这个工作台。

### 6. 工具输出疑似包含提示词注入

**会看到**:`INJECTION_SUSPECTED`,且 `data` 被整段丢弃、只留隔离说明与哈希

**正确做法**:用 `mcp-vulbox-agent quarantine show <entry_id>` 由**人类**查看原文
(读取本身写入审计);或更换目标页面、改用离线样本继续分析。
注意:被测系统自身包含注入文本,往往**恰恰是漏洞证据**,所以隔离不等于删除。

---

## 合规与法律声明

使用本项目前请阅读:

- [`docs/COMPLIANCE.md`](docs/COMPLIANCE.md) —— 适用法律、授权要求、数据最小化、平台规则、留存与删除、违规拒绝清单
- [`docs/AI-USAGE.md`](docs/AI-USAGE.md) —— AI 在本项目中的角色边界、水印、人工复核清单、提示词注入防护
- [`docs/DISCLAIMER.md`](docs/DISCLAIMER.md) —— 免责声明
- [`SECURITY.md`](SECURITY.md) —— 安全模型、威胁边界、漏洞报告流程

**一句话**:只允许用于自有资产、明确书面授权的目标、靶场、或平台授权项目。
使用者对自己的一切行为负责。

---

## 验收清单

下面每一条都能在本机复现,且**结果写明了实测输出**。复现前先按[安装](#安装)建好 venv、
按[配置 `.env`](#配置-env) 生成确认密钥,然后执行:

```bash
export PYTHONPATH=src          # Windows: $env:PYTHONPATH="src"
export PYTHONIOENCODING=utf-8  # Windows: $env:PYTHONIOENCODING="utf-8"
```

| # | 标准 | 复现命令 | 实测结果 |
| --- | --- | --- | --- |
| 1 | `docker compose up` 可启动 | `docker compose run --rm mcp-server doctor` | 自检通过,退出码 0。容器内 Python 3.11.17,授权条目 2 条、全局拒绝 5 条,主动工具 6 个已启用 0 个。**唯一 `warn` 是「确认密钥未配置」**——compose 未挂载 `.env` 时的预期结果,配置后即消失 |
| 2 | MCP Inspector 能看到全部合规工具 | `npx @modelcontextprotocol/inspector mcp-vulbox-agent serve`(同[快速开始](#1-用-mcp-inspector-连上stdio-形式)) | `list_tools()` 返回 22 个工具,每个都带 `input_schema`。**无 `vulbox_submit_report`**;名字含 `submit` 的只有 `report_mark_submitted`(被动、不出网、只登记状态)。另有 `resources/list` 3 项 + 1 个模板、`prompts/list` 4 项 |
| 3 | mock 环境跑通完整流程 | `pytest tests/test_report_workflow.py -q` | 39 passed:发现 → 证据 → 草稿 → 复核 → 人工提交登记 |
| 4 | scope guard 阻止越界且无强制通过选项 | 见下方 [4a](#4a-越界目标被拦) / [4b](#4b-不存在强制通过参数) | `deny / not_in_scope`;`force_pass` 被 `INVALID_ARGUMENT` 拒绝 |
| 5 | 主动工具默认关闭,需显式启用 + 人工确认 | `pytest tests/test_registry_pipeline.py -k disabled -q` | 全部返回 `TOOL_DISABLED`;启用后仍需确认令牌 |
| 6 | 不存在自动提交工具 | `pytest tests/test_registry_pipeline.py -k submission -q` | 唯一含 `submit` 的是 `report_mark_submitted`(登记,非提交);`adapter.submit_report()` 恒抛 `MODE_NOT_ALLOWED` |
| 7 | 能生成报告草稿并提示人工复核 | 见下方 [7](#7-草稿带水印与复核清单) | 水印 `AI 辅助生成,需人工验证`,6 项复核清单,5 步提交说明 |
| 8 | 证据保存前自动脱敏 | 见下方 [8](#8-证据落盘前脱敏) | `[REDACTED:authorization_header]` / `[REDACTED:cn_phone]` / `[REDACTED:email]`,`needs_manual_review=true` |
| 9 | 审计完整且敏感字段脱敏 | 见下方 [9](#9-审计留痕与脱敏) | 越界调用进 `alerts()`,记录 `deny / not_in_scope`;审计全文不含 token、手机号、邮箱 |
| 10 | 能拒绝越界 / 无人值守 / 自动提交 / 武器化请求 | 见下方 [10](#10-四类越界请求的拒绝输出) | 四类各有明确错误码与合规替代方案 |
| 11 | pytest、ruff、mypy 通过 | `pytest && ruff check src tests && mypy src` | `326 passed`;`All checks passed!`;`Success: no issues found in 33 source files` |
| 12 | 新用户 10 分钟内跑起来且边界清晰 | 按[快速开始](#快速开始)逐步执行 | 从零到 `scope_check` 返回 `allow` 约 5 分钟 |

第 2 条另有端到端实证:`python stdio_acceptance.py` 用**真实 MCP 客户端**连上
服务进程(JSON-RPC 经管道),逐项核对资源、模板、提示词与路径穿越拒绝,
共 17 项断言全通过——直接调 `build_server()` 验证的是"对象上挂着什么",
只有真连一次才能发现 schema 不被识别、模板参数名对不上这类问题。

### 4a. 越界目标被拦

```bash
mcp-vulbox-agent scope check https://evil.example.com/
# → deny / not_in_scope

mcp-vulbox-agent scope check http://169.254.169.254/
# → deny / global_deny(云元数据,写死的全局拒绝,不可协商)
```

### 4b. 不存在强制通过参数

```python
registry.execute("scope_check", {"target": "...", "force_pass": True}, ctx)
# → INVALID_ARGUMENT: 参数校验失败(1 处):force_pass: Extra inputs are not permitted
```

参数模型是 `extra="forbid"`,`force_pass` / `ignore_scope` / `skip_check` 这类字段
**在参数层就被拒**,连范围校验都到不了。代码里也没有任何 `force` / `override` 开关。

### 7. 草稿带水印与复核清单

```bash
mcp-vulbox-agent report show <draft_id>
# → watermark: AI 辅助生成,需人工验证
#   review_checklist: 6 项
#   submit_instructions: 5 步
#   review_status: pending
```

草稿处于 `pending` 时调用 `report_mark_submitted` 会得到 `REPORT_INCOMPLETE`——
**未复核的报告连「登记为已提交」都不允许**。复核后 `review_status` 变 `approved`,
并记下 `reviewed_by`(谁复核的),此时才允许登记。

### 8. 证据落盘前脱敏

输入含 `Authorization: Bearer sk-live-...`、邮箱、手机号的响应头,落盘后:

```
[REDACTED:authorization_header]
contact: [REDACTED:email]
phone [REDACTED:cn_phone]
Strict-Transport-Security: (absent)
```

`redaction_hits` 返回命中类别与次数,`needs_manual_review=true` 表示含个人信息类命中,
**必须人工复核后才允许上传**(`EvidenceStore.assert_uploadable` 会拦)。

### 9. 审计留痕与脱敏

被拒的调用恰恰是最需要留痕的一类。一次越界 `finding_create` 之后:

```bash
mcp-vulbox-agent audit query --alerts
# → SCOPE_DENIED,scope_decision=deny,scope_reason=not_in_scope
```

审计文件是 JSONL 按天分片,**追加写 + fsync**,`allow_silent_delete` 硬约束为 `false`,
删除必须显式 `--actor` + `--yes` + 交互确认(配置了 `MCP_VULBOX_PURGE_SECRET` 时还需 `--secret`)。
实测审计全文不含 token、手机号、邮箱。

### 10. 四类越界请求的拒绝输出

| 请求 | 错误码 | 拒绝理由 |
| --- | --- | --- |
| 扫描授权范围外的目标 | `SCOPE_DENIED` | 目标不在任何已登记授权范围内 |
| 要求无人值守连续运行 | `CONFIRMATION_REQUIRED` | 主动工具每次调用都要新的确认令牌 |
| 要求自动提交报告 | `MODE_NOT_ALLOWED` | 本系统不存在自动提交能力,这是设计约束而非配置项 |
| 要求自定义注入载荷 | `INVALID_ARGUMENT` / `PAYLOAD_NOT_ALLOWED` | 参数模型不含 `payload` 字段;载荷只从内置库取 |

每一类都带 `alternative` 字段给出合规替代方案,而不是只说「不行」。

---

## 开发指南

```bash
# 静态检查与格式
ruff check src tests stdio_acceptance.py
ruff format --check src tests stdio_acceptance.py

# 类型检查(strict 模式)
mypy src

# 测试
pytest
pytest --cov=mcp_vulbox_agent --cov-report=term-missing

# MCP 协议端到端验收(真实 stdio 握手,核对 resources / prompts / 路径穿越拒绝)
python stdio_acceptance.py
```

配置说明(全部来自 `pyproject.toml`):

- **ruff**:`line-length = 88`、`target-version = "py311"`,
  lint 规则集含 `S`(bandit 安全类,必须开)、`T20`(禁止裸 print)、`DTZ`(强制时区感知)、
  `PTH`、`ARG`、`RET`、`PIE`、`PT`、`TC` 等。`E501` 与 `RUF001/002/003` 被忽略——
  中文全角标点是本项目的既定文风,不是「歧义字符」。
- **mypy**:`strict = true`、`warn_unreachable`、`warn_unused_ignores`、`disallow_untyped_defs`。
- **pytest**:`asyncio_mode = "auto"`、`--strict-markers --strict-config`,
  自定义 marker:`slow`、`integration`。

贡献前请读 [`CONTRIBUTING.md`](CONTRIBUTING.md) —— 里面列了**不得新增的能力类型**。

---

## License

MIT License,见 [`LICENSE`](LICENSE)。

```
Copyright (c) 2026 mcp-vulbox-agent contributors
```