自动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.md | Java/JVM Inventory规则 |
references/skills/build-and-dependency-sources.md | 隔离构建和第三方源码扩展 |
references/skills/pi-subagents.md | Pi子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.jsonl | HTTP入口 |
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.md | G6前的报告草稿 |
*_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/**,同时硬排除下面这些路径段:
| 路径段 | 原因 |
|---|---|
.idea | IDE 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可用。
必须检查当前实际工具:
- 精确工具名
subagent; action:list;- 可执行Agent;
- Schema支持时
action:doctor; subagent_wait或等价通知。
模式:
| 条件 | Mode |
|---|---|
subagent可用且存在合适Reviewer | pi-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。
这样同时解决:
- 上下文膨胀;
- 类型知识和主流程耦合;
- 新增漏洞类型维护困难。
新增漏洞类型时基本只需要:
- 新增
references/vulnerables/<type>.md; - 在
vulnerability-index.md加入Signal映射; - 保留统一
审计要点 / 门禁 / Example结构。
漏洞知识库
当前Reference覆盖:
注入和动态执行
| Reference | 重点 |
|---|---|
sql-injection.md | JDBC/JPA/HQL/MyBatis等SQL结构和值边界 |
nosql-injection.md | Mongo/Elastic/Redis/Cypher操作符和查询对象 |
command-injection.md | Shell、ProcessBuilder、Option Injection |
expression-template-injection.md | SpEL/OGNL/MVEL/JEXL/模板/脚本 |
jndi-ldap-injection.md | JNDI Lookup和LDAP Filter/DN |
dynamic-code-classloading.md | ScriptEngine、Compiler、Reflection、ClassLoader |
Web和访问控制
| Reference | 重点 |
|---|---|
xss.md | 反射/存储XSS和输出Context |
authorization-idor.md | Subject、Tenant、Owner和Resource绑定 |
authentication-session.md | Login、JWT、Session、Password、MFA |
csrf.md | 浏览器自动凭据和状态变更 |
cors.md | Origin、Credential和敏感响应 |
open-redirect.md | Redirect目标解析 |
http-header-injection.md | CRLF和响应Header |
cache-host-header.md | Host Header和跨请求缓存污染 |
request-smuggling.md | 多解析边界和Framing差异 |
文件、网络和解析
| Reference | 重点 |
|---|---|
ssrf.md | URL、Redirect、IP/DNS和内部网络 |
xxe-xml.md | XML/DTD/XSLT/XPath外部资源 |
path-traversal.md | Resolve、Normalize、Symlink |
archive-extraction.md | Zip Slip、Link、Bomb |
file-upload.md | Upload、存储路径、解析和执行 |
insecure-deserialization.md | ObjectInputStream、多态反序列化和Gadget |
数据和配置
| Reference | 重点 |
|---|---|
hardcoded-secrets.md | Secret真实性和部署可用性 |
cryptography-randomness.md | Cipher、Nonce、Random、TLS |
sensitive-data-exposure.md | DTO、Serializer、Export、Log |
error-handling-logging.md | Stack Trace、日志注入和敏感日志 |
mass-assignment.md | DTO→Entity过度赋值 |
insecure-configuration.md | Actuator、Debug、Profile、管理端点 |
业务和资源
| Reference | 重点 |
|---|---|
business-logic.md | 状态机和业务不变量 |
race-condition.md | Check-then-act、事务和Lost Update |
denial-of-service.md | 正则、递归、压缩、无界集合和资源上限 |
websocket-messaging.md | Destination、Message身份和Consumer |
dependency-vulnerabilities.md | Maven/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。
这样做主要有两个目的:
- 让父Agent可以机器验收;
- 防止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 Management | Managed |
| Range/Dynamic | Range |
| 无法解析 | 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 | 已检查且说明为什么不足 |
| Location | Entry/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_channel | DNS/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_name | Java自动代码审计 |
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时,优先考虑:
- 把Framework Semantics进一步拆成独立Reference;
- 为不同JVM Framework增加Route/Authorization Inventory规则;
- 给Cross Flow建立更明确的Medium Index Schema;
- 给Dependency Reachability定义统一结构;
- 给Report Schema增加显式
schema_version; - 给Lane Result增加版本字段,便于未来迁移;
- 把宿主特定限制进一步隔离到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没有Fanout | references/skills/pi-subagents.md |
| Lane结果不完整 | references/skills/route-lane-contract.md |
| Finding疑似误报 | references/skills/evidence-gates.md |
| 不知道该加载哪种漏洞Reference | references/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/ |