SQLFix 博客

自然语言到SQL:重要的安全检查

将问题翻译成SQL对于初稿确实很有用,但自动执行模型输出到活跃数据库可能会导致一个打字错误变成删除表。安全的做法是仅草稿:模型提出,人类或受保护的管道处理。SQLForm遵循这种仅草稿的模型,且在没有审查步骤的情况下永远不会运行它生成的内容。

1. 从草稿管道开始

我围绕着语言模型是一个草稿助手,而不是执行引擎的想法构建了 SQLForm。当用户要求“显示上个月的销售额按地区划分”时,模型返回一个 SELECT 语句。该语句被放入草稿队列中,并且永远不会发送到生产连接器。人类操作员或自动化门户必须显式地批准草稿,然后才能离开队列。这种分离创建了一个清晰的审计跟踪:每个触及数据的查询都有一个记录的批准步骤。

草稿队列被保存在一个轻量级表中,包含用户 ID、时间戳、原始提示、生成的 SQL 和批准状态的列。如果系统崩溃,队列可以被重放,且不会丢失任何查询。我选择了这种设计是在看到团队由于演示脚本运行了模型认为是 SELECT 语句的 DELETE 语句而丢失数据之后。队列通过构造防止了这种类别的错误。

2. 在语句到达队列之前检测变异语句

第一道防线是一个简单的词法扫描。我查找DELETE、UPDATE、INSERT、DROP、TRUNCATE、ALTER、GRANT、REVOKE和CREATE等令牌。如果出现任何这些令牌,草稿将被标记为变异并放入需要高级审查员明确批准的暂存状态。即使是包含调用变异函数的WITH子句的SELECT语句也会被捕获,因为扫描是在任何解析之前对原始SQL字符串执行的。

我还检查语句分隔符。分号后跟着另一个令牌意味着用户可能正在尝试链接操作。例如,“SELECT * FROM users;DELETE FROM logs”将通过一个简单的SELECT-only检查,但由于扫描器在第一个分号后看到DELETE令牌而被阻止。

为了避免对合法对象名称(如名为“delete_flag”的列)的误报,扫描器仅在令牌后跟着空白字符或括号时将其视为关键字,匹配典型的SQL语法。这使得规则精确而不会禁止有用的标识符。

3. 在只读沙盒中运行草稿

一旦草稿被批准,我就会在生产模式的只读副本中执行它。副本每晚从主副本刷新,并且没有为 SQLForm 使用的连接角色分配写入权限。如果模型产生了某种变异语句,数据库将返回错误,且不会更改任何数据。这将潜在的昂贵错误转化为审查者可以立即看到的可见失败。

我还在连接级别强制执行只读属性,方法是为会话设置 PostgreSQL 参数 default_transaction_read_only = on。在 MySQL 中,我使用 SET SESSION TRANSACTION ISOLATION LEVEL READ ONLY; 启动会话。这些设置会导致服务器用明确的错误代码拒绝任何 WRITE、ALTER 或 DROP 命令,我会在 UI 中显示这些错误。

使用副本还有一个额外的好处:查询可以在不影响 SLA 的情况下运行在稍微过时的数据上,我可以在不冒着生产负载风险的情况下监控查询长度和资源使用情况。如果副本滞后超过可配置的阈值,系统将保持草稿并通知批准者数据可能不同步。

4. 参数化批准的查询

在草稿通过人工审查步骤后,我将 SQL 移动到应用层作为准备语句。模型生成的文本成为一个模板,其中任何用户提供的值都作为参数绑定。我从不在批准后连接原始字符串,因为这会重新引入草稿原本要避免的注入风险。

我遵循 OWASP SQL 注入防御备忘录的指导,该备忘录将准备语句列为主要防御措施。在实践中,我使用数据库驱动程序的本地 API:PostgreSQL 的 pg_prepare / pg_execute 和 MySQL 的 PreparedStatement。驱动程序负责转义和类型安全,因此包含引号或分号的值被视为数据,而不是 SQL。

