你的 AI Agent 还在裸奔连数据库?去年有团队三分钟清空了两张核心表
你的 AI Agent 还在裸奔连数据库?去年有团队三分钟清空了两张核心表
前言
上周帮一个客户做安全评估,打开他们的 AI Agent 代码仓库一看,我后背直冒冷汗——数据库连接串直接硬编码在配置文件里,Agent 用 root 权限连接,生成的 SQL 没有任何过滤就直接执行。
我问他们:"你们不怕 Agent 生成一条错误的 DELETE 语句?"
对方答:"我们有备份啊。"
备份?三分钟够你恢复吗?
去年真出过这种事。一个团队在测试环境演示 AI Agent,让 Agent 根据自然语言查询用户数据。结果 LLM 生成了一条带错误 WHERE 子句的 DELETE 语句,测试账号没有行级权限控制,三分钟内清空了两张核心表。备份是有的,但恢复花了四个小时,期间业务完全中断。
今天这篇,我把自己在实际项目中踩过的坑、总结出的 5 种 AI Agent 安全访问数据库的姿势分享出来。不是理论,是真正上线跑过的方案。
姿势一:最小权限 + 只读账号(最低成本,但不够)
最简单的做法:给 Agent 一个只读账号,限制它只能 SELECT。
-- 创建只读用户
CREATE USER 'agent_reader'@'%' IDENTIFIED BY 'strong_random_password';
GRANT SELECT ON target_db.* TO 'agent_reader'@'%';
FLUSH PRIVILEGES;
优点是实现成本几乎为零,缺点也很明显——Agent 只能读不能写,很多业务场景用不了。
踩坑记录:我曾经用这个方案给一个客服 AI 用,结果开发同事抱怨"Agent 没法帮客户修改订单状态"。最后妥协方案是:读操作用只读账号,写操作走审批 API。
# 读操作直接查数据库
def read_query(sql: str):
return db.execute(read_only_conn, sql)
# 写操作走审批接口
def write_operation(action: str, params: dict):
return approval_api.submit(action=action, params=params)
姿势二:SQL 白名单 + 语法树校验(推荐方案)
这是我目前用得最多的方案。核心思路:Agent 生成 SQL 后,不是直接执行,而是先过一道 SQLParser 解析,检查语法树是否合规。
import sqlglot
def validate_sql(sql: str) -> tuple[bool, str]:
"""校验 Agent 生成的 SQL 是否安全"""
try:
parsed = sqlglot.parse_one(sql)
except Exception as e:
return False, f"SQL 解析失败: {e}"
# 1. 禁止危险操作
forbidden_types = ['DELETE', 'DROP', 'TRUNCATE', 'ALTER', 'INSERT', 'UPDATE']
if parsed.key.upper() in forbidden_types:
return False, f"禁止执行 {parsed.key} 操作"
# 2. 禁止子查询嵌套超过 2 层(防止复杂注入)
if _check_subquery_depth(parsed, max_depth=2):
return False, "子查询嵌套过深"
# 3. 禁止访问敏感表
tables = _extract_tables(parsed)
sensitive_tables = {'user_credentials', 'payment_info', 'api_keys'}
if tables & sensitive_tables:
return False, f"禁止访问敏感表: {tables & sensitive_tables}"
# 4. 检查 WHERE 子句(防止全表扫描)
if not _has_where_clause(parsed):
return False, "缺少 WHERE 条件,可能导致全表扫描"
return True, "校验通过"
def _extract_tables(parsed) -> set:
"""提取 SQL 中涉及的表名"""
tables = set()
for table in parsed.find_all(sqlglot.exp.Table):
tables.add(table.name.lower())
return tables
关键点:用 sqlglot 而不是正则来解析 SQL。正则匹配 SQL 是个天坑,我之前用正则过滤 DROP TABLE,结果被一条 SELECT * FROM users WHERE name = 'DROP TABLE test' 绕过了——因为用户数据里恰好包含这个字符串。
姿势三:参数化查询 + 预定义模板(最安全)
最高安全级别:不让 Agent 自由生成 SQL,而是预定义一套查询模板,Agent 只负责填参数。
QUERY_TEMPLATES = {
"get_user_by_id": {
"sql": "SELECT id, name, email FROM users WHERE id = %s AND status = 'active'",
"params": ["user_id"],
"description": "根据用户ID查询用户信息"
},
"get_recent_orders": {
"sql": """
SELECT order_id, amount, created_at
FROM orders
WHERE user_id = %s AND created_at >= %s
ORDER BY created_at DESC
LIMIT %s
""",
"params": ["user_id", "since_date", "limit"],
"description": "查询用户最近的订单"
}
}
def execute_template(template_name: str, params: dict):
"""通过模板执行查询,Agent 不能自由拼 SQL"""
if template_name not in QUERY_TEMPLATES:
raise ValueError(f"未知查询模板: {template_name}")
template = QUERY_TEMPLATES[template_name]
param_values = [params[p] for p in template["params"]]
# 参数化查询,SQL 注入?不存在的
return db.execute(template["sql"], param_values)
安全是安全了,但灵活性大打折扣。Agent 能做的查询类型完全取决于你预定义了多少模板。
实际项目经验:我们给 Agent 定义了 47 个查询模板,覆盖了 90% 的业务场景。剩下 10% 的边缘场景,Agent 会回复"抱歉,这个查询超出了我的能力范围,请联系管理员"。
姿势四:中间代理层(企业级方案)
在 Agent 和数据库之间加一层 SQL 代理,所有 SQL 都经过代理审核后才执行。
架构大概是这样:
AI Agent → SQL Proxy (Go/Python) → Database
↓
审计日志 + 权限校验 + 风险评分
代理层的核心逻辑:
class SQLProxy:
def __init__(self):
self.risk_engine = RiskEngine()
self.audit_logger = AuditLogger()
def execute(self, sql: str, context: dict) -> dict:
# 1. 记录审计日志
self.audit_logger.log(sql=sql, agent_id=context['agent_id'])
# 2. 风险评分
risk_score = self.risk_engine.evaluate(sql)
if risk_score > 0.8:
# 高风险:直接拒绝
return {"error": "SQL 风险过高,已拒绝执行", "risk_score": risk_score}
if risk_score > 0.5:
# 中风险:需要人工审批
return {"status": "pending_approval", "risk_score": risk_score}
# 低风险:执行并返回结果
result = self.db.execute(sql)
# 3. 检查结果集大小(防止泄露过多数据)
if len(result) > 1000:
return {"error": "结果集过大,请添加更多过滤条件", "count": len(result)}
return {"data": result, "rows": len(result)}
踩坑记录:结果集大小限制很重要。有一次 Agent 执行了一条 SELECT * FROM logs,没加时间范围限制,直接返回了 500 万条记录,把 Agent 的上下文窗口撑爆了,后续对话全乱了。
姿势五:数据库 Activity Monitoring(事后兜底)
前面四种都是事前防御,第五种是事后监控。部署数据库活动监控,实时检测异常查询模式。
# prometheus + alertmanager 配置片段
groups:
- name: db_agent_monitor
rules:
# 检测 DELETE/UPDATE 操作
- alert: AgentDangerousQuery
expr: increase(db_query_total{query_type=~"DELETE|UPDATE|DROP", source="ai_agent"}[5m]) > 0
labels:
severity: critical
annotations:
summary: "AI Agent 执行了危险 SQL 操作"
# 检测异常查询量
- alert: AgentQuerySpike
expr: rate(db_query_total{source="ai_agent"}[5m]) > 100
labels:
severity: warning
annotations:
summary: "AI Agent 查询频率异常升高"
# 检测全表扫描
- alert: AgentFullTableScan
expr: increase(db_slow_queries{source="ai_agent", type="full_scan"}[10m]) > 5
labels:
severity: warning
annotations:
summary: "AI Agent 触发了多次全表扫描"
监控不能阻止事故发生,但能让你在事故发生后第一时间知道,并且有完整的审计日志用于溯源。
我的实际选择
说了五种方案,我在不同项目中的选择是这样的:
- 内部工具/演示环境:姿势一(只读账号)+ 姿势五(监控),够用
- 面向客户的 AI 产品:姿势二(SQL 白名单)+ 姿势四(代理层),安全与灵活性平衡
- 金融/医疗等高合规场景:姿势三(模板查询)+ 姿势四(代理层)+ 姿势五(监控),三重保险
没有银弹,关键是根据你的业务场景选择合适的组合。
如果你也在做 AI Agent 与数据库对接的安全方案,欢迎在评论区交流踩坑经验。
关注「安全值班室」公众号
每天AI安全早报 + 实战攻防案例 + 网安学习路线连载
浙公网安备 33010602011771号