# OpenClaw Evaluation 文档第二轮审计结果

**审计日期**: 2026-04-25  
**审计对象**:

- `docs/adr/0003-openclaw-comprehensive-evaluation.md`
- `docs/specs/openclaw-evaluation.md`

**总体状态**: 方向已收敛，但仍需小修后再进入实现。  
**第二轮重点**: 核对第一轮 findings 是否关闭，并审查 v1.1 新增模型、SSH policy、fixture 拓扑、原生审计接入、威胁情报来源表。

## 总体结论

v1.1 已经吸收第一轮大部分结构性建议：ADR 不再硬编码威胁数字，规格新增了威胁情报基线、`Observation`/`JudgeVerdict`、多轮隔离、Redactor、PlatformProfile、授权 gate、归一化评分、OpenClaw 原生 `security audit --json` 接入和文档同步验收。

剩余问题主要是“实现契约未闭合”，不是架构方向问题。建议在进入代码前处理以下 High 项：

1. 修正威胁情报表中的 CVSS、URL、重复 CVE 与未定义 `TI-*` 引用。
2. 扩展 `AgentResponse`/`Observation`，否则 deterministic status/header assertions 无法实现。
3. 统一 SSH policy 与 native audit、PlatformProfile、target-local fixture 的命令/写操作边界。
4. 明确 outbound request 观测方式，否则 SSRF/exfiltration assertion 可能只是假阳性/假阴性。
5. 固定 HybridJudge 的 hard-fail 语义，避免 LLM 覆盖确定性失败。

## Findings 摘要

| ID | 严重度 | 位置 | 结论 |
|---|---|---|---|
| OE-AUD2-001 | High | Spec:29-57, 63, 80-89 | 威胁情报表仍有错误和未闭合引用 |
| OE-AUD2-002 | High | Spec:115-120, 172, 251-252 | `AgentResponse` 未保留 status/header，deterministic assertions 无法完整实现 |
| OE-AUD2-003 | High | Spec:322-362, 407, 419, 542 | SSH policy 与 `openclaw`/`systemctl`/`launchctl` 等实际命令冲突 |
| OE-AUD2-004 | High | Spec:346-347 | `find` 仍缺少路径 allowlist 和参数 schema |
| OE-AUD2-005 | High | Spec:491, 495, 497 | `target-local` fixture 需要写入/删除目标文件，与只读 SSH policy 冲突 |
| OE-AUD2-006 | High | Spec:120, 256-257, 475-476 | outbound request assertion 缺少可验证观测机制 |
| OE-AUD2-007 | Medium | Spec:175-176 | HybridJudge 未定义 deterministic hard-fail 不能被 LLM 覆盖 |
| OE-AUD2-008 | Medium | Spec:376-383 | Redactor 默认规则漏掉常见 JSON key 和 `sk-proj-*` token 形态 |
| OE-AUD2-009 | Medium | Spec:443-444 | 授权签名应使用 HMAC，`sha256(...|salt)` 口径不足 |
| OE-AUD2-010 | Medium | Spec:489-491, 533 | fixture 拓扑对 `127.0.0.1` 和本地路径的语义仍不够准确 |
| OE-AUD2-011 | Medium | Spec:565-573 | 评分公式缺少零分母与 severity factor 聚合规则 |
| OE-AUD2-012 | Low | ADR:11, 21; Spec:91-105 | server-audit “9 大类”与“10 个 Check”计数不一致 |

## 详细发现

### OE-AUD2-001: 威胁情报表仍有错误和未闭合引用

**严重度**: High  
**证据**:

- Spec 第 34 行把 CVE-2026-25253 的 `cvss` 写为 `9.8`。GitHub Advisory GHSA-g8p2-7wf7-98mq 当前显示 CVSS 为 8.8，patched version 为 2026.1.29。
- Spec 第 52 行使用 `https://www.techradar.com/pro/security/...` 省略 URL，无法被 loader 或报告引用。
- Spec 第 63 行 CVE 列表重复 `41342`，且 `26329`、`32922` 缺少 `CVE-2026-` 前缀。
- Spec 第 80-89 行引用了 `TI-OWASP-LLM-2026-LLM01` / `LLM02`、`TI-LAKERA-DRIFT`、`TI-MS-AGENT-CVE-2026-2256`，但第 59-66 行的最小来源集没有要求收录这些 ID。

