# 数据字段

所有状态文件用 JSON。Agent 读写时严格遵循本结构。

> **结构规范以 [`f/*.json`](field-spec/) 为准**(JSON 字段规范 Draft-07,8 个文件)。本文件是人类可读说明 + 字段语义,如有结构冲突以 字段文件为准。field-spec 文件作为字段规范供模型写入时参考,不做机器校验。
>
> **核心设计**:不预定义漏洞类型清单。Discovery Agent 对每个 feature 做威胁建模,自由命名威胁项(如 `IDOR-编辑他人订单`、`存储型 XSS-评论字段`、`未授权访问-管理接口`),并给出推理理由。Attack Agent 遍历 `threat_model` 逐项验证。

存储根目录:`{工作目录}/pentest-data/{project-id}/`。**多项目隔离**:每个项目一个子目录(project-id 命名),根目录 `pentest-data/index.json` 是项目清单。project-id 默认从 target hostname 第一段提取(`bi-fine-sit.agibot.com` → `bi-fine-sit`),可 `--project <name>` 覆盖。

---

## index.json(项目清单,多项目入口)

多项目管理入口。位置:`pentest-data/index.json`(**根目录,不在项目子目录内**)。Discovery 建项目时初始化,每次进入项目更新, Audit 完成时回填状态。详见 [`f/003.json`](f/003.json)。

```json
{
  "_note": "项目清单,Discovery 建项目时初始化,Audit 更新状态",
  "projects": [
    {
      "project_id": "finebi",
      "target": "https://bi-fine-sit.agibot.com",
      "system_name": "智元FineBI-SIT",
      "dir": "finebi",
      "created": "2026-06-20T10:00:00+08:00",
      "last_active": "2026-06-20T13:00:00+08:00",
      "phase": 3,
      "status": "completed",
      "vulns_confirmed": 3
    }
  ]
}
```

**字段**:`project_id`(唯一,`[a-z0-9-]+`)/ `target` / `system_name` / `dir`(=project_id) / `created` / `last_active` / `phase`(断点恢复) / `status`(`in_progress`/`completed`/`abandoned`) / `vulns_confirmed`(Audit 回填)。

**写入时机**:Discovery 建项目 → append 一条(文件不存在则建);每次进入项目 → 更新 `last_active` + `phase`;Audit 完成 → 更新 `status=completed` + `vulns_confirmed` + `system_name`。

---

## state.json(任务断点状态)

```json
{
  "project_id": "demo",
  "base_url": "https://target.example.com",
  "current_phase": 2,
  "current_module": "user_mgmt",
  "session_pool_ready": true,
  "active_sessions": ["admin", "user1", "user2"],
  "session_pool_limitation": null,
  "tested_module_ids": ["dashboard"],
  "scan_start_time": "2026-06-12T10:00:00+08:00",
  "registration_page": "https://target.example.com/register"
}
```

字段说明:

