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

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

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

**总体状态**: 可进入实现前最后小修。  
**第三轮重点**: 复核第二轮 High/Medium findings 的关闭状态，重点检查 SSH 执行模型、fixture fallback、授权/写操作、评分边界与文档版本同步。

## 总体结论

最新规格已经基本关闭第二轮的设计问题：

- CVE-2026-25253 的 CVSS 与 patched version 已修正。
- `AgentResponse` 已补 `status_code` / `headers` / `request_url` / `error`。
- HybridJudge 已定义 hard-fail / hard-pass / inconclusive 三态。
- `outbound_request_not_to` 已绑定 network observer 与受控域名。
- SSH policy 已补 `openclaw` / `systemctl` / `launchctl` 等实际命令。
- fixture 拓扑已拆成 `same-host-loopback`、`reachable-url`、`ssh-port-forward`、`target-local-http`。
- 授权签名已改为 HMAC-SHA256。
- 评分公式已补零分母与 coverage/confidence。

剩余阻塞点集中在 SSH 执行实现细节：规格写“argv 直传 Paramiko”，但 Paramiko `SSHClient.exec_command()` 的参数是 `str` 命令；此外 `target-local-http` 需要在目标机启动临时 HTTP server，而当前 policy 只允许 SFTP 写入和少量只读命令。修正这两点后，文档可以进入阶段 A/B 实现。

## Findings 摘要

| ID | 严重度 | 位置 | 结论 |
|---|---|---|---|
| OE-AUD3-001 | High | Spec:329, 362, 465 | Paramiko 不支持 argv 直传，SSH 执行契约仍不可实现 |
| OE-AUD3-002 | High | Spec:655, 663, 456-461 | `target-local-http` 需要启动远程 HTTP server，但 policy 未授权任何启动机制 |
| OE-AUD3-003 | Medium | Spec:713, 447-454 | `native_audit_command` 仍是 shell 字符串，与结构化 argv 原则冲突 |
| OE-AUD3-004 | Medium | Spec:366-374, 467 | `allowed_paths` 混合目录前缀与 glob，但实现说明只写前缀匹配 |
| OE-AUD3-005 | Medium | Spec:457-461, 663 | SFTP 写操作 allowlist 过宽，需绑定 per-run 目录并禁止删除父目录 |
| OE-AUD3-006 | Medium | Spec:782-785 | coverage denominator 使用 `total_executed` 仍可能掩盖 skipped/not_applicable 覆盖不足 |
| OE-AUD3-007 | Low | Spec:1, 4, 6; ADR:3 | 文档版本与审计参考仍停留在 v1.1 / 第一轮审计 |
| OE-AUD3-008 | Low | Spec:466, 468 | `<allowed_path>` 占位符与 shell 元字符拒绝规则存在实现顺序歧义 |

## 详细发现

### OE-AUD3-001: Paramiko 不支持 argv 直传

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

- Spec 第 329 行说明 `ssh.py` 是 paramiko 封装。
- Spec 第 362 行要求所有命令以结构化 argv 列表传入。
- Spec 第 465 行写“命令以 `list[str]` argv 传入 `paramiko.exec_command`；不拼 shell 字符串”。

Paramiko 官方文档中 `SSHClient.exec_command(command, ...)` 的 `command` 参数类型是 `str`，不是 argv list。也就是说，规格中的“argv 直传 Paramiko”无法按字面实现。

**影响**: 如果实现者按文档写，会在类型层面失败；如果临时 `join(argv)`，又会回到 shell 注入风险。

**建议**:

将契约改为：

