# 实测发现与修复 本插件的第一版能装上、能跑,但第一次真实使用暴露了 4 个缺陷。样本是一次授权的 DVWA 靶场 SQL 注入测试,任务最终**确实完成了**(登进 DVWA、`UNION` 注出 5 条用户凭据、 生成报告)——所以这些不是"完全不能用"的缺陷,而是**代价**问题: > 29 次工具调用里,只有 1 次用了 VulnClaw 自带的 HTTP 工具。 > 其余全靠模型手写 PowerShell 脚本发请求。 这个比例的成因值得记录:模型**知道** `http_probe_batch` 存在(推理里明确提到了), 但它需要的每一次 HTTP 调用都失败了,于是逐步退化成自己造工具。 下面每条都有对应的回归测试,跑法见 [README](../README.md#5-验证安装)。 --- ## 1. 嵌套对象 schema 被错误"关闭" ### 现象 模型两次尝试用 `fetch` 完成带会话的登录,两次被 Harness 挡回: ``` Error: invalid arguments: "cookies.PHPSESSID" is not a declared property (additionalProperties: false); "form.username" is not a declared property (additionalProperties: false); "form.password" is not a declared property (additionalProperties: false); ... ``` 另一次 `findings_report` 同样被拒: ``` Error: invalid arguments: "findings[0].title" is not a declared property (additionalProperties: false); "findings[0].severity" is not a declared property (additionalProperties: false); ... ``` ### 根因 VulnClaw 里这些字段的 schema 是「`type: object` + 描述,但 `properties` 为空」—— **靠描述说明键名的键值对袋子**: ```json "cookies": { "type": "object", "description": "Simple per-request cookies." } ``` 插件当时把「源里没有 `additionalProperties`」一律写成 `additionalProperties: false`, 于是这些袋子编译成**拒绝一切键**的空对象。受影响的共 24 处,包括: | 工具 | 字段 | |---|---| | `fetch` | `headers` / `params` / `cookies` / `form` | | `http_probe_batch` | `requests[].params` / `.headers` / `.cookies` | | `findings_report` / `attack_map` / `compliance_map` / `remediation_advice` | `findings[]` | | `findings_diff` | `baseline[]` / `current[]` | | `brute_force_login` | `extra_data` | | `traffic_repeat` | `headers` | 后果是决定性的:它切断了"用 `fetch` 完成带会话的登录"这条路, 把模型推向了 PowerShell。 ### 修复 `encodeToolParameters()` 中: - **没有声明 `properties` 的对象 → 开放**(`additionalProperties: true`); - **显式写了 `false` 且*有*声明属性的对象 → 仍然闭合**(作者在表达意图)。 ### 验证 `dev-regression.mjs` 用会话里**真实发过的参数**逐条验证,并对照确认约束没有被放宽: ``` ok fetch accepts cookies + form (session seq 44/64 shape) ok findings_report accepts structured findings (session seq 176 shape) ok attack_map accepts structured findings ok http_probe_batch accepts headers/cookies/params bags ok fetch still requires url ok an explicitly closed object with declared properties stays closed ok a declared property still rejects a wrong type ok a required nested property is still enforced ``` 顺带记录一个 Harness 自身的契约:**工具参数的根对象**不携带 `additionalProperties`, 未知的根级键会被接受。这不是本插件能或应该约束的,测试中把它固定为已知行为。 --- ## 2. `python_execute` 缺少它自己承诺的库 ### 现象 模型的第一个 `python_execute` 就失败: ``` [+] Python execution result (trusted-local): [stderr] Traceback (most recent call last): File "C:\...\tmpa0sq6x0l.py", line 15, in import requests, re ModuleNotFoundError: No module named 'requests' ``` 模型不信,又单独测了一次: ```python print("hello") try: import requests print("requests version:", requests.__version__) except Exception as e: print("import failed:", type(e).__name__, e) ``` 结果 `import failed: ModuleNotFoundError No module named 'requests'`。 ### 根因 VulnClaw 的 `python_execute` 工具描述写着: > 预装库:requests, beautifulsoup4, pycryptodome, base64, json, re 等 但 `requests` **不在** `vulnclaw` 的依赖列表里(它用 `httpx`)。 `beautifulsoup4` / `lxml` / `pycryptodome` 在,`requests` 不在。 更糟的是没有优雅的绕过方式:`__import__()` 和 `importlib.import_module()` 会被 AST 检查拦下(`lab mode blocked operation: ast:...`),只有普通 `import` 语句可用。 于是模型花了 5 次调用确认"Python 里做不了 HTTP",然后放弃 Python。 ### 修复 `python-runtime.js` 在解析出解释器后检查并补装承诺的执行库 (`ensureExecutionLibraries()`,每次进程只检查一次)。 理由:**工具描述是模型推理所依据的契约的一部分**,环境应当去符合它, 而不是留一个"文档说有、实际没有"的能力。补装失败只记录不致命。 ### 验证 `dev-deps-repair.mjs` 验证检测不产生假阳性,以及安装路径确实能让目标库可导入。 --- ## 3. 会话级工具在首次调用时崩溃 ### 现象 `vulnclaw_set_target` 是模型的**第一个动作**,直接失败: ``` Error: Cannot read properties of undefined (reading 'invoke') ``` `vulnclaw_report`(模型最终调用了 3 次)同样失败——整个会话**没有任何报告产出**。 ### 根因 `ensure()` 返回的是发现结果 `{info, tools}`,而会话级工具却去解构 `{ client }`: ```js // tools.js async run(args) { const { client } = await ensure(); // ← undefined await client.invoke("set_context", ...); } ``` 因为外层有一个恰好叫 `client` 的变量,这个错误在"注册流程先跑过一遍"的路径上不会暴露, 只在**会话级工具是第一个被调用的工具**时出现——而那正是真实使用中的顺序。 我当时的测试先调了动态工具,所以没抓到。 ### 修复 `ensure()` 统一返回 `{client, info, tools}`,所有调用方使用返回值。 约定:**任何工具都必须能被单独调用,不依赖调用顺序。** ### 验证 `dev-verify-activation.mjs` 专门按"注册完成后第一个动作就是 `vulnclaw_set_target`"的顺序回归, 并同时验证 `vulnclaw_report` 走通同一条处理路径。 --- ## 4. 工具目录里 55% 与渗透无关 ### 现象 模型实际收到 **111** 个工具,其中 **56** 个是 `cua_driver_native__*`: 点击窗口、读剪贴板、启动应用、录制屏幕、移动光标…… 对 Web 目标渗透毫无用处,却占了超过一半的目录——注意力和 prompt 预算花在了用不上的能力上。 ### 修复 预设默认 deny 掉这一族(`keepDefaultDeny: true`,可用 `denyTools` 增补)。 **为什么用 deny 而不是 allow**:Harness 的 `tools.restrict()` 会拒绝 "指名了本部署没有的工具"的限制,而动态注册的 VulnClaw 工具在预设激活时还不存在—— allow 列表无法枚举它们。deny 列表只需列举必然存在的工具。 调用还包在 try/catch 里:裁剪失败只导致目录不干净,绝不让模式坏掉。 ### 验证 `dev-verify-activation.mjs` 检查 deny 列表被应用、非平凡、且**不包含任何一个渗透工具**。 --- ## 5. 人设补强 工具可用不等于模型会用。上述修复让正确路径**可行**,人设让模型**知道**该走哪条。 新增的**环境契约**(`cordis.patch.yml` 里的 persona prefix): | 规则 | 针对的问题 | |---|---| | HTTP 一律走 `fetch` / `http_probe_batch`;**明确禁止**用 `pwsh`/`shell_command` 手写 HTTP 客户端或为此写脚本文件 | 会话里的主要退化路径 | | 多个变体一起比较时用 `http_probe_batch`,不要连着发多次 `fetch` | 同上 | | `fetch` 不会跨调用保持 Cookie,每次要显式带 `cookies` | 会话里模型在这个点上反复试探 | | `python_execute` 的能力边界;**禁止** `__import__()` / `importlib` / `exec()` / `getattr()` 动态导入 | `lab mode blocked operation` | | 报错先完整读错误再决定下一步,不要靠换工具重写一遍来绕过 | 模型在 `ModuleNotFoundError` 上浪费了 5 次调用 | | 预期与实际不符(如用户给的凭据无效)时先做对照实验,再明确告知改用了什么 | 会话里用户给的密码 `123456` 无效,模型改用了 DVWA 默认凭据——做法正确,但应明确告知 | 最后一条尤其值得保留:模型当时**做对了**(做对照实验确认是凭据问题,改用 `admin/password` 并成功登录),但这类偏离用户指令的决策必须让用户看见。 --- ## 修复后的状态 | 套件 | 覆盖 | 结果 | |---|---|---| | `dev-verify-activation.mjs` | 导出形态 / 工具注册 / 目录裁剪 / 首调回归 | 31/31 | | `dev-regression.mjs` | 上述缺陷 1、3 | 9/9 | | `dev-verify-schema.mjs` | 37 个 schema 过 Harness 真实编译器 + 必填一致性 | 37/37 | | `dev-test.mjs` | 桥接客户端与工具面冒烟 | 25/25 | 修复后在同样的 Harness 与 VulnClaw 版本上复测:**全部工具正常调用**。