| 字段 | 类型 | 说明 |
|------|------|------|
| `base_url` | string | 目标 origin,所有 URL 基于此拼接 |
| `current_phase` | int | 1 / 2 / 3 |
| `session_pool_limitation` | string? | `null`(≥2 账号)/ `"single_account"`(仅 1 账号)/ `"no_account"`(0 账号)。账号不足不阻塞,正常降级测试 |
| `tested_module_ids` | string[] | **断点用**——已测模块的 id 列表(轻量)。完整记录(含 status/vulns_found)在 [module_queue.json](#module_queuejson模块队列支持阶段-2-动态增长) 的 `tested_modules`(对象数组)里。两者同名异形,不要混用 |
| `registration_page` | string? | Discovery 探测到的注册页 URL,无则 `null`;账号不足时供阶段 3 提示用户注册补全 |
| `project_id` | string | 本项目 project-id,对应 `pentest-data/{project-id}/`;默认 target hostname 第一段,`--project` 可覆盖 |

**时间戳生成**:所有 `*_time` / `scan_start` / `scan_end` / `init_time` 字段通过 Bash `date -Iseconds` 获取当前时间,**禁止模型编造**。

---

## module_queue.json(模块队列,支持阶段 2 动态增长)

阶段 1 由 Discovery Agent 写入一级模块(`source: "initial"`);阶段 2 Attack Agent 在测试中发现的子模块入口 append 进来(`source: "discovered_in_{模块id}"`)。`discovered_module_ids` 是全局去重列表——append 前必须先查它。

```json
{
  "base_url": "https://target.example.com",
  "module_queue": [
    {"name": "用户管理", "id": "user_mgmt", "url": "https://...", "priority": "high", "source": "initial"},
    {"name": "角色管理", "id": "role_mgmt", "url": "https://...", "priority": "medium", "source": "discovered_in_user_mgmt"}
  ],
  "tested_modules": [
    {"name": "仪表盘", "id": "dashboard", "status": "completed", "vulns_found": 0}
  ],
  "discovered_module_ids": ["dashboard", "user_mgmt", "role_mgmt"]
}
```

调度与去重:

- `module_queue.pop(0)` → 测试 → `tested_modules.append(module)`
- 发现新模块入口时:生成 `id` → 若 `id` 已在 `discovered_module_ids` 则跳过 → 否则 append 到 `module_queue` 并加入 `discovered_module_ids`
- `source` 字段追溯模块来源,审计时可看出模块发现链路

---

## modules/{module_id}.json(单模块完整数据)

```json
{
  "module": "用户管理",
  "module_id": "user_mgmt",
  "url": "https://target.example.com/admin/user",
  "status": "completed",

  "features": [
    {
      "feature_id": "user_edit_001",
      "name": "用户编辑",
      "type": "form",
      "url": "https://target.example.com/admin/user/edit",
      "access_path": [
        {"step": 1, "action": "登录"},
        {"step": 2, "action": "进入仪表盘"},
        {"step": 3, "action": "点击「用户管理」", "url": "https://.../admin/user"},
        {"step": 4, "action": "点击「编辑」", "url": "https://.../edit?id=5"}
      ],
      "apis": [{"url": "https://...", "method": "PUT", "params": ["id"]}],
      "threat_model": [
        {
          "threat_id": "T1",
          "vuln_type": "IDOR-编辑他人用户",
          "reasoning": "PUT /api/user/{id} 的 id 参数由客户端控制,后端可能未校验资源归属",
          "target_api": "https://...",
          "target_params": ["id"]
        },
        {
          "threat_id": "T2",
          "vuln_type": "垂直越权-普通用户访问管理接口",
          "reasoning": "路径前缀 /admin/ 暗示需要管理员角色,但前端路由可能可绕过",
          "target_api": "https://...",
          "target_params": []
        },
        {
          "threat_id": "T3",
          "vuln_type": "批量赋权-role 字段注入",
          "reasoning": "编辑请求体可能接受 role 字段,客户端可注入提升自身权限",
          "target_api": "https://...",
          "target_params": ["role"]
        }
      ]
    }
  ],

  "validation_results": [
    {
      "feature_id": "user_edit_001",
      "tests": [
        {
          "threat_id": "T1",
          "vuln_type": "IDOR-编辑他人用户",
          "status": "confirmed",
          "evidence_type": "api",
          "access_path": ["...继承自 feature..."],
          "request_response": {
            "baseline": {
              "method": "PUT", "url": "https://...",
              "headers": {"Authorization": "Bearer ...(admin_token)"},
              "body": {"id": 5, "username": "admin_test"},
              "response_status": 200,
              "response_body": "{\"code\":0}"
            },
            "attack": {
              "method": "PUT", "url": "https://...",
              "headers": {"Authorization": "Bearer ...(user1_token)"},
              "body": {"id": 5, "username": "hacked"},
              "response_status": 200,
              "response_body": "{\"code\":0}"
            }
          },
          "session_pool_ref": {"attacker": "user1", "victim": "admin"}
        },
        {
          "threat_id": "T2",
          "vuln_type": "垂直越权-普通用户访问管理接口",
          "status": "not_vulnerable",
          "evidence_type": "api",
          "request_response": {
            "attack": {
              "method": "PUT", "url": "https://...",
              "headers": {"Authorization": "Bearer ...(user1_token)"},
              "body": {"id": 5, "username": "x"},
              "response_status": 403,
              "response_body": "{\"code\":403}"
            }
          }
        },
        {
          "threat_id": "T3",
          "vuln_type": "批量赋权-role 字段注入",
          "status": "not_vulnerable",
          "evidence_type": "api",
          "notes": "注入 role=admin 字段,后端忽略,自身角色未变"
        }
      ]
    }
  ],

  "cross_role_verification": {
    "tested": true,
    "test_time": "2026-06-12T10:30:00+08:00",
    "role_pairs_tested": [
      {"attacker": "user1", "victim": "admin", "pair_type": "vertical"},
      {"attacker": "user2", "victim": "user1", "pair_type": "horizontal"}
    ],
    "findings": [
      {"target_api": "...", "attacker": "user1", "victim": "admin", "result": "idor_confirmed"}
    ]
  }
}
```

**threat_model 字段约定**:

| 字段 | 说明 |
|------|------|
| `threat_id` | feature 内唯一标识,推荐 `T1`/`T2`/... |
| `vuln_type` | **模型自由命名**,精确描述威胁(避免泛泛的 "IDOR",用 "IDOR-编辑他人用户" 这种带场景的) |
| `reasoning` | **必填**,基于 feature 的什么特征推断出此威胁(数据流 / 权限边界 / 外部输入 / 状态变更 等) |
| `target_api` / `target_params` | 威胁指向的具体接口和参数 |

**validation_results.tests[].status 枚举**:`confirmed` / `suspicious` / `not_vulnerable` / `blocked`

**反遗漏两个字段(v2.2)**:

- `feature.coverage_note`(每个 feature 必填,非空):回答三句泛化自问——**输入面**(每个外部可控输入要么测要么说为啥不测)、**行为面**(每个接口/状态变化/并发点/角色组合都覆盖)、**深度面**(判"安全"的是真穷尽还是一种姿势)。几句话,不要表格。对应 G2 + `r/023.txt`。
- `test.unruled_out`(每条 `not_vulnerable` 必填,非空):列出"还没排除的攻击面"。不确定的不能标 not_vulnerable。对应 G2 + `r/024.txt`。

**威胁建模要求**:

- 每个威胁必须有 `reasoning`,不得只写 `vuln_type` 不解释
- 威胁项数量不限,但模型需诚实——没风险的 feature 可以 `threat_model: []`
- 任何 `threat_model` 项必须在 `validation_results.tests` 中有对应 `threat_id` 的验证记录(否则 G2 不过)
- 每个 feature 必须有非空 `coverage_note`;每条 `not_vulnerable` 必须有非空 `unruled_out`(否则 G2 不过)

---

## sessions/pool.json(会话池索引)

```json
{
  "target": "https://target.example.com",
  "init_time": "2026-06-12T10:00:00+08:00",
  "last_refresh": "2026-06-12T12:00:00+08:00",
  "accounts": [
    {
      "role": "admin",
      "username": "admin",
      "role_level": "high_privilege",
      "session_file": "account_admin.json",
      "cookie_value": "sessionid=abc; token=xyz;",
      "status": "active",
      "capabilities": ["full_access", "user_management"]
    }
  ]
}
```

`status`: `active` / `expired` / `error`
`role_level`: `high_privilege` / `standard` / `low` —— **由用户在启动时显式声明**,不从用户名推断(G1 判定依赖此字段)

**注意**:不再保存 `context_id`。Browser Context 只存在于当前 Claude 会话,跨会话不可恢复,断点续接时必须重新登录初始化(见 [SKILL.md](SKILL.md))。

---

## sessions/account_{role}.json(单账号凭证快照)

```json
{
  "role": "admin",
  "username": "admin",
  "role_level": "high_privilege",
  "login_time": "2026-06-12T10:00:00+08:00",
  "last_used": "2026-06-12T11:30:00+08:00",
  "auth": {
    "token": "eyJhbGc...",
    "token_type": "Bearer",
    "cookie_string": "sessionid=abc; token=xyz;",
    "cookies": [{"name": "sessionid", "value": "abc", "domain": "...", "path": "/"}]
  },
  "identity": {
    "user_id": 1,
    "display_name": "管理员",
    "roles": ["admin"],
    "owned_resource_ids": [1, 5, 9]
  },
  "status": "active"
}
```

**凭据安全**:`account_*.json` 含明文 Token / Cookie,`pentest-data/` 必须加入 `.gitignore`(见 [templates/.gitignore](templates/.gitignore));凭据位于 `pentest-data/{project-id}/sessions/`,per-project 隔离。测试结束清理流程见 `r/015.txt`。
