一、Codex Security 简介

Codex Security 是一套基于 Codex Agent 的代码安全审计工具。本文基于 @openai/codex-security 0.1.14 版本 SDK,梳理其代码设计和运行过程。

传统静态分析工具(Fortify、CodeQL)把源码抽象成 AST 或数据流图,再用预定义规则匹配已知漏洞模式。这种规则匹配模式存在两个严重问题:

  • 漏报:规则是死的,业务逻辑漏洞、跨组件授权缺陷、状态机绕过落在规则覆盖之外;
  • 误报:规则只认模式不认语义,长得像漏洞的代码一律报警,海量假告警淹没真问题。

Codex Security 充分利用 LLM 灵活强大的语义理解能力:阅读源码、建立威胁地图、验证安全问题,全流程由 AI Agent 自动完成,无需编译源码、不用构建 AST、不用编写规则。

1. 一次扫描过程

Codex Security 执行一次审计扫描通常包含以下步骤:

graph LR
    A[目标绑定] --> B[威胁建模]
    B --> C[漏洞发现]
    C --> D[证据验证]
    D --> E[攻击路径分析]
    E --> F[结果生成]
  • 目标绑定:确定仓库、指定路径、Git revision、working tree 或 diff;
  • 威胁建模:识别资产、入口点、信任边界、攻击者能力和关键控制;
  • 漏洞发现:从外部输入、权限边界和敏感操作中寻找安全问题;
  • 证据验证:检查 attacker、source、control、sink、前置条件和反证;
  • 攻击路径分析:确认问题是否真实可达,并校准影响、置信度和严重性;
  • 结果生成:输出结构化 JSON,再由确定性代码完成校验,并生成报告。

2. 三种扫描工作流

Codex Security CLI 支持三种扫描模式:standard、deep 和 diff。三种模式的简要对比如下:

维度 Standard(默认) Deep Diff
触发方式 scan .,可配合 --path scan . --mode deep,可配合 --path scan . --diff origin/main 或 scan . --working-tree
扫描对象 整个仓库或指定路径 整个仓库或指定目录 commit、分支差异或本地修改
工作方式 一次完整审计,按需并行调查 重复执行多个完整 Standard,再语义归并 审查所有变更源码
耗时 低到中 高,discovery 最长 96 小时(可提前结束) 通常较低,取决于 diff 和验证方式
适用场景 日常审计、模块检查 关键发布、高价值资产、安全验收 PR、CI、commit 和 working tree 审查
覆盖表达 语义化 coverage 和已审文件计数 聚合已完成的 Standard,未完成工作标记为 partial/deferred 所有变更源码和候选都必须被处理,未解决项标记 deferred

二、Codex Security 代码结构

1. 两类代码