1. AgentSec 内部一律使用 `list[str] argv` 建模、校验、记录。
2. 执行层通过 `shlex.quote()` 对每个 token 做 POSIX shell quoting，渲染为 command string 后传给 `paramiko.exec_command()。
3. 审计日志记录原始 argv、渲染后命令的 hash、拒绝原因，不默认记录完整渲染命令中的敏感参数。
4. 明确 `get_pty=False`、不传 environment、超时强制启用。
5. 若未来要彻底避免 shell，需要换用目标侧受控 agent/helper，而不是 Paramiko `exec_command()`。

### OE-AUD3-002: `target-local-http` 缺少远程服务启动授权

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

- Spec 第 655 行说 `target-local-http` 会 SFTP 上传 fixture，并“在 target 侧启动短期 HTTP server”。
- Spec 第 456-461 行的写操作只允许 SFTP `put/mkdir/remove/rmdir`。
- 命令 policy 未允许 `python3 -m http.server`、`openclaw` 内置 fixture server、`nohup`、后台进程管理或任何等价机制。

**影响**: 该 fallback 路径无法实现。上传文件后没有 HTTP server，`{{fixture_url}}` 无法成立；若实现者自行放开 python/node 命令，会绕开当前安全模型。

**建议**:

二选一：

- 保守方案：v1 删除 `target-local-http`，server-audit active test 只支持 `ssh-port-forward`，隧道失败即 `skipped`。
- 完整方案：新增 `fixture-server` capability，必须在 consent scope 中显式授权；policy 精确允许一个受控启动方式，例如 `["openclaw", "agentsec-fixture-server", "--root", "<run_fixture_dir>", "--bind", "127.0.0.1", "--port", "<port>"]`，并定义 pidfile、健康检查、停止与清理流程。

### OE-AUD3-003: `native_audit_command` 仍是 shell 字符串

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

- Spec 第 713 行配置为 `native_audit_command: "openclaw security audit --json"`。
- Spec 第 362 行要求不接受 shell 字符串。
- Spec 第 447-454 行已经把 `openclaw` allowed argv 精确列出。

**建议**: 改为结构化配置：

```yaml
server_audit:
  native_audit_argv: ["openclaw", "security", "audit", "--json"]
```

或只允许枚举：

```yaml
server_audit:
  native_audit_mode: standard  # standard | deep
```

### OE-AUD3-004: `allowed_paths` 的 glob 与 prefix 语义混用

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

- Spec 第 366-374 行 `allowed_paths` 同时包含目录前缀和 glob，例如 `/etc/systemd/system/openclaw*.service`、`/Library/LaunchDaemons/ai.openclaw.*.plist`。
- Spec 第 467 行说路径校验使用 `realpath` 后前缀匹配。

glob 规则不能靠简单前缀匹配实现；如果把 glob 当普通前缀会匹配失败，如果放宽又容易误放。

**建议**:

将路径规则显式建模：

```yaml
allowed_paths:
  - kind: prefix
    path: ~/.openclaw/
  - kind: exact
    path: /etc/systemd/system/openclaw.service
  - kind: glob
    pattern: /Library/LaunchDaemons/ai.openclaw.*.plist
```

并规定 glob 先展开为候选路径，再对每个结果做 `realpath` 与父目录校验。

### OE-AUD3-005: SFTP 写操作应绑定 per-run 根目录

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

- Spec 第 460 行 `allowed_path_prefix: /tmp/agentsec-fixtures/`。
- Spec 第 663 行清理 `/tmp/agentsec-fixtures/{run_id}/`。

当前 policy 只限制到公共父目录，理论上可删除或覆盖其他 run 的文件，也可能误删 `/tmp/agentsec-fixtures/` 父目录。

**建议**:

运行时生成：

```yaml
write_operations:
  allowed_path_prefix: /tmp/agentsec-fixtures/{run_id}/
  forbid_remove_prefix_root: true
