跳到正文

AGENT PROJECT INTERVIEW · Q22 / 25

Text2SQL 生成的 SQL 敢直接执行吗?安全校验与执行边界怎么设计

从 AST 白名单、Schema 权限、参数化、成本预检到只读隔离和结果验证,讲清 Text2SQL 的 SQL 安全执行链路。

作者与复核:吴师兄内容依据:公开 Agent 教程、项目题资料与工程推导查看关联教程 →查看吴师兄公开作品 →

25 道题目Q22
本页内容21 节

30 秒先说结论

Text2SQL 生成的 SQL 先解析 AST,限制单条只读语句,校验表列函数与 Join,使用当前用户权限和参数绑定,再加结果上限、超时与成本预检,在最小权限只读环境执行。写操作走独立动作工具和审批,结果仍需检查敏感字段与业务合理性。

面试官可能会问:“模型已经生成 SQL,为什么不能直接连生产库执行?你会在哪几层拦截?”

Text2SQL 最大的风险,不只是语法错。一个合法 SELECT 也可能全表扫描、错误连接造成结果膨胀、读取无权字段、通过函数访问外部资源,或者返回大量敏感数据。更不用说模型受用户提示影响后生成 DELETEDROP、多语句和绕过限制的表达式。

关键认知是:模型输出永远是不可信计划。Prompt 里写“只生成安全查询”不能代替代码校验,关键词黑名单也不能代替 SQL 解析和数据库权限。安全要从生成约束、AST 校验、Schema 与权限、查询成本、隔离执行、结果审查和审计七层共同完成。

Text2SQL 的默认能力最好是只读查询。若业务真的需要修改数据,应作为另一类高风险工具,使用明确动作 Schema、Dry-run、人工确认、幂等和审计,不能让一个自然语言查询端点同时拥有任意写权限。

先给出 30 秒回答

Text2SQL 的 SQL 不能直接执行。我会把模型当作不可信计划生成器,先解析为 AST,要求单语句、只读语句和允许的语法结构,再校验表、列、函数、Join 与 Schema 版本,并用当前用户身份做行列权限判断。随后强制参数化、结果上限、超时和只读事务,对复杂查询先做 Explain 或成本预检。真正执行使用最小权限账号、只读副本或隔离查询服务,结果还要检查行数、敏感字段和口径异常。写操作走独立工具和审批,不通过 Text2SQL 直接执行。全链路保留原问题、生成 SQL、校验结果、执行状态和版本 Trace。

这段回答的重点是:SQL 可解析、能执行和允许执行是三件不同的事。

Text2SQL SQL 安全执行链路

任何一层拒绝都应安全停止,不能为了返回结果绕过校验。

第一层:生成阶段只能减少风险,不能证明安全

可以在生成 Prompt 中明确数据库方言、允许的表列、只读要求、禁止多语句、默认结果上限和参数占位符。Function Calling 也可以让模型返回结构化对象,例如 SQL 模板、参数、使用的表列和解释。

这些约束能减少错误,却不能成为安全边界。模型可能忽略指令,用户输入可能包含提示注入,Few-shot 也可能带入旧语法和无权字段。

生成器不应拿到数据库凭证。它只输出候选计划,执行器在独立服务中完成校验和访问控制。即使模型声称 risk=low,后端仍按实际 AST 与身份判断。

不要依赖“过滤 DROP 关键词”。大小写、注释、嵌套语句、方言函数和多语句都可能绕过字符串匹配。安全判断需要 SQL Parser 将文本转换成结构树。

第二层:语法和 AST 白名单

解析器先确认 SQL 是目标方言下的一条完整语句,没有额外语句尾随。解析失败直接拒绝并返回可读错误,不把原始异常和数据库细节暴露给用户。

白名单可以只允许查询语句,并明确允许的节点类型。写入、删除、建表、授权、事务控制、调用存储过程、外部文件或网络能力默认拒绝。

“只允许 SELECT”仍不够。某些方言允许在 CTE、函数或特殊语法中产生副作用,或者读取系统目录和外部表。校验器需要遍历 AST,而不是只看第一个词。

多语句必须禁止。SELECT ...; DELETE ... 的第一个词是 SELECT,字符串前缀检查会漏掉第二条语句。

校验结果应结构化:是否通过、失败层、错误代码、可否重新生成和用户可见说明。模型可以根据“未知列”重新生成,但遇到权限拒绝不能换写法继续探测。

第三层:表、列、函数与关系校验

