你的 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安全早报 + 实战攻防案例 + 网安学习路线连载

关注安全值班室

posted on 2026-05-31 09:06  明.Sir  阅读(43)  评论(0)    收藏  举报

导航