让 AI Agent 访问真实数据库听起来很简单——给它一个只读用户就行了,对吧?
QueryBear 工程师在实战中发现,这个”简单”的方案只维持了两天。
问题一:模型会写它不该写的东西
模型完全有能力写出 DELETE 语句,然后用注释 tricks 绕过糟糕的正则检查。“只读”在应用代码层强制执行,离被一条错误的字符串匹配击败只有一步之遥。
问题二:模型会写”合法”但会摧毁数据库的东西
一个”只读” Postgres 用户仍然可以执行 SELECT pg_sleep(3600) 并悄悄占用一个连接。几次下来,你的连接池就死了,你的真实应用开始对所有人返回 500。
或者 Agent 写了一个跨 12 张表的笛卡尔积,返回 5 亿行——技术上完全合法,但会让你的数据库 OOM。
问题三:模型会读它不该读的东西
一个看起来无害的 users 和 oauth_tokens 的 join,可以拉出你根本没打算暴露的凭证。SQL 有效,用户有权限,结果是一场安全事故。
这个问题没有语法检查能catch到。
问题四:来自你自有数据的提示注入
这是最让人头疼的。你的 support_messages 表中的一行文本,可能说服 Agent 去查询一个完全不同的连接。
攻击者不是你的用户,而是三个月前某人写的一段文字,现在正躺在你的数据库里。
深层问题:每层防御都能被绕过
任何单一检查都是”纸墙”。你需要堆叠多层防御:
第一层:什么都不工作
从尽可能限制开始。默认是:没有表,没有列,没有写操作,什么都没有。
然后精确地揭开 Agent 实际需要的那些能力,一层不多。
第二层:每种失败模式加一层
解析器是一层。数据库级只读事务是一层。语句超时、行限制、列白名单、预执行成本估算器、审计日志。
每一层都存在是因为它前面的层可能失败。
第三层:像有人试图打破它一样测试
不是”happy path 通过”。是对抗性测试用例。从真实世界列表中提取的提示注入 payload。多语句攻击。看起来没问题但实际有问题的查询。看起来有问题但实际没问题的查询。
QueryBear 的实战方案
他们的技术栈包括:
- SQL 解析器:严格只允许预期内的操作
- 表和列白名单:Agent 只能看到它被允许看到的
- AST 层重写:行限制和超时不能被 Agent 绕过
- 预执行成本检查:拒绝会扫描过多的查询
- 数据库级只读事务和语句超时:作为硬性兜底
- 完整审计日志:可以重放所有操作
总结
给 AI Agent 数据库访问不是加一个”只读用户”就能解决的。你需要:
- 默认拒绝:从最限制开始
- 洋葱模型:每层防御都是下一层的后备
- 持续测试:模拟攻击者思维
- 审计追踪:出问题能追溯
如果你只建了一层,你建的是一堵墙。如果你把各层都建了,你建的是一栋楼。