自动Java代码审计SKILL设计

面向Java/JVM仓库的静态白盒语义审计SKILL。核心不是“扫描危险API”,而是把完整审计拆成可寻址、可恢复、可并行、可复核的状态机,最后生成可继续交给动态验证流程消费的机器可读报告。

通用SKILL组织原则参考Agent SKILL设计

当前版本:1.1.4

定位

这个SKILL主要处理:

  • Java/Kotlin/JVM仓库;
  • 静态白盒代码审计;
  • Source → Flow → Sink数据流;
  • 认证、授权、租户和所有权边界;
  • 跨接口、持久化和异步二阶段数据流;
  • Maven/Gradle依赖漏洞;
  • 可供后续动态验证消费的白盒漏洞报告。

明确不处理:

  • 启动应用;
  • 发送真实请求;
  • 运行测试或PoC;
  • 修改业务代码;
  • 自动修复漏洞;
  • 非JVM项目。

这里需要始终保持一个边界:

审计结论来自源码语义和配置证据,而不是运行结果。

构建可以执行,但只能用于辅助解析依赖、Source Set、符号和编译错误,不能把“编译成功”当成安全结论。

核心设计

这个SKILL可以拆成四层:

flowchart TD
 A[控制层<br/>SKILL.md] --> B[状态层<br/>.ai-audit]
 A --> C[分析契约层<br/>references/skills]
 A --> D[漏洞知识层<br/>references/vulnerables]

 B --> E[Inventory]
 E --> F[入口Analysis Units]
 F --> G[Cross Flow]
 G --> H[Dependency Research]
 H --> I[Candidate Adjudication]
 I --> J[Independent Review]
 J --> K[Coverage]
 K --> L[Report]

 C --> E
 C --> F
 C --> I
 C --> L

 D --> F
 D --> G
 D --> I

各层职责:

