如何发现 AI Agent 中未经检查的危险函数调用?Diplomat 静态扫描实战
你交付了一个 TypeScript AI Agent。它能够读写数据库、发送邮件、调用支付接口、删除文件、执行系统命令。每一个能力都是精心设计的功能——但你知道哪些函数调用没有任何安全检查吗?
这并非危言耸听。在传统 Web 应用中,每次危险操作都有人类点击确认。但在 AI Agent 中,LLM 自主决定调用哪些函数、以什么参数调用、调用多少次——它可能循环调用、幻觉出错误参数、甚至被 prompt injection 利用。
Diplomat Agent 是一个开源的静态 AST 扫描器,专门分析你的 TypeScript AI Agent 代码,找出所有未经安全检查的工具调用。它只有两个运行时依赖,在 7,874 个 TypeScript 文件的代码库上只需约 9 秒就能完成全量扫描。
为什么 AI Agent 需要安全检查?
在传统应用中,函数调用有天然的安全缓冲:用户通过 UI 操作触发、有确认弹窗、有速率限制。
但在 AI Agent 架构中:
- LLM 自主决定调用 — Agent 根据上下文自行选择调用哪些工具函数
- 无人工确认链 — 没有每次操作的确认对话框
- 循环风险 — LLM 可能重复调用同一个函数数千次
- 参数幻觉 — Agent 可能使用错误参数调用危险函数
- Prompt Injection — 攻击者可能通过注入操控 Agent 调用未授权的函数
Diplomat 的团队扫描了 OpenClaw Agent 代码库(7,874 个 TypeScript 文件),发现了 419 个有真实副作用的工具调用,其中 332 个(79%)没有任何安全检查。
快速开始
通过 npm 安装并运行扫描:
npm install -D @diplomat-ai/diplomat-agent-ts npx diplomat-agent-ts scan . # 扫描项目根目录 npx diplomat-agent-ts scan ./src # 扫描指定子目录
输出示例:
Found 419 tool calls with real side effects 332 (79%) have NO checks 87 (21%) have partial checks ⚠️ chargeCustomer (src/payments.ts:42) — no bounds, no rate limit, no approval ⚠️ deleteUserData (src/users.ts:156) — no confirmation, no batch protection ✅ sendWelcomeEmail (src/notifications.ts:23) — checked:ok
检测范围
Diplomat 支持 40+ 种模式,覆盖 12 类副作用:
| 类别 | 示例调用 | 所需守卫 |
|---|---|---|
| 支付(payment) | stripe.charges.create() | bounds、rate limit、approval |
| 数据库写入(database_write) | Prisma .create(), Mongoose .save() | 输入验证、rate limit |
| 数据库删除(database_delete) | Prisma .delete(), raw DELETE | 批量保护、确认 |
| HTTP 写入(http_write) | axios.post(), fetch(POST) | rate limit、重试上限 |
| 邮件(email) | nodemailer.sendMail() | rate limit |
| 消息(messaging) | twilio.messages.create() | rate limit |
| Agent 调用(agent_invocation) | agent.run(), graph.invoke() | 输入验证、approval |
| 动态代码(dynamic_code) | eval(), new Function() | 确认 |
| 文件删除(file_delete) | fs.rm(), fs.unlink() | 确认 |
| 破坏性操作(destructive) | execSync(), spawnSync() | 确认 |
守卫检查项包括:输入验证(Zod、Yup)、速率限制(NestJS @Throttle)、权限检查(NestJS Guards)、确认步骤、幂等性键、重试上限。
CI 集成:阻止未经检查的代码合入
在 GitHub Actions 中配置 Diplomat 扫描,让任何包含未检查危险调用的 PR 无法通过:
- name: Diplomat governance scan run: npx -y @diplomat-ai/diplomat-agent-ts scan . --fail-on-unchecked
--fail-on-unchecked 参数使扫描器在发现任何 no_checks 状态的调用时返回 exit code 1,直接阻断 CI 流水线。partial_checks 状态仅作为警告,不会阻断。
也可以在 pre-commit hook 中使用:
repos:
- repo: local
hooks:
- id: diplomat-agent-ts
name: diplomat governance scan
entry: npx -y @diplomat-ai/diplomat-agent-ts scan . --fail-on-unchecked
language: system
pass_filenames: false
工具调用 SBOM:构建可见性
除了终端输出,Diplomat 可以生成 toolcalls.yaml——一个类似 package-lock.json 的行为清单(SBOM),记录每个工具调用及其安全状态:
npx diplomat-agent-ts scan . --output-registry toolcalls.yaml
生成的文件可 commit 到仓库,在 PR 中 diff——新增危险函数调用时,reviewer 可以直观看到变化:
spec_version: "1.0"
language: typescript
summary:
total: 12
no_checks: 8
partial_checks: 3
confirmed: 1
tool_calls:
- function: chargeCustomer
file: src/payments.ts
line: 42
checks: []
missing:
- no bounds on amount
- no rate limit
- no idempotency key
owasp: [ASI-01, ASI-02, ASI-03, ASI-06]
标记已确认的安全调用
对于故意不设守卫或通过外部中间件保护的函数,可以用 inline annotation 标记:
// checked:ok — protected by middleware/approval.ts
export async function chargeCustomer(amount: number, customerId: string) {
return stripe.charges.create({ amount, currency: "usd", customer: customerId });
}
下次扫描时,该调用会被归为 confirmed 状态,不再报警。diplomat:ok 和 canary:ok 也被接受。
实践技巧
- 先扫描后开发 — 在新项目中先运行扫描建立基线,后续每次新增工具调用时审查安全状态
- CI 强制闸门 — 将
--fail-on-unchecked加入 CI,防止未经检查的工具调用进入 main 分支 - SBOM 版本控制 — 将
toolcalls.yaml纳入代码审查流程,每次 Agent 能力变更时都有记录 - 结合运行时防护 — Diplomat 的静态扫描与 diplomat-gate 运行时强制形成互补(后者可以在 <1ms 内对每次调用做出 CONTINUE / REVIEW / STOP 决策)
- 告诉你的 AI Agent — 可以要求 Claude Code、Copilot 或 Cursor 在生成工具调用代码后自动运行扫描:”Run
diplomat-agent-ts scan .and fix any unguarded tool calls.”
结语
AI Agent 的信任问题本质上是可见性问题。当你不知道自己的 Agent 能做什么危险操作时,你根本无法信任它。Diplomat Agent 用静态分析的方式,帮你回答一个最基本的问题:「我的 AI Agent 在真实世界中到底能干什么?」——以及更重要的,「哪些操作没有任何防护?」
对于正在构建 TypeScript AI Agent 的团队来说,Diplomat 应该成为开发流程中的最小安全基线。