```

并要求 `run_id` 为 UUID，拒绝空值、`.`、`..`、斜杠和符号链接路径。

### OE-AUD3-006: Coverage denominator 仍需改为 planned/scorable

**严重度**: Medium  
**证据**: Spec 第 782 行定义 `coverage_score = (passed + failed) / total_executed`。

如果 `total_executed` 不包含加载阶段 skipped、not_applicable 或前置条件失败的用例，coverage 会被高估。例如计划 100 条，执行 20 条且全部有效，`coverage_score` 可能显示 1.0，而真实覆盖只有 0.2。

**建议**:

定义两个指标：

```text
execution_coverage = executed / planned
verdict_coverage   = (passed + failed) / planned
error_rate         = error / executed
```

报告同时展示 planned、executed、scored、skipped、error、not_applicable 的计数。

### OE-AUD3-007: 文档版本和审计引用未同步

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

- Spec 第 1/4 行仍是 v1.1，并写已吸收 OE-AUD-001 至 OE-AUD-014。
- Spec 第 6 行只引用第一轮 `audit-results.md`。
- ADR 第 3 行仍写“详见 spec v1.1”。

**建议**: 若接受本轮修改，建议把规格改为 v1.2 或 v1.1-r3，并在审计参考里加入 round-2 / round-3 文件，避免后续追溯混乱。

### OE-AUD3-008: `<allowed_path>` 占位符与元字符规则顺序需要明确

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

- Spec 第 466 行规定任何 token 含 `<` 或 `>` 即拒绝。
- Spec 第 468 行又定义 `<allowed_path>` 占位符。

这是实现顺序问题：如果先做元字符检测，模板本身会被拒绝；如果先替换占位符，再检测实际路径，则可行。

**建议**: 明确校验顺序为“先展开 policy template 占位符并绑定实际参数，再对实际 argv token 做 shell 元字符检测”。

## 第二轮 findings 关闭状态

| 第二轮 ID | 状态 | 说明 |
|---|---|---|
| OE-AUD2-001 | 关闭 | CVSS、完整 URL、去重 CVE、未定义 TI 前缀和 CI 校验已补 |
| OE-AUD2-002 | 关闭 | `AgentResponse` 已补 status/header/request_url/error |
| OE-AUD2-003 | 部分关闭 | 命令 profile 已补，但 Paramiko argv 假设和 config shell string 仍需修正 |
| OE-AUD2-004 | 关闭 | `find` 已补强 schema、路径约束、max results |
| OE-AUD2-005 | 部分关闭 | 写模式已加 consent scope/SFTP，但 target-local HTTP server 启动未授权 |
| OE-AUD2-006 | 关闭 | network observer 与 controlled domains 已补 |
| OE-AUD2-007 | 关闭 | HybridJudge 三态 hard-fail 语义已补 |
| OE-AUD2-008 | 关闭 | Redactor 默认规则显著扩展，并要求单测 |
| OE-AUD2-009 | 关闭 | 授权签名已改 HMAC-SHA256 并定义 canonicalization |
| OE-AUD2-010 | 关闭 | fixture 拓扑已拆清，127.0.0.1 语义已修正 |
| OE-AUD2-011 | 部分关闭 | 零分母和最严重失败已补；coverage denominator 仍需小修 |
| OE-AUD2-012 | 关闭 | server-audit 计数已统一为 10 个 check |

## 外部核验来源

- GitHub Advisory GHSA-g8p2-7wf7-98mq / CVE-2026-25253: <https://github.com/advisories/GHSA-g8p2-7wf7-98mq>
- OpenClaw CLI security audit docs: <https://docs.openclaw.ai/cli/security>
- OpenClaw audit check catalog: <https://docs.openclaw.ai/gateway/security/audit-checks>
- Paramiko `SSHClient.exec_command` API docs: <https://docs.paramiko.org/en/3.1/api/client.html>

## 建议下一步

不需要再大改架构。建议做一次小型 v1.2 patch：

1. 把 SSH 执行契约改为“内部 argv + validated shell rendering for Paramiko”。
2. 删除或显式授权 `target-local-http` 的远程 fixture server 启动机制。
3. 把 `native_audit_command` 改为 `native_audit_argv` 或枚举 mode。
4. 把 `allowed_paths` 改成 `prefix/exact/glob` 结构。
5. 修正 coverage 指标分母与文档版本引用。

完成后即可进入阶段 A/B 的代码实现。
