研发协作
审计原生 C/C++ 用户态代码(内存破坏、整数溢出、竞态…
当你需要审计原生 C/C++ 用户态程序(Linux/macOS 守护进程、Windows 服务)的内存安全、整数溢出、竞态或类型混淆问题时,用一次 Workflow 调用编排 4–14 个审查 agent,并如实报告覆盖缺口与未裁决的严重级别。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
审计原生 C/C++ 用户态代码(内存破特定
解决思路
- 解析调用行上的自由文本(如 `flamenco only`、`high severity only`、`use haiku`),据此预填它能推出的参数。
- 对仍然无法确定的参数发起**一次** `AskUserQuestion` 调用;绝不对必填参数静默取默认值。
- 确定四个参数:`threat_model`(`REMOTE` / `LOCAL_UNPRIVILEGED` / `BOTH`:出现 "remote"/"network"/"attacker" → `REMOTE`;"local"/"unprivileged" → `LOCAL_UNPRIVILEGED`;否则询问)、`worker_model`(`haiku` / `sonnet` / `opus` / `inherit`,只有显式给出模型名才推断,否则询问;`inherit` 表示沿用会话模型)、`severity_filter`(`all` / `medium` / `high`:"all"/"every"/"noisy" → `all`;"medium and above" → `medium`;"high only" → `high`;否则询问)、`scope_subpath`(仓库相对目录,可选;"X only"、"just audit X/" → 对应子目录,与顶层目录做模糊匹配;缺省为 `.`;有歧义就询问)。
- 整轮运行中始终保持两个 scope 相互独立:`finding_scope_root` = `scope_subpath`(默认 `.`),findings 必须落在它内部,且单元列表由这棵树生成;`context_roots` = `.`,可自由读取以确认调用方、编译开关和可达性。只有当用户明确禁止更大范围读取时才把 `context_roots` 收窄到 `finding_scope_root`,并说明这样做会降低可达性置信度。
- 用给出的 bash 片段解析插件根路径:先取 `${CLAUDE_PLUGIN_ROOT:-}`,若为空或 `$root/workflows/c-review.js` 不存在,则回退到 `find "$HOME/.claude" -path '*/c-review/workflows/c-review.js' -print -quit`。回退搜索**只用 `~/.claude`,绝不用 `.`**——`.` 是被审计的仓库,若某个 vendored/镜像的目录树抢先命中,就会运行它自己那份脚本。让 find 的 stderr 正常输出,缺失 `~/.claude` 是要上报的真实失败,不是噪声。
- 两条路径都解析不出来时直接停止,不要带空路径继续运行;并且要说明最终解析到的是哪个路径,这样在八个 agent 跑起来之前就能看出用的不是已安装插件的那份拷贝。
- 创建带 UTC 时间戳的输出目录(Workflow 脚本不能调用 `Date.now()`,时间戳只能在这里生成):`output_dir="$(pwd)/.c-review-results/$(date -u +%Y%m%dT%H%M%SZ)"`,然后 `mkdir -p "$output_dir"; echo "$output_dir"`。
- 解析 scope 的绝对路径并**同时传递两种写法**:`scope_abs="$(cd "${scope_subpath:-.}" && pwd)"`(失败则输出 `scope_subpath does not exist`)。原因:Workflow 脚本没有文件系统 API,而 `assemble_findings.py` 是按**它自己的 cwd** 解析 `--scope` 的;只传一种写法会导致 workflow 把 finding 路径里的 `src/` 剥掉、而 assembler 剥掉 `/repo/src/`,两边对重复 finding 的判断就会不一致。
- 确认 `uv` 在 PATH 上(Detect 阶段要跑单元枚举器,Assemble 阶段要跑 `assemble_findings.py`)。`uv` 缺失就说明并停止——整个审查的分片都是基于那份单元列表切出来的。
- 明确:调用本技能**本身**就是多 agent 编排的授权,直接调用 `Workflow`,不要再问一次。真实代码库的审查本来就会超过默认的 workflow 规模指引,那个指引是建议性的、这里正是它豁免的场景;不要为了迁就它而缩小 fan-out,也不要用手工 spawn 的 `Agent` 调用来替代。
- 只发**一次** `Workflow` 调用:`scriptPath` 用 Phase 1 解析出的绝对路径,`args` 必须是真正的 JSON 对象,不能是 JSON 编码后的字符串。
- 传入必填 args:`outputDir`、`pluginRoot`、`threatModel`、`severityFilter`、`findingScopeRoot`、`findingScopeRootAbs`、`contextRoots`、`workerModel`(例如 `findingScopeRoot: "expat/lib"`、`findingScopeRootAbs: "/abs/path/to/repo/expat/lib"`、`contextRoots: "."`、`workerModel: "sonnet"`)。
- `findingScopeRootAbs` 实际上不是可选项:省略后 workflow 会告诉 assembler 不知道绝对根路径,于是同一个 bug 以 `/repo/expat/lib/xmlparse.c` 归档和以 `xmlparse.c` 归档就再也合并不了。
- 另有六个可选参数,省略时各取默认值,但**类型传错**时 workflow 会带字段名抛错而不是回退默认。只在用户明确要求或做评测时才传:`maxUnitLines`(默认 `150`,审查单元的行数上限,超长函数会按语法接缝切开;调高会重新引入该上限本要避免的饱和问题)、`linesPerAgent`(默认 `1500`,每个审查 agent 负责的源码行数;**在小代码树上是个 no-op**——`--agent-min`(默认 4)会给推导出的数量兜底,所以两个差异很大的取值可能产生完全相同的分工,想要钉死 fan-out 请用 `reviewAgents`)、`reviewAgents`(默认推导,用于钉死审查 fan-out;同样受那个下限约束:推导值和显式值都被夹在 4–14,显式传大于 14 会把上限抬到该值本身,小于 4 会被抬到 4,尾部过小不值得单独派 agent 的切片会并入相邻切片,所以最终数量可能比要求的少 1)、`invariantAudit`(默认 `false`,为 sweep 增加共享状态不变量审计,会多一整个 agent,状态机密集的目标才开)、`exclude`(默认 `[]`,glob 或子串数组,单元枚举器会跳过它们,每个元素变成一次 `--exclude`;当枚举因某个它无法接管的路径而中止时使用——比如解析到 scope 根之外的符号链接、不可读目录——被排除的路径会以覆盖缺口的形式出现在枚举总数里,而不是被静默吞掉)、`benchmarkMode`(默认 `false`,**仅评测用**,会给 reviewer prompt 加外部来源声明和两个 schema 字段,不改变任何 finding,真实审计请保持关闭)。
- 依赖 workflow 自身的参数校验:缺字段时会带字段名抛错。它内部跑五个阶段:Detect(1 个 agent,运行 `enumerate_units.py` 得到单元列表、**依据实际 API 使用**判定平台标志、找出共享状态结构体、按 bug 类别判断是否存在候选点);Review(4–14 个 agent,每个负责单元列表的一段连续切片,返回**带 severity 的 findings** 以及每个 (unit × question) 一行台账);Sweep(0–2 个 agent,按类别轴:对每个在任何地方都没有条目、且 Detect 未排除的 bug 类别派一个 agent,一个都没有就整个阶段跳过;外加 `invariantAudit: true` 时的结构体字段审计);Dedup(0–1 个 agent,只处理 assembler 无法确定性合并的冲突,通常跳过);Assemble(1 个 agent,运行 `assemble_findings.py`:台账门禁、确定性合并、`findings.json`、`REPORT.md`、`REPORT.sarif`)。
- 中等规模目标预期**约 8–10 个 agent**。
- `Read <output_dir>/REPORT.md` 并返回它。
- 在 findings 旁边**明确说一次**:没有跑过误报复核(no false-positive review ran)。每个 severity 都是 reviewer 自己的判断(`severity_source: "reviewer"`),`judgeRan` 恒为 `false`,没有任何东西拒绝过任何条目。要让用户预期其中一部分是错的或超出范围,而不是把这份列表当成已裁决结果;也不要自己去过滤。
- 把 workflow 结果里任何意味着本次运行是**部分运行**的东西,与 findings 分开、醒目地呈现出来:`artifactsWritten: false`(`REPORT.md` 和 `REPORT.sarif` 缺失,`parts/` 下的分部文件完好,手工重新 assemble——绝不从工具结果里重建);`artifactsWritten: null`(assemble agent 什么都没返回,产物是否存在**未知**——命令可能已跑完只是结构化回答失败,动手前先列一下输出目录);`gateAccepted: false` 但产物已写出(一种看起来像成功的失败:产物完整,但**覆盖度门禁**没能运行或拒绝了台账,于是这份报告是「已组装但未验证」;**不要**重跑 assembler,去读 `ledger-gate.json` 并报告缺口,`artifactError` 一定会带原因);`coverage: null`(覆盖度是**未测量**而非完整,绝不能把这样的运行报告为全覆盖,指向 `ledger-gate.json`);`coverage`(报告 `checksSatisfied` / `checksRequired`,`checksCompleted` 只能说成「已作答」而不能说成「已审查的函数」;`checksSatisfied` 低于 `checksCompleted` 说明门禁丢弃了行,属于覆盖度**完整性**失败,要指名 `violations`);`groupsFailed`、`agentFailures`、`notes`(这些地带**未被覆盖**,不要让一份干净的报告暗示它们被覆盖了);`unrecognisedParts`(整个 agent 的输出没有进入任何产物;`null` 意味着**未检查**,不是「没有」);`silentClasses` / `ruledOutClasses` / `platformDroppedClasses`(分别是「扫过了、没发现」/「什么都没看」/「按配置超出范围」,要分开报告——只有中间那个意味着需要人工去看)。
- 当 `artifactsWritten` 为 false 时手工重新组装:`uv run --no-project <plugin_root>/scripts/assemble_findings.py --run-dir <output_dir> --threat-model <MODEL> --severity-filter <FILTER> --no-judge --scope <finding_scope_root> --context-roots <context_roots> --worker-model <worker_model> --expect <part-stem>=<finding-count>`(workflow 日志里点名的每个 part 各一个 `--expect`)。
- **`--expect` 不是可选的。** 没有它,allowlist 会放进 `parts/` 下的每一个文件,包括没人派发过的那个,运行仅因这一条就以 **1** 退出。如果无法从 workflow 日志里恢复计数,那么 exit 1 才是正确结果,这次运行就是「已组装但未验证」。
- **绝不能丢掉 `--no-judge`。** 去掉它会把每个 reviewer 的 severity 覆盖成 MEDIUM,并为一个没有任何 judge 看过的运行记录 `judge_ran: true`。
- 有六个 flag 完全无法重建(`--expect-complete`、`--benchmark-mode`、`--groups-attempted`、`--groups-failed`、`--agent-failure`、`--external-source`),所以文档会在一次可能丢过切片的运行上写着 `agent_failures: []`——要把这一点报告出来。没有 `units.json` 时 assembler 同样以 **1** 退出:门禁根本没跑。
- 最后列出产物:`findings.json`、`REPORT.md`、`REPORT.sarif`、`units.json`、`ledger-gate.json`、`detect.json`,以及 `parts/` 和 `assignments/` 目录。
- 拒绝以下合理化说辞:「运行大体上是成功的,那我就直接把报告呈现出来」——失败的 agent 是未覆盖的地带,不是舍入误差,要放在 findings 旁边报告;「覆盖度 80%,基本算完整了」——缺的那 20% 是 `ledger-gate.json` 里一份精确的 (unit, question) 对清单,要点名;「我自己写 findings 而不跑 workflow」——手工编排代价高得多且召回更差,永远调用 `Workflow`;「产物失败了,那我就从工具结果重建报告」——分部文件就在磁盘上,assembler 是确定性的,重跑它;「零 findings,那就没什么可报告的」——零 findings 的运行同样产出两份产物,而且对真实 C 代码零 findings 本身就值得明说;「workflow 返回了 findings,我可以跳过读 REPORT.md」——工具结果有截断且只带计数、不带 findings,报告才是产物;「没有 judge 跑过,那我就自己过滤 findings」——不行,静默丢弃 findings 复刻了 judge 的成本却没有它的严谨性,还会让你说的和产物对不上,应当照原样报告 pipeline 的产出并标注为未裁决;「没有 judge 跑过,那我就把 severity 当成权威」——也不行,那只是一个 reviewer 的意见;「按类别切分的 fan-out 会找到更多」——按位置分片是刻意设计的,类别目录只是其上的一次有界完备性扫描,不要额外加一个。
适用边界 · 注意事项
- 不适用于内核驱动或内核模块。
- 不适用于托管语言(Java、C#、Python、Go、Rust)。
- 不适用于没有 libc 的嵌入式或裸机代码。