为了确保模板保持有效,我使用轻量级 SQL 解析器(如 sqlparse)解析批准的 SQL,并验证每个占位符是否对应于参数索引。如果解析器找到一个看起来像值但未绑定的流浪令牌,则草稿被发送回进行修订。

5. 人工审查作为控制点

审查员不需要成为 SQL 专家;清单是故意设计得很短。首先,他们验证 SELECT 列表是否与原始提示中提到的列相匹配。如果用户要求“每个地区的总收入”而查询返回 COUNT(*),则审查员会标记它。其次,他们确认没有出现变异令牌,依赖扫描器同时也进行视觉扫描以检查任何巧妙的混淆。第三,他们查看 WHERE 子句,以查看是否意外地拉取了比预期更多的行(例如,缺少日期过滤器,导致全表扫描)。

我将生成的查询视为建议,正如我审查新同事编写的 SQL 一样。审查员可以批准、请求更改或拒绝。如果他们请求更改,他们可以直接在 UI 中编辑草稿;编辑后的版本成为一个新草稿,并再次经过相同的检查。这一循环确保模型的输出永远不是最终权威。

速度提高的原因是消除了从头开始编写样板 SQL 的需要。我已经看到团队将生产第一个草稿查询的时间从十分钟缩短到不到两分钟,同时仍然保持人类在安全方面的参与。

这不是什么

我并不声称 SQLForm 能够捕获每一个可能的错误。词法扫描器可以通过不寻常的编码或使用类似注释的数据库特定语法来规避。一个决心的攻击者可以制作一个提示,使得模型输出一个使用带有副作用的存储过程的语句,而扫描器不会标记它,因为过程名称不在我们的关键字列表中。只读副本可以保护免受意外写入的影响,但如果副本配置不正确并继承写入权限,安全网就会消失。

人工审查是必不可少的,但审查人员可能会错过微妙的逻辑错误,特别是当查询很长或使用复杂的窗口函数时。这个过程可以减少风险,但它并不能消除风险。我把 SQLForm 当作一个风险减少工具,而不是万能的解决方案。

参考资料

https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html

https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html

https://cwe.mitre.org/data/definitions/798.html

https://cwe.mitre.org/data/definitions/89

常见问题

仅针对只读副本或具有阻止修改语句的硬门限。自动运行生成的SQL语句对生产环境可能导致草稿错误变为数据丢失。保持模型在仅建议角色中。

模型草稿在审查前属于不可信输入,与CWE-89 SQL注入属于同一类。只读角色意味着即使意外执行,也无法改变生产数据。

不。 一旦查询移动到应用程序代码中,请使用预备语句。将值连接回字符串中会重新引入注入风险。审查和参数化是独立的控制措施。

DELETE、UPDATE、DROP、TRUNCATE和权限授予语句不应在自动执行路径中运行。它们需要显式、独立的批准。

尝试产品:SQLForm

2026-09-16 · 仅主来源 · 不包含虚假流量指标

相关文章

参数化查询与生成的SQL

探索完整的AI微SaaS主题矩阵 →

NL2SQL 是否可以安全地自动运行?

仅在只读副本或具有阻止修改语句的硬闸门的情况下。自动运行生成的 SQL 对生产环境可能导致草稿错误变成数据丢失。将模型保持在仅建议角色中。

为什么只读上下文很重要? 模型草稿在审查之前是不可信的输入,与 CWE-89 SQL 注入属于同一类。只读角色意味着即使意外执行,也不会改变生产数据。 审查 SQL 是否意味着我可以跳过参数化?

模型草稿在审查之前是不可信的输入,与 CWE-89 SQL 注入属于同一类。只读角色意味着即使意外执行,也不会改变生产数据。

安全层应该默认阻止哪些语句?

DELETE、UPDATE、DROP、TRUNCATE 和特权授予语句不应该在自动执行路径中运行。它们需要显式、单独的批准。

仅供决策参考。不代替DBA审查或生产负载测试。无性能保证。

← 返回博客

这次结果有帮助吗?反馈