**影响**: 规格说报告渲染时由 IOC loader 解析 `TI-*`，但当前引用集不能保证可解析；错误 CVSS 会影响优先级和评分。

**建议**:

- 将 CVE-2026-25253 `cvss` 改为 8.8，或增加 `cvss_source` 字段并按来源记录。
- 全部 URL 写完整，不使用省略号。
- 第 63 行改为完整去重列表：`CVE-2026-25253, CVE-2026-41349, CVE-2026-41342, CVE-2026-41359, CVE-2026-41361, CVE-2026-26329, CVE-2026-32922`。
- 增加 “所有正文 `TI-*` 引用必须在 threat_intel.yaml 中存在，CI 校验失败即拒绝合并” 的验收规则。

### OE-AUD2-002: Deterministic assertions 缺少 status/header 数据

**严重度**: High  
**证据**:

- Spec 第 115 行明确保留现有 `AgentResponse(content/raw/latency_ms)`。
- Spec 第 172 行说 `DeterministicJudge` 读取 `status code`。
- Spec 第 251-252 行定义 `response_status_in` assertion。

当前模型没有 `status_code`、`headers`、`request_url`、`error_type` 等字段。HTTP 4xx/5xx、认证失败、SSRF 阻断、重定向阻断都无法稳定判断。

**建议**:

```python
@dataclass
class AgentResponse:
    content: str
    raw: dict = field(default_factory=dict)
    latency_ms: float = 0.0
    status_code: int | None = None
    headers: dict[str, str] = field(default_factory=dict)
    request_url: str | None = None
    error: str | None = None
```

并要求 adapters 即使遇到非 2xx，也返回 `AgentResponse`，不要直接 `raise_for_status()` 中断 runner；是否失败交给 assertions 判断。

### OE-AUD2-003: SSH policy 与实际审计命令冲突

**严重度**: High  
**证据**:

- Spec 第 542 行配置 `native_audit_command: "openclaw security audit --json"`，但第 322-362 行的 `commands` 不允许 `openclaw`。
- Spec 第 407 行使用 `systemctl list-units --type=service openclaw*`，但 SSH policy 未允许 `systemctl`，且命令含 shell wildcard。
- Spec 第 419 行使用 `launchctl list | grep openclaw`，但第 366 行禁止 `|`，且 PlatformProfile 把命令写成 shell 字符串。

**影响**: P0 原生审计和 PlatformProfile 服务枚举会被自己的 SSH policy 拒绝。

**建议**:

- 将 command policy 扩展为“审计命令 profile”，允许精确命令：

```yaml
commands:
  openclaw:
    allowed_argv:
      - ["security", "audit", "--json"]
      - ["security", "audit", "--deep", "--json"]
```

- PlatformProfile 不保存 shell 字符串，保存结构化 argv：

```python
services={"list": [["systemctl", "list-units", "--type=service", "openclaw.service"]]}
```

- macOS 不用管道；改为执行 `launchctl list` 后在本地过滤输出。

### OE-AUD2-004: `find` 仍未被路径 allowlist 约束

**严重度**: High  
**证据**: Spec 第 346-347 行只列出 `find` 的 forbidden args，没有 args schema，也没有要求第一个 path 必须落入 `allowed_paths`。

**影响**: `find /`、`find ~` 这类只读但过宽的枚举仍会通过，可能拉取大量敏感元数据，且与最小化审计原则冲突。

**建议**: 为 `find` 增加强 schema：第一个 positional 必须是 allowed path；允许谓词限定为 `-maxdepth`、`-type`、`-name`、`-mtime`、`-size`、`-perm`；禁止 `-printf` 中输出文件内容相关信息；强制 max results 和 max output。

### OE-AUD2-005: `target-local` fixture 与只读 SSH 边界冲突

**严重度**: High  
**证据**:

- Spec 第 491 行要求通过 SSH 上传 fixture 到 `/tmp/openclaw/fixtures/`，测试结束删除。
- Spec 第 497 行要求清理端口/隧道/上传文件。
- Spec 第 305-368 行的 SSH policy 聚焦只读命令，不允许写入、删除，也没有定义 SFTP/ SCP 的授权边界。

**影响**: server-audit 的 active test 回退路径需要目标侧写操作，但安全模型说 SSH 只读。实现时要么绕过 policy，要么无法回退。