Codex Security 的代码可分为两部分:一部分是传统代码逻辑,调用关系确定;另一部分是用自然语言写成的 Agent 工作流(skills/*.md 等),执行逻辑由 LLM 控制,调用关系和执行结果存在一定的不确定性。

graph TD
    subgraph WA[确定性代码]
        A1[src/*.ts CLI、SDK、事件处理]
        A2[scripts/*.py Workbench、校验、报告生成]
        A3[SQLite 扫描和 Finding 状态]
        A4[MCP Runtime 工具适配]
    end

    subgraph WB[Agent 工作流]
        B1[skills/*.md 工作流定义]
        B2[references/*.md 审查规则与数据契约]
        B3[LLM 阅读源码、委派任务、产生草稿]
    end

    WA -->|Prompt、环境变量、MCP 工具| WB
    WB -->|事件流、工具调用、产物文件| WA

2. Standard 运行过程

以 Standard 模式的扫描为例:

1
codex-security scan ./my-repo

阶段一:准备工作

1
2
3
4
5
bin/codex-security.mjs
↓
src/cli.ts main()
↓
src/api.ts CodexSecurity#run()

run() 主要完成四件事:

1
2
3
4
5
6
7
8
9
10
11
① 绑定扫描目标
解析仓库和 --path,记录 Git revision 或 snapshotDigest

② 准备运行环境
配置 Codex、安装 bundled plugin、定位 Python

③ 注册扫描
调用 workbench_db.py,在 SQLite 中登记 scanId 和 scanDir

④ 构造 Prompt
把 skill、目标信息和产物约束交给 Codex thread

环境变量提供 Agent 运行时的上下文,主要包括:

1
2
3
4
5
6
7
CODEX_SECURITY_SCAN_ID
CODEX_SECURITY_SCAN_DIR
CODEX_SECURITY_REPOSITORY
CODEX_SECURITY_PLUGIN_ROOT
CODEX_SECURITY_TARGET_KIND
CODEX_SECURITY_STATE_DIR
PYTHON

阶段二:启动 Agent

startThread() 创建 Codex thread,runStreamed() 启动推理并持续接收 Codex Runtime 返回的事件流:

1
2
3
4
5
6
7
const thread = codex.startThread({
workingDirectory: scanDir,
skipGitRepoCheck: true,
approvalPolicy, // 默认为 "on-request",用户可配 "never"
});

const { events } = await thread.runStreamed(prompt, { signal });

runScanEvents() 中按事件类型分发处理:

1
2
3
4
5
6
7
8
9
Codex Agent
│
│ 持续发送运行事件
▼
runScanEvents()
│
├── item.completed:更新消息和进度
├── turn.failed:结束并报告错误
└── turn.completed:进入结果处理

如果事件流提前断开,却没有收到 turn.completed,会认为本次执行不完整,不会直接把扫描标记为完成。

Codex 的工作目录为扫描输出目录,被审查仓库以绝对路径传入。默认权限设置为:

1
2
3
4
文件系统根目录              只读
workspace roots 可写
CODEX_SECURITY_STATE_DIR 可写
managed credential home 只读

阶段三:Agent 内部审计

第 0 步:能力预检

Standard 在审查源码前,会检查当前 Codex 运行环境是否支持预定的多 Agent 工作流,主要包括:

  • 是否能够创建子 Agent;
  • 是否有足够的 worker 槽位。Standard 推荐配置为 6 个可用槽位,用于同时运行一个 baseline auditor 和多个 focused investigator。

预检通过后,再开始代码审计。

第 1 步:建立审计上下文

父 Agent 读取以下文件,并根据源码建立威胁地图,定位入口点、信任边界、敏感操作和安全控制:

  • security-scan/SKILL.md;
  • references/core-scan.md;
  • 适用目录中的 SECURITY.md;
  • 用户上下文;
  • 可选威胁模型和知识库。

SKILL.md 和 core-scan.md 定义了 Standard 的审计算法和运行流程,LLM 是它的执行引擎。

第 2 步:启动 baseline auditor

父 Agent 启动一个 baseline auditor:

1
fork_turns: "none"  // 子 Agent 不继承父会话历史

auditor 不接收父 Agent 生成的威胁地图,它根据独立 prompt 从头检查目标源码。这种设计是为了避免所有 worker 都围绕同一组预设展开调查,减少”先入为主”造成的漏报。

第 3 步:派发 focused investigators

父 Agent 将安全问题整理为 investigation packets,再根据工作量决定 investigator 数量。

investigator 可以从不同方向对源码展开调查:

1
2
3
4
Forward        从外部输入追向敏感操作
Backward 从 sink 反查调用者
Authorization 检查权限、租户和状态机
Open-ended 调查其他有源码证据的可疑机制

investigator 同样设定 fork_turns: "none",并在 prompt 里禁止它继续派生子 Agent,防止递归爆炸。

第 4 步:合并与验证

父 Agent 综合 auditor 和 investigator 的运行结果,根因相同、修复方式相同则合并为一个 finding,仅 CWE 相同不合并。

随后对每个 finding 进行一次源码路径检查,通过静态审计验证 finding 的真实性:

1
2
3
4
5
6
7
attacker
→ entry point
→ trust boundary
→ attacker-controlled data
→ effective control
→ sensitive operation
→ impact

阶段四:验收与交付

Standard 完成审计后,Agent 将结果输出到 CODEX_SECURITY_SCAN_DIR 目录下:

1
2
3
scan-manifest.json
findings.json
coverage.json

然后通过事件通知确定性代码验收:

1
2
3
4
5
6
7
8
9
Codex turn.completed          // Agent 运行结束
↓
prepare-scan-completion // 补全 metadata、派生 fingerprint、校验文档 Schema、生成 report.md
↓
collectResult() // 检查四个必要产物,并通过 src/contract.ts 再次校验 fingerprint 等信息
↓
complete-scan // SQLite 更新审计状态,running → complete
↓
ScanResult // 返回结果

3. 全流程图

Standard 的流程全景如下:

graph TB
    subgraph WA[确定性准备]
        A1[CLI / SDK] --> A2[绑定目标与输出目录]
        A2 --> A3[准备 Runtime 与 Plugin]
        A3 --> A4[SQLite 注册扫描]
        A4 --> A5[构造 Prompt]
    end

    subgraph WB[Agent 审计]
        B1[Standard Preflight]
        B2[Baseline Auditor]
        B3[父 Agent 建立威胁地图]
        B4[Focused Investigators]
        B5[合并与源码验证]
        B6[写入 Canonical JSON]

        B1 --> B2
        B1 --> B3
        B3 --> B4
        B2 --> B5
        B4 --> B5
        B5 --> B6
    end

    subgraph WA2[确定性交付]
        C1[Python 生成 report.md]
        C2[TypeScript 二次校验]
        C3[SQLite 标记 complete]
        C4[返回 ScanResult]

        C1 --> C2 --> C3 --> C4
    end

    A5 --> B1
    B6 --> C1

4. 设计要点

① 审查判断与状态发布分离

Agent 负责源码分析,但审计状态由确定性代码检查并更新,产物校验和数据库事务都成功之后,扫描状态才会转为 complete。

② Agent 写草稿,确定性代码负责定稿

Agent 先输出结构化的 JSON 结果,确定性程序再根据这些结果自动生成 report.md,而不是让 Agent 直接编写报告。

③ Baseline Auditor 与父 Agent 使用不同初始假设

baseline auditor 不接收父 Agent 生成的威胁假设,这种 prompt 隔离可以补充父 Agent 未覆盖的方向,降低漏报的概率。