AST 中引用的每张表和列都必须存在于当前 Schema 版本,并位于为当前查询选出的允许子图。模型不能临时访问未提供的表。

列要解析别名和作用域,避免同名字段混淆。SELECT * 通常应拒绝或展开为明确允许列,防止 Schema 新增敏感字段后被自动带出。

函数也要白名单。普通聚合与日期函数可以按业务开放,执行系统命令、读文件、访问网络、休眠或资源消耗异常的函数应拒绝。用户自定义函数不能因为名字看起来无害就放行。

Join 校验需要检查连接键是否来自允许的关系元数据,以及是否存在笛卡尔积。缺失条件的 Join、可疑的多对多展开和没有业务意义的同名列连接应被拦截或要求重新生成。

Schema 变化后,缓存 SQL 必须重新校验。不能因为过去审核通过,就在新版本继续直接执行。

第四层:权限必须由数据库或后端身份执行

模型不知道用户当前真实权限,也不能可靠判断某列是否敏感。

执行服务根据登录用户、租户、角色、数据域和用途得到允许表列。行级权限可以通过数据库 RLS、安全视图、强制租户条件或受控查询层实现。列级权限通过安全视图、字段白名单、脱敏或聚合结果控制。

不能只要求模型在 WHERE 里加 tenant_id。模型可能漏掉、写错或被注入移除。租户约束应由查询重写器或数据库策略强制,用户生成 SQL 无法绕过。

错误信息也要防泄露。无权访问和资源不存在有时不能区分得过细,否则攻击者可用查询探测 Schema。内部 Trace 保留准确原因,对外返回最小可读信息。

查询账号坚持最小权限。默认连接只读副本或只读角色,不拥有 DDL、DML、管理函数和跨域权限。应用层校验即使有漏洞,数据库仍有最后一道边界。

第五层:参数化而不是拼接用户值

模型可以生成 SQL 模板和参数列表,但执行器用数据库驱动的绑定参数传值。用户输入、实体名称、日期和筛选值不能通过字符串拼接进入 SQL。

参数化主要保护值位置。表名、列名和排序方向通常不能用普通参数绑定,因此必须从经过校验的标识符白名单中选择,不能直接使用用户文本。

IN 列表、模糊搜索和日期范围也要按驱动能力构造参数。不要为了方便把整个 WHERE 条件当字符串交给模型。

业务术语映射出的实体值要校验类型、长度和枚举。数据库报错不应把完整 SQL、内部表名或连接信息直接返回用户。

第六层:查询成本与资源护栏

只读查询也能拖垮数据库。没有过滤的大表扫描、相关子查询、错误 Join、超大排序、递归 CTE 和高基数聚合都可能占用大量资源。

执行器应强制结果行数与输出字节上限、超时、并发限制和资源组。默认添加 LIMIT 时要注意语义,聚合查询和分页不能简单字符串追加,应该在 AST 层重写。

对复杂查询可以先执行 Explain 或数据库提供的成本估算,检查计划节点、预计扫描量、Join 类型和是否命中分区。成本估算不是绝对准确,但能拦住明显异常计划。

限制值应根据数据库能力、业务 SLA 和真实回放校准。没有运行证据时不要给固定秒数、扫描行数或成本阈值。

执行环境最好与核心交易库隔离。分析查询走只读副本、数据仓库或专用查询服务,避免一个 Text2SQL 请求与在线写流量争抢资源。

SQL 安全校验的七层边界

Prompt、AST、权限、资源和数据库账号共同构成纵深防御。

第七层:执行结果也需要验证

SQL 成功返回不代表回答正确或安全。

结果验证先检查列是否符合期望 Schema,行数和数据量是否在限制内,敏感字段是否意外出现,租户和数据域是否正确。对聚合结果检查空值、异常负数、重复行和明显不合理量级。

不能因为结果为空就不断放宽权限或删除过滤。空结果可能是合法事实,也可能是口径、实体或时间错误,应返回上游做语义判断。

结果展示要避免二次泄露。下载、导出和完整明细可能需要更高权限。给模型用于解释的结果也应最小化,必要时只提供聚合或脱敏内容。

Text2SQL 还应生成查询解释:使用的指标口径、时间范围、过滤、分组和默认值。用户可以发现“按支付时间”与自己想要的“按完成时间”不同,而不是只看到一张数字表。

为什么写操作必须拆成另一类工具

自然语言查询的用户心智是“看数据”。若同一端点还能更新、删除或建表,一个歧义或提示注入就可能产生真实副作用。