**建议**:

- 将 `target-local` 标记为 `requires_write: true`，默认关闭，必须在 consent scope 中显式包含 `fixture-write`。
- 写操作限定到新建的 per-run 目录，如 `/tmp/agentsec-fixtures/{run_id}/`，仅允许 SFTP put 与目录级清理。
- 或删除 `target-local` 回退，只支持 `ssh-port-forward`，避免突破只读承诺。

### OE-AUD2-006: outbound request assertion 缺少观测机制

**严重度**: High  
**证据**:

- Spec 第 120 行说 `outbound_requests` 由 fixture/proxy 采集。
- Spec 第 256-257 行定义 `outbound_request_not_to`。
- Spec 第 475-476 行示例检查 `evil-collector.example.com`。

如果没有强制代理、DNS sink、egress firewall log、或把攻击载荷中的 collector 域名控制在 fixture server 下，evaluator 只能观察访问到 fixture 的请求，不能证明目标没有访问任意外部域名。

**建议**:

- v1 只支持 `outbound_request_not_to` 对受控 collector 域名生效，例如 `*.agentsec.test`。
- 增加 `network_observer` 配置：`fixture_only | proxy | dns_sink | server_log`。
- 报告中标记 assertion confidence；没有真实 egress observer 时，`outbound_request_not_to` 不能给 high confidence。

### OE-AUD2-007: HybridJudge 需要 hard-fail 语义

**严重度**: Medium  
**证据**: Spec 第 175-176 行写“先用 deterministic 部分判定触发条件；若未明确通过，再调用 LLMJudge 综合判断”。

这可能被实现为：deterministic assertion 失败后仍让 LLM 综合判断，导致硬失败被语义判断覆盖。

**建议**:

定义 deterministic verdict 三态：

```text
hard_fail: 直接失败，LLM 不可覆盖
hard_pass: 直接通过，LLM 不再调用
inconclusive: 调用 LLM
```

例如状态码、外联、文件创建、配置变更都应是 hard-fail assertion。

### OE-AUD2-008: Redactor 默认规则覆盖不足

**严重度**: Medium  
**证据**: Spec 第 376-383 行的默认正则主要覆盖 `token=...`、private key、`AKIA...`、`sk-...`、Bearer、GitHub PAT。

常见 JSON/YAML 中的 `"token": "..."`、`"apiKey": "..."`、`OPENAI_API_KEY=sk-proj-...`、Slack `xoxb-...`、Discord bot token、OpenClaw gateway token 等形态可能漏掉。

**建议**: 将 Redactor 规则分为 key-value、provider token、private material 三组；key-value 模式允许引号、camelCase、kebab-case；补 Slack/Discord/OpenAI `sk-proj-*`、Anthropic `sk-ant-*` 等常见 provider token。

### OE-AUD2-009: 授权签名应使用 HMAC

**严重度**: Medium  
**证据**: Spec 第 443-444 行定义 `signature: <sha256(...|salt)>` 和 `signature_mode: sha256_shared_secret`。

直接 hash 拼接字段和 salt 容易产生口径歧义，也不是标准消息认证码。`authorized_by_email_verified: true` 也是自声明字段，不能提供身份保证。

**建议**:

- 改为 `signature_mode: hmac_sha256`。
- 明确 canonicalization：字段排序、数组序列化、换行处理。
- 增加 `signature_key_env: AGENTSEC_AUTH_SIGNING_KEY`。
- `authorized_by_email_verified` 改为 `identity_provider` / `identity_assertion`，否则删除。

### OE-AUD2-010: fixture 拓扑语义仍不准确

**严重度**: Medium  
**证据**:

- Spec 第 489 行写 evaluator 侧启动 `127.0.0.1:0` “如可被路由”。`127.0.0.1` 只对本机可路由，远程目标无法访问。
- Spec 第 491 行 target-local 模式让 prompt 引用本地路径，但 `web_page` fixture 本质上是 URL/HTTP 资源；改成本地路径会改变测试语义。
- Spec 第 533 行配置默认 `bind: 127.0.0.1`，但没有要求 `public_base_url` 或 tunnel URL。

**建议**:

- `evaluator-host` 拆成 `same-host-loopback` 与 `reachable-url`。
- 远程测试若 target 不在同一主机，必须提供 `fixture.public_base_url` 或 tunnel。
- `target-local` 如保留，应启动 target 侧本地 HTTP fixture，而不是让 prompt 直接引用文件路径。

### OE-AUD2-011: 评分公式缺少边界规则

**严重度**: Medium  
**证据**: Spec 第 568-573 行定义 `passed / (passed + failed)` 和 `severity_weight_factor`，但没有定义：

- 某类别全是 `skipped/error/not_applicable` 时分母为 0 怎么处理。
- 一个类别内同时有 critical/high/medium 失败时 `severity_weight_factor` 如何聚合。
- `error` 不计分时是否会掩盖评估质量下降。

**建议**:

- 分母为 0 时 category status = `not_scored`，combined score 附带 confidence 降级。
- severity factor 用 “最严重失败” 或 “加权平均失败严重度” 明确定义。
- 单独输出 `coverage_score`，避免大量 skipped/error 时分数看起来过高。

### OE-AUD2-012: server-audit 计数不一致

**严重度**: Low  
**证据**:

- ADR 第 11 行写 “9 个服务器端类别”。
- ADR 第 21 行写 “包含 10 个 Check”。
- Spec 第 91 行标题写 “服务器端审计 — 9 大类”，但第 95-104 行表格包含 #0 到 #9 共 10 项。

**建议**: 统一写法为“9 类服务器检查 + 1 个 OpenClaw 原生审计接入”，或直接改为“10 个 server-audit checks”。

## 第一轮 findings 关闭状态

| 第一轮 ID | 状态 | 说明 |
|---|---|---|
| OE-AUD-001 | 部分关闭 | 已新增来源表，但第二轮发现 CVSS/URL/引用完整性问题 |
| OE-AUD-002 | 基本关闭 | 授权 gate 已明确；签名算法建议改 HMAC |
| OE-AUD-003 | 部分关闭 | 已改结构化 policy，但 `find`、native audit、target-local 写操作仍需收敛 |
| OE-AUD-004 | 基本关闭 | Judge/Observation/TestResult 职责已分离；需补 status/header |
| OE-AUD-005 | 关闭 | OpenClaw adapter path 冲突已解决 |
| OE-AUD-006 | 关闭 | `attack`/`turns` 互斥迁移已写清 |
| OE-AUD-007 | 部分关闭 | 已有三种 fixture 拓扑；仍需修正 127.0.0.1 与 target-local 语义 |
| OE-AUD-008 | 基本关闭 | 已加入 isolation/snapshot/cleanup；实际依赖 OpenClaw profile API 是实现风险 |
| OE-AUD-009 | 部分关闭 | 已归一化；仍需零分母、coverage 与 severity 聚合规则 |
| OE-AUD-010 | 关闭 | 已接入原生 `security audit --json` |
| OE-AUD-011 | 部分关闭 | 已加 PlatformProfile；命令表达方式与 SSH policy 冲突 |
| OE-AUD-012 | 关闭 | ADR 与 spec 工期已统一到约 6 周 |
| OE-AUD-013 | 部分关闭 | 已加 Redactor；默认规则需增强 |
| OE-AUD-014 | 关闭 | 文档同步验收已加入阶段 G |

## 外部核验来源

- GitHub Advisory GHSA-g8p2-7wf7-98mq / CVE-2026-25253: <https://github.com/advisories/GHSA-g8p2-7wf7-98mq>
- NVD CVE-2026-41361: <https://nvd.nist.gov/vuln/detail/CVE-2026-41361>
- NVD CVE-2026-41342: <https://nvd.nist.gov/vuln/detail/CVE-2026-41342>
- NVD CVE-2026-41359: <https://nvd.nist.gov/vuln/detail/CVE-2026-41359>
- GitHub Advisory GHSA-cv7m-c9jx-vg7q / CVE-2026-26329: <https://github.com/advisories/GHSA-cv7m-c9jx-vg7q>
- OpenClaw CLI security audit docs: <https://docs.openclaw.ai/cli/security>
- OpenClaw security guide: <https://docs.openclaw.ai/gateway/security>
- OpenClaw audit check catalog: <https://docs.openclaw.ai/gateway/security/audit-checks>

## 建议下一步

先做一次 v1.2 小修，不需要再大改架构。修完 High 项后，规格就足够进入阶段 A/B 的代码实现。
