# 安全 SQL 护栏

Canonical URL: https://pg.edu.rich/docs/ai/safe-sql

Last reviewed: 2026-08-02





## 数据库角色是第一道边界 [#数据库角色是第一道边界]

```sql
CREATE ROLE agent_reader LOGIN;
GRANT CONNECT ON DATABASE commerce TO agent_reader;
GRANT USAGE ON SCHEMA app TO agent_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO agent_reader;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
  GRANT SELECT ON TABLES TO agent_reader;

ALTER ROLE agent_reader SET default_transaction_read_only = on;
ALTER ROLE agent_reader SET statement_timeout = '5s';
ALTER ROLE agent_reader SET lock_timeout = '1s';
ALTER ROLE agent_reader SET idle_in_transaction_session_timeout = '10s';
```

凭据由 secret manager 或云身份集成配置，不要保存在迁移文件、prompt 或工具响应中。确认该角色不能 `SET ROLE` 到更高权限角色。

## 执行前策略 [#执行前策略]

对模型生成的 SQL 做 parser/AST 级校验，不用正则代替解析器。默认规则：

* 只允许单条 `SELECT`。
* 拒绝 `COPY ... PROGRAM`、大对象、外部数据包装器和危险函数。
* 拒绝多个语句和注释绕过。
* 限制可访问 schema、表和列。
* 强制参数绑定；标识符只能从白名单选择。
* 对非聚合结果施加 `LIMIT`，同时在驱动层设置最大返回字节数。
* 执行 `EXPLAIN (FORMAT JSON)` 做成本预检时，不能把成本估算当作时间保证。

## 每次读取使用只读事务 [#每次读取使用只读事务]

```sql
BEGIN READ ONLY;
SET LOCAL statement_timeout = '5s';
SET LOCAL lock_timeout = '1s';
SET LOCAL search_path = app, pg_catalog;

SELECT id, status, total_cents
FROM orders
WHERE customer_id = $1
ORDER BY placed_at DESC
LIMIT 100;

COMMIT;
```

只读事务仍可能运行昂贵查询并泄露可读取的数据，所以权限、成本和结果限制缺一不可。

## 写操作不要开放任意 SQL [#写操作不要开放任意-sql]

优先向 Agent 暴露领域工具：

```json
{
  "tool": "cancel_order",
  "arguments": {
    "order_id": 8842,
    "expected_status": "pending",
    "reason": "duplicate order",
    "idempotency_key": "case-2026-184"
  }
}
```

应用服务验证身份与状态转换，在事务中执行参数化 SQL，并返回明确结果。批量写、DDL、`GRANT`、备份恢复和复制配置不应暴露给通用 Agent。

## 审计字段 [#审计字段]

至少记录：请求者/租户、工具名、模型与提示版本、契约版本、数据库目标、SQL 指纹（参数脱敏）、风险级别、审批者、行数、耗时、SQLSTATE、是否截断。不要把原始敏感结果复制进普通日志。

<Callout type="warn" title="行数检查不能替代事务设计">
  先执行写入再发现“影响太多行”可能已经触发触发器或产生外部事件。应在受控事务里预览目标集，或通过领域 API 将可修改集合限制在查询本身。
</Callout>