需要修改数据时,优先定义明确业务动作,例如“创建工单”“修改标签”,而不是允许模型生成任意 UPDATE。动作 Schema 只暴露必要字段,后端再转换为受控事务。

高风险动作先 Dry-run,展示对象、字段变化和影响范围,再由用户或审批系统确认。执行使用幂等键,超时后先查询结果,避免重复写。

数据库迁移、批量修复和管理 SQL 更不应走普通 Agent 自动执行。可以让模型生成建议脚本,但必须经过代码评审、测试环境和发布流程。

一个完整例子:查询上月各渠道净收入

语义层已经确认“净收入”口径、上月自然周期和渠道维度。生成器得到最小 Schema,输出带参数的候选 SQL。

Parser 确认单条查询语句,AST 遍历发现只使用允许的表、列、聚合和 Join。权限层根据当前用户限制可见业务线,数据库策略强制租户条件。执行器绑定开始与结束时间参数。

成本预检发现查询使用时间分区,并在允许的分析环境执行。执行设置只读事务、超时和结果上限。返回后检查渠道字段、收入聚合与数据域,没有把客户明细和敏感列交给模型。

若模型意外生成 SELECT *,列校验拒绝并要求按允许字段重写。若生成了退款表但当前角色无权访问,不让模型尝试别的绕过方式,而是返回权限范围内无法完成。

这个例子里没有任何一层依赖模型自报安全。

Trace 至少记录哪些内容

记录原始用户意图的安全摘要、解析后的业务槽位、Schema 版本、候选 SQL 哈希或受控文本、参数类型、AST 校验结果、引用表列、权限决策、成本预检、执行环境、耗时、结果行数、错误类型和最终解释。

日志需要脱敏。参数可能包含个人信息,SQL 也可能暴露内部表名。普通运营日志不保存完整敏感内容,受限审计按用途和保留期限访问。

每个失败要有首个拒绝层。Parser 拒绝、权限拒绝、成本拒绝、数据库超时和结果异常对应不同修复,不能都记成“SQL 失败”。

缓存命中也要记录原 SQL 的 Schema 与权限版本。权限变化后旧缓存立即失效。

校验失败后应该重写还是直接拒绝

不是所有失败都适合让模型再试一次。

列名拼错、别名不完整或目标方言语法错误,可以把结构化错误和允许的 Schema 返回生成器,在有限次数内重新生成。结果上限缺失,也可以由 AST 重写器按确定规则补上。

权限拒绝、跨租户、危险函数、写操作和越权数据源不能交给模型“换个写法”。如果允许它根据详细拒绝原因不断尝试,系统可能变成 Schema 和权限探测器。此时应终止查询,向用户说明当前权限或能力边界。

成本过高可以分两类。能确定性改写的分页、选择明确列或按分区增加已确认时间条件,可以生成新候选并再次完整校验;会改变业务含义的采样、缩短时间、减少维度,则必须让用户确认。

每次重写都从第一层重新走完整链路,不能继承上一次“部分通过”的安全状态。重试次数受预算控制,连续产生同类失败时停止,避免模型在 SQL 变体中循环。

这一区分同时改善可用性和安全性:可修复的格式错误不必让用户承担,越权与语义变化又不会被系统静默绕过。

怎样测试 SQL 安全链路

语法与 AST 样本覆盖多语句、注释混淆、嵌套写操作、系统表、危险函数、外部访问、递归和异常 CTE。

Schema 样本覆盖不存在的表列、同名列、错误别名、缺失 Join、笛卡尔积、多对多膨胀和旧版本 SQL。

权限样本覆盖跨租户、无权字段、脱敏字段、结果导出和错误信息探测。把应用校验故意关闭一层,数据库只读与行级策略仍应挡住关键越权。

资源样本覆盖无过滤扫描、超大排序、高基数聚合、慢函数、超时、并发和返回过大。结果样本覆盖空集、重复、异常量级和敏感列意外出现。

写操作样本确认普通 Text2SQL 入口全部拒绝,独立动作工具的 Dry-run、审批、幂等和超时恢复可验证。

评测报告按拒绝层和误拒绝分类。安全规则不能只追求“全拦”,合法高频查询也要可用。阈值和允许语法通过真实 Query 回放持续校准。

SQL 风险与处理方式矩阵

语法、权限、资源、结果和副作用风险需要不同控制。

常见的五个误区

第一,Prompt 说只读就等于只读。真正只读靠 AST、账号和数据库策略。

第二,屏蔽几个危险关键词就安全。SQL 方言和嵌套结构能轻易绕过字符串匹配。