内容定位
控制层SKILL.md阶段、门禁、调度和硬约束
状态层.ai-audit/**Canonical状态、队列、事实和恢复点
分析契约层references/skills/**Inventory、Evidence、Lane、构建等结构化协议
漏洞知识层references/vulnerables/**按当前入口信号渐进加载的漏洞类型知识
报告契约assets/WHITEBOX_VULNERABILITY_REPORT_TEMPLATE.md最终机器可读输出格式

这和普通“一个SKILL.md把所有内容塞进去”的方式不同,实际采用的是Agent SKILL设计#正文设计里的渐进式正文思路:主文件只控制流程,具体知识按阶段和信号加载。

为什么设计成状态机

设计成状态机,是因为真正的问题并不是一次推理能不能找出漏洞,而是:

  • 仓库可能很大;
  • 一个入口可能跨十几个文件;
  • 多入口需要并行;
  • 同一个Sink可能被多个入口调用;
  • 候选需要第二次独立证伪;
  • 依赖研究需要联网;
  • 会话可能中断;
  • 上下文可能压缩;
  • 最终报告必须完整且可解析。

因此不能依赖“当前对话里还记得做到哪里”来维持审计状态。

完整执行链:

flowchart LR
 G0[G0 预检] --> G1[G1 Inventory]
 G1 --> B[1B 隔离构建]
 B --> G2[G2 逐入口深挖]
 G2 --> G3[G3 跨入口分析]
 G3 --> G4[G4 依赖研究]
 G4 --> G5[G5 候选裁决与复核]
 G5 --> G6[G6 报告]
 G6 --> DONE[completed]

state.json.status也是严格阶段状态,不允许随意跳跃。

状态对应阶段
initialized工作区建立
inventory建立清单
building隔离构建和依赖解析
analyzing单入口深挖
cross_flow跨入口分析
dependencies依赖漏洞研究
adjudicating候选裁决和复核
reporting报告生成
completed所有门禁通过
blocked存在真实静态证据阻断

这里的blocked不是“执行不下去了”,而是静态输入和允许工具确实无法补足必要证据

完整性优先

模型资源明确排除出审计状态:

  • Token;
  • Context长度;
  • 推理预算;
  • 调用费用;
  • 轮次;
  • 主观“快没时间了”。

这些都不能成为:

  • 缩小Scope;
  • 跳过入口;
  • 跳过漏洞类型;
  • 跳过复核;
  • blocked
  • blind_spot
  • unreviewed
  • 提前报告。

这个取舍很重要,审计完整性不能随着当前会话资源变化。

状态持久化的目的不是“少分析”,而是让分析可以无限分段:

flowchart LR
 A[完成一个独立单元] --> B[立即落盘]
 B --> C[重新读取校验]
 C --> D[更新Canonical State]
 D --> E[处理下一个单元]

以后维护时继续保持“完成一个独立单元就立即落盘”的方式,不要退化成“先扫描完整仓库,最后一次性整理”。

仓库结构

路径作用
SKILL.md主状态机和阶段门禁
agents/openai.yaml展示名称、描述和默认Prompt
assets/WHITEBOX_VULNERABILITY_REPORT_TEMPLATE.md最终报告机器契约
references/skills/context-schema.md.ai-audit持久化状态模型
references/skills/java-inventory.mdJava/JVM Inventory规则
references/skills/build-and-dependency-sources.md隔离构建和第三方源码扩展
references/skills/pi-subagents.mdPi子Agent发现、fanout和回退
references/skills/route-lane-contract.md单入口子Agent输出契约
references/skills/evidence-gates.md候选证据和独立复核门禁
references/skills/vulnerability-index.md信号到漏洞Reference的路由
references/vulnerables/*.md每种漏洞的语义审计知识

.ai-audit是Canonical Context

.ai-audit/ 才是真正的审计上下文,不依赖聊天记录。这个目录同时承担:

  • Snapshot身份;
  • 审计Scope;
  • Pending Queue;
  • 已完成Unit;
  • Candidate;
  • Review;
  • Dependency Findings;
  • Cross Flow;
  • Coverage;
  • Report Draft;
  • Final Report。

主要文件:

路径Source of Truth
manifest.json本轮审计身份、Scope、Snapshot、白名单
state.json状态机、门禁、队列、执行模式
inventory/project.json项目技术栈
inventory/routes.jsonlHTTP入口
inventory/entrypoints.jsonl非HTTP入口
inventory/dependencies.jsonl依赖清单
inventory/sinks.jsonl危险Sink信号
inventory/security-controls.json安全控制
analysis/units/*.json每个入口的Canonical分析
analysis/candidates/*.json候选漏洞状态
analysis/reviews/*.json独立证伪复核
analysis/cross-flows.jsonl二阶段/跨入口数据流
analysis/dependency-findings.jsonl依赖漏洞研究
coverage.json总量、Reviewed、Blocked、Gap
report-draft.mdG6前的报告草稿
*_WHITEBOX_VULNERABILITY_REPORT.md最终报告

从维护角度看,.ai-audit/ 实际上就是一个小型审计数据库。

Canonical Writer

并行部分只保留一个Canonical Writer:只有主Agent可以修改Canonical State。

flowchart TD
 A[Route Lane A] --> X[subagents/lane-a.json]
 B[Route Lane B] --> Y[subagents/lane-b.json]
 C[Review Lane] --> Z[subagents/review.json]

 X --> P[主Agent验收]
 Y --> P
 Z --> P

 P --> U[analysis/units]
 P --> V[analysis/candidates]
 P --> W[analysis/reviews]
 P --> S[state.json]

子Agent只负责把独立Artifact写到 .ai-audit/subagents/<lane-id>.json,不能:

  • 修改state.json
  • 修改Canonical Unit;
  • 修改Coverage;
  • 直接生成最终Finding;
  • 修改源码。

这样避免多个Agent同时更新状态造成Lost Update和结论分叉。

写入顺序

Canonical Context有明确的写入顺序:

flowchart TD
 A[写Inventory条目] --> B[重读校验]
 B --> C[更新State Queue]

 D[写Unit] --> E[写Candidate]
 E --> F[更新State]

 G[写Review] --> H[更新Candidate disposition]

 I[Lane Artifact] --> J[主Agent复读源码验收]
 J --> K[写Canonical Result]

 L[写report-draft] --> M[G6]
 M --> N[写Final Report]
 N --> O[state=completed]

这个顺序相当于简单的Write-Ahead/Checkpoint约束。

以后重构时不要把“写结果”和“更新队列”顺序反过来,否则中断恢复会产生已经从Pending删除、但实际Artifact不存在的状态。

Snapshot和恢复

恢复时先检查:

  • state.json
  • manifest.json
  • 当前仓库Root;
  • Scope;
  • Snapshot Fingerprint。

恢复原则:

flowchart TD
 A[发现已有.ai-audit] --> B[读取Manifest和State]
 B --> C{Snapshot一致?}
 C -->|是| D[恢复Pending Queue]
 C -->|否| E[创建新run_id或询问重新审计]

不能把旧Snapshot的分析无提示混进新代码。

如果一个Unit状态为running,但没有可解析结果,恢复时重新放回pending

聊天上下文只是一层临时缓存,不能替代这些文件。

写入边界

SKILL只允许写入 .ai-audit/**,同时硬排除下面这些路径段:

路径段原因
.ideaIDE Metadata
.work工作上下文
.ai-audit本SKILL自身产物
.auto-verification后续动态验证产物

这四个路径不只是“不分析”,而是必须在:

  • 文件枚举前排除;
  • 搜索前排除;
  • Route统计前排除;
  • Dependency统计前排除;
  • Coverage分母前排除。

这里强调的是过滤必须发生在Inventory之前,不能先扫描再从结果里删,否则已经污染了ID、统计和索引。

大文件协议

Pi内部文件工具在大于 51,200 bytes 时存在截断风险,因此大文件读取本身设计成可恢复任务。

规则:

项目约束
大文件阈值51,200 bytes
单块最大读取32KB
状态目录.ai-audit/chunks/
完成条件从起点连续覆盖到EOF
重叠块必须确认内容一致
截断输出继续读取,不能依据残缺尾部推理

过程:

flowchart TD
 A[读取前取得Size] --> B{> 50KB?}
 B -->|否| C[普通读取]
 B -->|是| D[分块读取 <= 32KB]
 D --> E[记录Range + SHA256]
 E --> F{覆盖连续到EOF?}
 F -->|否| D
 F -->|是| G[complete=true]
 G --> H[允许基于完整内容分析]

这个机制同样应用于:

  • 源码;
  • 子Agent输出;
  • 构建日志;
  • 最终报告。

阶段0:预检

阶段0只解决一个问题:这次审计到底是什么。

要确定:

  • 是否JVM项目;
  • Repository Root;
  • Snapshot;
  • 用户Scope;
  • Module;
  • Excluded;
  • Write Allowlist;
  • 子Agent能力;
  • Execution Mode。

这一阶段的门禁是 G0,核心不是做漏洞判断,而是把审计身份、Scope、Snapshot和执行能力确定下来。

子Agent能力发现

Pi环境下不能通过:

  • settings;
  • package目录;
  • slash command存在;

来猜测子Agent可用。

必须检查当前实际工具:

  1. 精确工具名subagent
  2. action:list
  3. 可执行Agent;
  4. Schema支持时action:doctor
  5. subagent_wait或等价通知。

模式:

条件Mode
subagent可用且存在合适Reviewerpi-subagents
一开始就不可用sequential
运行中并行基础设施失效degraded-sequential

能力发现属于G0硬门禁,不是可有可无的性能优化开关。

阶段1:Inventory

Inventory阶段不急着找漏洞,而是先建立一个可寻址的审计空间

flowchart TD
 A[Repository Scope] --> B[Module]
 A --> C[HTTP Routes]
 A --> D[Non-HTTP Entry Points]
 A --> E[Security Controls]
 A --> F[Dangerous Sinks]
 A --> G[Dependencies]

 C --> H[Stable IDs]
 D --> H
 F --> H
 G --> H

这是整个设计里非常关键的一层。

如果直接“搜索危险API然后分析”,会天然漏掉:

  • 没有显眼Sink的授权漏洞;
  • 业务逻辑漏洞;
  • 非HTTP入口;
  • 二阶段漏洞;
  • 没命中关键字的框架控制;
  • 被Wrapper包起来的Sink。

所以先把Repository转成Normalized Inventory,再开始做漏洞判定。

Java/JVM Inventory

技术栈

需要记录:

  • JDK;
  • Spring/Jakarta/Servlet/JAX-RS;
  • Struts/Vert.x/Javalin等Web Framework;
  • ORM;
  • Template Engine;
  • Database;
  • Message Queue;
  • RPC;
  • Serializer;
  • Cloud SDK。

HTTP入口

要合并真实路径:

  • Class Level Route;
  • Method Level Route;
  • HTTP Method;
  • Consume/Produce;
  • Handler;
  • Parameter Source;
  • Filter/Interceptor;
  • Security Annotation。

Spring路径必须处理类级和方法级组合,不能只搜索@GetMapping字符串。

非HTTP入口

同等重要:

  • @Scheduled
  • Quartz;
  • Spring Batch;
  • CLI;
  • CommandLineRunner
  • Kafka/JMS/Rabbit;
  • WebSocket;
  • GraphQL;
  • gRPC;
  • File Watcher;
  • JMX/RMI;
  • 生命周期Hook;
  • 反序列化Callback。

“不是HTTP”不代表不属于Attack Surface。

Security Control

Inventory阶段就建立:

  • SecurityFilterChain
  • Matcher顺序;
  • Method Security启用状态;
  • @PreAuthorize等方法注解;
  • Principal来源;
  • JWT/Session/API Key;
  • Tenant/Owner绑定;
  • CSRF;
  • CORS;
  • Global Exception;
  • Validation。

不能把 @PreAuthorize 本身直接当Guard,需要后续确认方法安全是否真的启用、代理边界是否有效。

Sink

参数语义记录,而不是只按方法名:

  • SQL/NoSQL/LDAP;
  • Expression/Template;
  • Process;
  • File/Path;
  • Archive;
  • HTTP Client;
  • XML;
  • Deserialize;
  • Reflection/ClassLoader;
  • HTML/Header/Redirect;
  • Log;
  • Cache;
  • Message;
  • Business State;
  • Crypto/Secret。

Inventory里的Sink只是一种 signal,不是Finding。

Stable ID

所有入口和关键对象都要有稳定ID,例如:

  • R-001
  • E-001
  • D-001
  • S-001
  • C-001

这是后续实现:

  • FIFO Queue;
  • 子Agent所有权;
  • Cross Flow引用;
  • Candidate关联;
  • Coverage集合;
  • Resume;

的基础。

没有Stable ID,多阶段流程很容易退化成自然语言待办,所以这层不能省。

G1

阶段1完成条件:

  • 每个HTTP Route有唯一ID;
  • 每个非HTTP Entry有唯一ID;
  • Dependency版本有证据来源;
  • Security Config进入Inventory;
  • 无法覆盖模块进入Gap。

到这里仍然不能把搜索命中直接写成漏洞

阶段1B:隔离构建

构建只作为补全静态解析的手段。

flowchart LR
 A[Repository Scope] --> B[复制必要文件]
 B --> C[.ai-audit/build-workspace]
 C --> D[Maven / Gradle]
 D --> E[Resolved Dependencies]
 D --> F[Source Set]
 D --> G[Compiler Diagnostics]
 E --> H[补强Inventory]
 F --> H
 G --> H

构建禁止在原仓库直接执行会产生文件的Task。

构建约束

允许的方向:

  • Dependency Resolution;
  • Compile;
  • Classes;
  • Assemble;
  • Skip Test。

禁止:

  • Test;
  • Integration Test;
  • Run;
  • BootRun;
  • Deploy;
  • Publish;
  • Migration。

构建插件本身可能执行代码,所以隔离Workspace不是单纯为了“保持Git干净”,也是执行边界。

Retry

首次失败后只允许基于明确错误做一次针对性重试。

不能为了让构建成功去修改项目文件。

这避免审计过程演化成“修复构建环境”。

第三方源码扩展

第三方依赖源码不放进默认Scope。

先建立候选:

flowchart TD
 A[Resolved Coordinate] --> B[实际import / Reflection / SPI]
 B --> C[下载Source JAR]
 C --> D[验证版本 + SHA256 + Package Namespace]
 D --> E[形成External Scope候选]
 E --> F{用户批准?}
 F -->|否| G[只做版本和公告研究]
 F -->|是| H[加入external_scope]
 H --> I[按同样门禁审计依赖源码]

关键设计:

下载源码 ≠ 获得审计授权。

即使Source JAR已经位于.ai-audit/dependency-sources/,在用户明确同意前也只能验证:

  • Coordinate;
  • Version;
  • Hash;
  • Package Match。

不能分析实现。

阶段2:逐入口深挖

入口作为最小并行分析单元。

一个Lane只拥有一个Route或一个Non-HTTP Entry,不要为了减少调度把多个入口塞进同一个Child。

单入口分析:

flowchart TD
 A[Entry] --> B[Parameter / DTO]
 B --> C[Authentication]
 C --> D[Authorization]
 D --> E[Service]
 E --> F[Repository / Client / Template / File]
 F --> G[Sink / Decision / Safe Return]

 B --> H[Source]
 H --> I[Field-level Flow]
 I --> G

 G --> J[Reverse Caller Search]
 D --> K[Counterevidence Search]
 J --> L[Unit Result]
 K --> L

每个入口必须覆盖六个维度:

维度必须有结论
Authentication
Authorization
Input
Data Flow
Output
Side Effects

即使没有漏洞,也必须有:

  • 搜过什么;
  • 读过什么;
  • 排除了什么;
  • 为什么排除。

Source → Flow → Sink

普通漏洞采用:

flowchart LR
 A[具体Source字段] --> B[Field-level Flow]
 B --> C[Transformation]
 C --> D[Guard / Barrier]
 D --> E[Sink]

像“用户输入最终进入SQL”这种描述不够,需要明确:

  • 哪个参数;
  • 哪个DTO字段;
  • 哪个方法;
  • 哪次转换;
  • 哪个Sink参数。

授权类漏洞则替换为:

flowchart LR
 A[Principal] --> B[Role / Tenant / Subject]
 B --> C[Resource]
 C --> D[Action]
 D --> E[Authorization Decision]

没有传统Sink也可以形成Finding。

正向和反向同时追踪

不能只从Controller一路向下看。

阶段2要求同时:

  • 从Entry向Sink正向追踪;
  • 从危险Sink反向查调用者。

原因:

  • Wrapper可能隐藏Sink;
  • 同一个Sink可能有多个入口;
  • 别名方法可能绕过明显路径;
  • 二次调用可能从另一个入口触发。

这也是后续Cross Flow建立的基础。

漏洞Reference渐进加载

vulnerability-index.md负责把当前观察信号路由到具体Reference:

flowchart LR
 A[当前入口和Sink信号] --> B[vulnerability-index]
 B --> C[加载适用Reference]
 C --> D[按类型检查Source / Sink / Barrier]
 D --> E[记录references_loaded]

启动时不要一次性读取全部漏洞Reference。

这样同时解决:

  • 上下文膨胀;
  • 类型知识和主流程耦合;
  • 新增漏洞类型维护困难。

新增漏洞类型时基本只需要:

  1. 新增references/vulnerables/<type>.md
  2. vulnerability-index.md加入Signal映射;
  3. 保留统一审计要点 / 门禁 / Example结构。

漏洞知识库

当前Reference覆盖:

注入和动态执行

Reference重点
sql-injection.mdJDBC/JPA/HQL/MyBatis等SQL结构和值边界
nosql-injection.mdMongo/Elastic/Redis/Cypher操作符和查询对象
command-injection.mdShell、ProcessBuilder、Option Injection
expression-template-injection.mdSpEL/OGNL/MVEL/JEXL/模板/脚本
jndi-ldap-injection.mdJNDI Lookup和LDAP Filter/DN
dynamic-code-classloading.mdScriptEngine、Compiler、Reflection、ClassLoader

Web和访问控制

Reference重点
xss.md反射/存储XSS和输出Context
authorization-idor.mdSubject、Tenant、Owner和Resource绑定
authentication-session.mdLogin、JWT、Session、Password、MFA
csrf.md浏览器自动凭据和状态变更
cors.mdOrigin、Credential和敏感响应
open-redirect.mdRedirect目标解析
http-header-injection.mdCRLF和响应Header
cache-host-header.mdHost Header和跨请求缓存污染
request-smuggling.md多解析边界和Framing差异

文件、网络和解析

Reference重点
ssrf.mdURL、Redirect、IP/DNS和内部网络
xxe-xml.mdXML/DTD/XSLT/XPath外部资源
path-traversal.mdResolve、Normalize、Symlink
archive-extraction.mdZip Slip、Link、Bomb
file-upload.mdUpload、存储路径、解析和执行
insecure-deserialization.mdObjectInputStream、多态反序列化和Gadget

数据和配置

Reference重点
hardcoded-secrets.mdSecret真实性和部署可用性
cryptography-randomness.mdCipher、Nonce、Random、TLS
sensitive-data-exposure.mdDTO、Serializer、Export、Log
error-handling-logging.mdStack Trace、日志注入和敏感日志
mass-assignment.mdDTO→Entity过度赋值
insecure-configuration.mdActuator、Debug、Profile、管理端点

业务和资源

Reference重点
business-logic.md状态机和业务不变量
race-condition.mdCheck-then-act、事务和Lost Update
denial-of-service.md正则、递归、压缩、无界集合和资源上限
websocket-messaging.mdDestination、Message身份和Consumer
dependency-vulnerabilities.mdMaven/Gradle组件、Advisory和Reachability

Lane契约

子Agent作为严格JSON接口,不让它自由发挥。

输出至少包含:

  • unit_id
  • Entry Fact;
  • Reviewed Files;
  • Loaded References;
  • Type Decisions;
  • Sources;
  • Flows;
  • Guards;
  • Sinks;
  • Authorization;
  • Candidate Drafts;
  • Dismissed Signals;
  • Cross Flow Edges;
  • Coverage;
  • Searches Performed;
  • Open Questions。

这样做主要有两个目的:

  1. 让父Agent可以机器验收;
  2. 防止Child只返回自然语言“看起来没问题”。

Lane里的Candidate只能作为 candidate_drafts,不能直接成为Canonical Finding。

子Agent架构

能力可用时,每个入口都先进入Lane Queue,并发上限固定为4。

flowchart TD
 Q[FIFO Pending Queue] --> W1[Wave 1]
 W1 --> A[Lane 1]
 W1 --> B[Lane 2]
 W1 --> C[Lane 3]
 W1 --> D[Lane 4]

 A --> S1[settled]
 B --> S1
 C --> S1
 D --> S1

 S1 --> W2[下一Wave]
 W2 --> E[Lane 5]
 W2 --> F[Lane 6]

第5个及之后的入口继续留在Canonical FIFO里,不能变成“以后想起来再处理”的隐式待办。

Parent和Child职责

主Agent子Agent
决定Scope只处理一个入口
管理Queue不修改Queue
管理Canonical State只写Lane Artifact
分配Stable ID使用父Agent给定ID
验收Source/Guard/Sink提交完整证据
分配Candidate ID只产生Local Candidate
Cross Flow只提交Cross Flow Edge
Dependency Research不拥有全局Dependency状态
Independent Review调度不复核自己
Coverage不修改Coverage
Final Report不写Final Report

Lane验收

Child返回结果后不能直接Merge,需要先做验收。

flowchart TD
 A[Lane JSON] --> B[Schema校验]
 B --> C[Unit ID校验]
 C --> D[文件范围校验]
 D --> E[Location可重读]
 E --> F[主Agent复读Entry]
 F --> G[复读关键Guard]
 G --> H[复读Sink]
 H --> I[复读Counterevidence]
 I --> J{通过?}
 J -->|是| K[写Canonical Unit]
 J -->|否| L[Targeted Retry一次]
 L --> M{仍失败?}
 M -->|否| K
 M -->|是| N[主Agent顺序重做]

主Agent的复读只用于Evidence Acceptance,不是把整个入口重新完整分析一遍。

并行回退

子Agent只作为加速层,审计正确性不能依赖它。

如果:

  • Tool失效;
  • Child启动失败;
  • Async失效;
  • Artifact保存失败;
  • Resume失败;
  • Lane验收持续失败;

执行:

flowchart TD
 A[并行基础设施失败] --> B[保存原始错误]
 B --> C[保留已验收Canonical结果]
 C --> D[未验收Active/Queued全部Requeue]
 D --> E[mode=degraded-sequential]
 E --> F[主Agent继续剩余G2-G6]

不能因为子Agent坏了:

  • 把入口标记Blocked;
  • 写Blind Spot;
  • 减少Coverage;
  • 停止审计。

这样就把“审计能力”和“并行基础设施”彻底分开。

G2 Dispatch Coverage

阶段2结束时,Dispatch Coverage需要满足:

flowchart LR
 T[全部入口ID] --> A[delegated_accepted]
 T --> B[sequential_fallback]

要求:

  • 两者互斥;
  • 两者并集等于Total;
  • 每个Fallback有原因;
  • 每个入口有Provenance。

所以“Unit Reviewed”还不够,还要知道:

是谁、通过哪条执行路径完成的。

阶段3:Cross Flow

阶段2主要覆盖“一次入口调用内”的数据流;阶段3则专门补上写入点 → 中间介质 → 后续读取 → 第二Sink这类跨入口链路。

flowchart LR
 A[入口A] --> B[Write]
 B --> C[(Database / Cache / File / Message / Log)]
 C --> D[入口/Consumer B]
 D --> E[Second Sink]

重点:

  • Stored XSS;
  • Second-order SQL;
  • Second-order Command/Expression Injection;
  • Upload → Parse/Execute;
  • Archive → Read/Load;
  • Cache Poisoning;
  • Log → Parser/Viewer;
  • Message → Consumer;
  • Email;
  • Async Task;
  • Credential Issue → Reuse;
  • Role/Tenant propagation;
  • Business State Machine;
  • Race Condition。

这也是这个SKILL区别于普通“Route Scanner”的关键部分。

跨入口分析的索引思路

不会把所有入口做两两组合,而是先把阶段2已经发现的 cross_flow_edges 按Medium聚合:

flowchart TD
 W1[Write Edge] --> DB[(orders table)]
 W2[Write Edge] --> DB
 DB --> R1[Read Consumer]
 DB --> R2[Admin Consumer]

 R1 --> S1[Sink]
 R2 --> S2[Sink]

重点搜索:

  • 所有持久化写入的消费者;
  • 所有消息生产者的消费者;
  • 所有缓存Writer的Reader;
  • 所有文件写入后的读取/解析点。

如果找不到观察入口,不臆造第二Sink,而是降低影响声明。

G3

必须确认:

  • 所有Persistence Write都找过Consumer;
  • 所有Async Producer都找过Consumer;
  • 所有对象访问都检查Subject/Tenant/Role/Owner绑定。

阶段4:依赖漏洞

依赖研究不能简化成“跑一个CVE Scanner”。

证据分三层:

flowchart TD
 A[精确Coordinate + Version] --> B[Advisory范围命中]
 B --> C[受影响Feature / Configuration]
 C --> D[Repository Reachability]
 D --> E[Application Impact]

每层都不能省。

身份

依赖身份统一使用 groupId:artifactId 或PURL。

不能用:

  • JAR文件名;
  • 产品简称;
  • 模糊CPE。

Version置信度

证据置信度
Resolved Graph / Lock最高
显式Version
可静态解析BOM/Dependency ManagementManaged
Range/DynamicRange
无法解析Unknown

遇到 unknown 不能硬猜版本。

数据源

主要:

  • OSV;
  • GitHub Advisory Database。

必要时交叉:

  • NVD;
  • Vendor Advisory;
  • CISA KEV。

KEV只用于表达已知在野利用优先级,不因此改变仓库Reachability判断。

Reachability

命中版本以后继续确认:

  • Class/Method是否使用;
  • Feature是否启用;
  • 攻击者数据是否到达;
  • JDK/Container/Protocol条件;
  • Vendor Backport;
  • Module是否被排除;
  • 是否只是Build Plugin/Test Dependency。

需要始终区分:Component Vulnerable ≠ Application Vulnerable

阶段5:Candidate生命周期

Candidate不能从Search结果直接进入Report。

stateDiagram-v2
 [*] --> signal
 signal --> accepted
 signal --> rejected
 signal --> blocked
 accepted --> independent_review
 independent_review --> confirmed
 independent_review --> conditional
 independent_review --> potential
 independent_review --> rejected
 independent_review --> blocked

Canonical Candidate的disposition只有:

  • signal
  • accepted
  • rejected
  • blocked

Report状态再表达:

  • confirmed
  • conditional
  • potential

这两个维度不要混在一起。

Evidence Gate

普通数据流Finding至少要满足:

证据要求
Entry默认可达或明确条件
Source具体攻击者可控字段
Flow按执行顺序连续
Sink参数语义匹配
Guard已检查且说明为什么不足
LocationEntry/Guard/Sink重新读取
Impact只写当前数据流能直接证明的结果

授权Finding则使用授权Decision Chain。

反证优先

Evidence阶段不是“找更多支持Finding的证据”,而是主动尝试证伪。

必须找:

  • Allowlist;
  • Binding;
  • Output Encoding;
  • Normalize Boundary;
  • Filter;
  • Interceptor;
  • AOP;
  • Method Security;
  • Repository Predicate;
  • Hibernate Filter;
  • Wrapper;
  • Framework Default;
  • Dead Code;
  • Mutually Exclusive Profile。

“没看到Guard”本身不是证据;需要记录具体在哪些范围搜索过Guard

Independent Review

每个Accepted Candidate都需要第二遍独立复核。

Reviewer:

  • 不读取Candidate结论;
  • 重新读原始代码;
  • 尝试推翻Finding;
  • 不能由第一次分析同一个入口的Child自己复核。
flowchart LR
 A[Candidate] --> B[Fresh Reviewer]
 B --> C[重新读取Source]
 C --> D[重新追Flow]
 D --> E[寻找遗漏Guard]
 E --> F[检查Impact]
 F --> G{结论}
 G --> H[uphold]
 G --> I[downgrade]
 G --> J[reject]
 G --> K[blocked]

如果子AgentReviewer失败,主Agent稍后清空前一遍结论自己做同等复核。

即使并行Reviewer不可用,也不能跳过独立复核。

Severity和Confidence

Severity和Confidence完全分开。

Severity看:

  • Proven Impact;
  • Required Identity;
  • Impact Scope;
  • Repeatability;
  • Boundary。

Confidence看:

  • Evidence可靠程度。

不能把:

  • CWE默认等级;
  • CVSS;
  • Advisory Severity;
  • Conditional最坏影响;

直接复制成当前应用Severity。

去重

同一个 Source → Flow → Sink 且处于同一权限边界的伴生现象优先合并。

可拆分条件:

  • Entry权限不同;
  • Impact对象不同;
  • Root Cause Fix不同;
  • Verification Workflow本质不同。

拆分后用related关联。

阶段6:Coverage

Coverage不能简化成一个百分比。

必须保存集合:

flowchart TD
 A[Coverage] --> B[Modules]
 A --> C[HTTP Routes]
 A --> D[Non-HTTP Entrypoints]
 A --> E[Sinks]
 A --> F[Persistent Media]
 A --> G[Runtime Dependencies]

 C --> H[reviewed]
 C --> I[blocked]
 C --> J[unreviewed]

百分比只从这些集合派生,不单独作为Source of Truth。

这样可以避免“99%覆盖率”但不知道到底漏了哪个Route。

报告设计

最终报告虽然是Markdown,但首先是下一阶段的机器接口,不是自由排版的说明文。

必须保持:

  • Metadata YAML;
  • Summary YAML;
  • 完整Finding列表一个YAML Block;
  • Coverage一个独立YAML Block。

特别是 ## 统一漏洞用例详情 标题后的第一个非空行必须直接是:

```yaml
...
```

## 覆盖、边界与去重 也遵循同样约束,标题和YAML之间不能插入解释文字。

为什么这么严格

下一步动态验证Skill需要稳定解析:

  • Endpoint;
  • Auth;
  • Source;
  • Flow;
  • Sink;
  • Evidence;
  • Verification Workflow;
  • Oracle;
  • Evidence Level。

如果报告格式只是“看起来像Markdown”,后续还需要重新自然语言解析,会重新引入歧义。

Verification Handoff

审计SKILL本身不执行PoC,但需要生成最短充分验证方案

统一结构:

flowchart LR
 A[Prerequisites] --> B[Workflow Step 1]
 B --> C[Save Variables]
 C --> D[Workflow Step 2]
 D --> E[Oracle]

单请求漏洞一个Step即可。

跨阶段漏洞必须显式拆Step:

  • Stored XSS:写入 → 读取;
  • Second-order Injection:Seed → Trigger → Observe;
  • Upload:Upload → Access/Parse/Execute;
  • Auth:获取身份 → 访问受保护资源;
  • Async:创建任务 → Poll → Read Result。

后续步骤只能引用前面save的变量。

不能把多请求过程压成一句PoC描述。

Evidence Level

报告支持:

Level表达
response响应直接证明
differential对照差异
time稳定时间差
state状态变化
effect文件/命令/渲染等真实效果
cross_identity不同身份间因果
cross_channelDNS/HTTP/消息等独立通道

静态审计只负责设计这个验证Oracle。

不能因为预计“HTTP 200”就把Impact写成已经落盘或执行。

G6

最终报告需要重新机器校验:

flowchart TD
 A[report-draft.md] --> B[解析Summary]
 B --> C[解析Finding YAML]
 C --> D[解析Coverage YAML]
 D --> E[重新计算Severity Count]
 E --> F[检查ID集合]
 F --> G[检查Workflow变量]
 G --> H[重新读取Evidence Line]
 H --> I[检查围栏外泄漏字段]
 I --> J{全部通过?}
 J -->|否| A
 J -->|是| K[Final Report]
 K --> L[state=completed]

因此不是“报告写完了”就算结束。

完成条件

审计真正完成需要满足:

  • 所有入口状态为reviewed|blocked
  • Blocked全部是真实静态证据阻断;
  • Accepted Candidate完成独立复核;
  • Dependency Query状态明确;
  • G0-G6全部落盘;
  • Final Report可以重新解析。

零Finding也不能表述:

“没有漏洞”。

固定语义应该是:

在已审计范围和静态证据边界内未形成可报告漏洞。

agents/openai.yaml

这里只提供界面层配置:

字段内容
display_nameJava自动代码审计
short_description分阶段语义审计Java仓库并生成完整白盒漏洞报告
default_prompt直接触发完整分阶段流程

真正逻辑仍然全部放在SKILL和Reference里,Agent Manifest不承担业务规则。

设计取舍

先Inventory再审计

避免搜索驱动审计。

安全审计空间先变成稳定ID集合,再针对集合逐个闭环。

Route作为并行最小单元

Route/Entrypoint天然:

  • 范围清晰;
  • 可独立验收;
  • 冲突少;
  • Coverage容易统计。

比“按漏洞类型分Agent”更适合并行。

如果按SQL/XSS/SSRF分别派Agent,每个Agent都会重复读大量同一调用链,而且授权和业务逻辑仍然容易遗漏。

主Agent唯一写Canonical State

牺牲部分并行写入效率,换取:

  • 恢复简单;
  • 无状态冲突;
  • Candidate ID唯一;
  • Coverage一致;
  • Report稳定。

这个取舍以后继续保留。

子Agent只是加速层

审计正确性不能依赖第三方并行插件。

所以任何并行故障都会回退Sequential,而不是变成Coverage Gap。

Search只是Signal

Grep、SAST、IDE Warning和构建告警都只能作为Signal,不能直接成为Finding。

这样可以大幅减少:

  • 名称误报;
  • Dead Code误报;
  • 已有Guard误报;
  • 不匹配API语义误报。

Evidence里强制找反证

审计逻辑不是单向证明,而是先形成Hypothesis,再主动Falsify

这比单纯堆证据更能控制幻觉。

Cross Flow独立阶段

如果Cross Flow只依附在Route Analysis里,很容易漏掉:

  • 数据库二次消费;
  • Admin后台;
  • Async;
  • Cache;
  • Log;
  • Message。

单独G3可以强制做全局Consumer Search。

Dependency Finding必须带Reachability

纯Version命中的CVE只作为组件风险。

只有Feature和攻击路径也成立,才能升级到应用级影响。

Report本身是协议

报告格式本质上是Audit → Verification的Interface,而不只是Presentation层。

所以宁可严格限制YAML围栏,也不为了人类阅读自由排版。

维护风险

Pi Subagent API版本

pi-subagents.md里保留了当前0.56.x调用形态,但明确要求优先读取实时Schema。

后续Pi升级时首先检查:

  • workflowScript
  • runs.all
  • async
  • context:"fresh"
  • outputMode:"file-only"
  • mission:false
  • subagent_wait

维护时不要把Reference里的示例当成永远不变的API。

50KB限制属于宿主约束

当前分块协议明显针对Pi文件工具行为。

如果以后主要运行宿主改变,需要区分:

  • Skill级“必须完整读取”原则;
  • Pi级51,200 bytes具体阈值。

“必须完整读取”的原则要保留,具体阈值可以随宿主调整。

Report Template耦合验证流程

报告字段改动会影响后续验证Skill。

因此修改:

  • verification_handoff
  • evidence_level
  • YAML Block位置;
  • Finding字段;

这些改动应按Schema Migration处理,而不是当成普通文案调整。

Kotlin和非Spring框架

Inventory明确覆盖JVM生态,但Reference中的大量示例仍以Java/Spring为主。

以后如果扩展:

  • Kotlin Coroutines;
  • Ktor;
  • Micronaut;
  • Quarkus;
  • Helidon;

应优先补Framework Semantics,而不是单纯补Sink关键字。

构建插件执行风险

即使在.ai-audit/build-workspace/,Maven/Gradle Plugin仍然是在当前用户权限下执行代码。

不能把“隔离目录”误认为真正的Sandbox。

遇到:

  • 内网连接;
  • Credential请求;
  • 启服务;
  • Workspace外写入;

必须停止构建路径。

后续增强方向

后续继续增强这个SKILL时,优先考虑:

  1. 把Framework Semantics进一步拆成独立Reference;
  2. 为不同JVM Framework增加Route/Authorization Inventory规则;
  3. 给Cross Flow建立更明确的Medium Index Schema;
  4. 给Dependency Reachability定义统一结构;
  5. 给Report Schema增加显式schema_version
  6. 给Lane Result增加版本字段,便于未来迁移;
  7. 把宿主特定限制进一步隔离到Host Reference。

不要优先继续增加漏洞类型数量。

现在更有价值的是:

提高现有类型的框架语义、Coverage可证明性和恢复稳定性。

修改时需要保持的核心约束

以后修改这个SKILL时,至少保持:

  • Inventory早于漏洞判定;
  • 每个入口有稳定ID;
  • 每个入口必须闭环六类Coverage;
  • Search/SAST只作为Signal;
  • Source必须是具体字段;
  • Flow不能跳步骤;
  • Sink必须匹配实际参数语义;
  • 授权必须追踪Subject/Tenant/Owner;
  • 必须主动搜索Counterevidence;
  • Cross Flow不能省略;
  • Dependency必须使用精确Coordinate;
  • Version Unknown不能猜;
  • Accepted Candidate必须独立复核;
  • Child不能写Canonical State;
  • Parallel失败必须回退Sequential;
  • Parallel失败不能成为Audit Blocked;
  • .ai-audit/**是唯一写入位置;
  • Snapshot变化不能复用旧结论;
  • 大文件必须完整覆盖;
  • Coverage保存ID集合而不只保存百分比;
  • 报告YAML必须实际解析;
  • Final Report前必须通过G6;
  • 静态审计不能写动态复现措辞;
  • Verification Workflow只能描述未执行的后续验证步骤。

快速定位

问题先看
整体流程SKILL.md
Resume异常references/skills/context-schema.md
Inventory漏入口references/skills/java-inventory.md
构建写脏仓库references/skills/build-and-dependency-sources.md
Subagent没有Fanoutreferences/skills/pi-subagents.md
Lane结果不完整references/skills/route-lane-contract.md
Finding疑似误报references/skills/evidence-gates.md
不知道该加载哪种漏洞Referencereferences/skills/vulnerability-index.md
某漏洞具体语义references/vulnerables/<type>.md
Dependency CVE判断references/vulnerables/dependency-vulnerabilities.md
最终报告格式错误assets/WHITEBOX_VULNERABILITY_REPORT_TEMPLATE.md
Coverage不一致.ai-audit/coverage.json + G6
子Agent失败后入口丢失state.json.execution.runtime_fallback
Snapshot变化manifest.json
大文件截断.ai-audit/chunks/