第三,SQL 执行成功就算正确。错误口径和错误 Join 同样能返回结果。

第四,统一追加 LIMIT 就不会拖库。聚合和排序仍可能扫描大量数据,且字符串追加可能破坏语义。

第五,测试库安全就可以直连生产。测试数据量、权限和执行计划都不同,正式环境仍需只读隔离和资源护栏。

Text2SQL 安全上线验收清单

模型输出从生成到结果展示,每一层都有独立证据。

面试官会怎样连续追问

追问一:只允许 SELECT 就安全吗

不安全。还要检查多语句、系统表、危险函数、外部访问、权限、Join 和查询成本,并使用最小权限只读账号。

追问二:SQL 注入怎么防

用户值使用驱动参数绑定,表列等标识符只能来自白名单;模型输出整体按 AST 校验,不能靠字符串拼接或关键词过滤。

追问三:怎样防止一个查询拖垮数据库

使用只读隔离环境、超时、并发和资源限制,AST 层结果上限,对复杂查询做 Explain 或成本预检,并通过回放校准规则。

追问四:租户条件让模型写进 WHERE 行不行

不够。租户与行级权限由数据库策略或受控查询层强制,模型不能拥有移除权限条件的能力。

追问五:为什么结果还要校验

执行成功可能返回错误粒度、重复行、异常量级或敏感字段。结果层要验证预期 Schema、范围、脱敏和业务合理性。

追问六:如果业务确实需要写数据库怎么办

拆成明确业务动作工具,使用最小参数、Dry-run、审批、幂等和审计,不开放任意 SQL 写入。

从 SQL 候选到安全结果:完整回答

Text2SQL 的模型输出我会当作不可信查询计划。先用目标方言 Parser 转 AST,要求单条只读语句并白名单语法;再验证表、列、函数、Join 与 Schema 版本,当前用户的行列权限由后端或数据库策略强制。值全部参数化,标识符来自白名单。执行前加结果上限、超时、并发与成本预检,使用最小权限账号和只读副本或分析服务。结果返回后还要检查字段、行数、敏感数据、重复和业务量级。写操作不走普通 Text2SQL,而是独立动作工具加 Dry-run、审批与幂等。Trace 记录每一层的首个拒绝原因,评测覆盖恶意语法、越权、慢查询、结果泄露和合法查询误拒绝。

SQL 放行链路是否可信:十一项核验

  1. 模型是否只生成候选计划,不持有数据库凭证。
  2. 是否使用目标方言 Parser 和 AST 白名单,而非关键词黑名单。
  3. 是否拒绝多语句、写操作、危险函数、系统表和外部访问。
  4. 表列、Join 与 Schema 版本是否逐项验证。
  5. 用户值是否参数化,标识符是否只来自白名单。
  6. 租户、行列权限是否由数据库或后端强制。
  7. 是否有只读隔离、超时、并发、结果上限与成本预检。
  8. 返回结果是否检查敏感列、粒度、重复和异常范围。
  9. 写操作是否拆成带 Dry-run、审批和幂等的独立工具。
  10. Trace 与评测是否能区分语法、权限、成本、执行和结果失败。
  11. 没有真实数据时,是否避免虚构阈值、延迟和安全效果。

Text2SQL 需要一条从自然语言到数据库结果的纵深防御,单个正则表达式承担不了这项工作。模型负责提出查询,系统负责决定它是否可理解、可授权、可承受、可执行和可展示;每次放行或拒绝还要留下可回查的规则版本与首个失败原因。

上线后还应把被拒绝的合法查询与被放行的高风险查询分开复盘:前者用于降低误拒绝,后者用于补规则和测试集。只有两类错误都可追踪,安全策略才不会在“过度拦截”和“默认放行”之间来回摆动。

INTERVIEW FOLLOW-UPS

面试官会怎样继续追问?

这些追问会继续检查方案取舍、验证方法和项目事实。
  1. Q01

    为什么只允许 SELECT 仍然不够安全?

  2. Q02

    SQL 注入、跨租户和敏感列泄露分别怎么防?

  3. Q03

    怎样在执行前拦住全表扫描和高成本查询?

  4. Q04

    业务必须写数据库时为什么要拆成独立动作工具?

PROJECT CHECK

换成你的项目,这几件事能否说清楚?

  • 用目标方言 Parser 和 AST 白名单校验
  • 强制 Schema、行列权限、参数化和只读账号
  • 设置成本预检、资源护栏与结果审查
  • 把写操作拆成 Dry-run、审批和幂等工具
用自己的项目经历练一次