当 Agent 暴露 run_sql、query_database 或 NL2SQL 类 MCP 工具时,平台进程往往持有比普通应用更高的数据库可达性:同一连接串可能触达分析库、含 PII 的副本,甚至与业务服务共享的 PostgreSQL 实例。模型输出的 SQL 属于不可信程序—— 除经典注入(拼接 '; DROP)外,只读查询仍可造成全表顺序扫描、长时间持锁、COPY (SELECT …) TO STDOUT 拉取大量行导致内存与出口带宽被打满。PostgreSQL 文档明确建议:statement_timeout、lock_timeout、idle_in_transaction_session_timeout 等参数应在会话级设置,而非全局写入 postgresql.conf,以免影响运维会话与迁移任务。本文讨论在不改动业务表结构语义的前提下,用角色权限 + 会话 GUC +(可选)RLS 为 Agent 工具链搭一层数据库侧边界;不覆盖「完全禁止任意 SQL、仅允许预注册模板」的强沙箱(该路线成本更高但更安全)。
问题背景:工具链把「DBA 能力」交给了不可信文本
典型数据流:
- 编排器向模型注册 SQL 工具,描述中常写「只读」「仅 SELECT」—— 这不是数据库强制约束。
- 模型返回一条或多条 SQL 字符串;网关可能做字符串黑名单后,用应用主库连接或只读副本连接执行。
- 结果集序列化进上下文,驱动下一轮推理;失败时错误信息(含表名、列名)可能反喂模型,形成信息泄露通道。
与面向人类的 BI 产品不同,Agent 场景还有:
- 高频重试:校验失败或空结果会触发多轮
run_sql,连接池与max_connections易被短时打满。 - 长事务习惯:部分框架在 tool 调用前后未显式
COMMIT,会话停留在idle in transaction,阻塞 vacuum 并占用连接(idle_in_transaction_session_timeout正是为此设计)。 - 多租户混用:若所有租户共用一个
agent_reader角色且无 RLS,一次提示注入可能读到邻租户行。 - 副本与主库混用:分析 Agent 连只读副本仍可对副本施加极高 I/O;若误连主库且权限过宽,只读标志也挡不住
INSERT(除非权限与 GUC 同时收紧)。
因此需要把防御拆成:身份(角色)、会话行为(GUC)、数据范围(RLS)、网关策略(语句形态与结果上限) 四层,且与 MCP/HTTP 工具的 SSRF、写操作审批正交。
可落地实现:角色、池化 checkout 与会话 GUC
1. 专用角色与权限基线
为 Agent 单独建角色,不要复用应用 runtime 的数据库用户。
CREATE ROLE agent_sql_runner NOLOGIN;
REVOKE ALL ON SCHEMA public FROM agent_sql_runner;
GRANT USAGE ON SCHEMA app_analytics TO agent_sql_runner;
GRANT SELECT ON ALL TABLES IN SCHEMA app_analytics TO agent_sql_runner;
ALTER DEFAULT PRIVILEGES IN SCHEMA app_analytics
GRANT SELECT ON TABLES TO agent_sql_runner;
建议同步落实:
| 项 | 建议 | 说明 |
|---|---|---|
| 登录载体 | LOGIN 角色 agent_pool_login 仅 GRANT agent_sql_runner |
应用持登录角色,执行前 SET ROLE agent_sql_runner |
CREATE |
对 Agent 可见 schema 显式 REVOKE CREATE |
PostgreSQL 15+ 默认撤销 PUBLIC 在 public 上的 CREATE,其他 schema 仍需手工收紧 |
| 扩展 | 不授予 pg_execute_server_program 等危险权限;不安装 dblink/file_fdw 给 Agent 角色 |
扩展函数可能打开出站或读文件路径 |
| 序列 | 只读分析通常 GRANT SELECT 即可;避免 USAGE on sequence 若担心侧信道 |
视审计要求而定 |
| 视图 | 优先只授视图不授基表 | 视图可隐藏列;须检查 security_invoker / 定义者权限(见风险节) |
default_transaction_read_only 设为 on 时,事务默认拒绝写语句;PostgreSQL 说明该参数在每个事务开始时继承,后续修改等价于 SET TRANSACTION。应对每条工具调用使用短事务:BEGIN READ ONLY → 单条查询 → COMMIT。
2. 会话级超时与只读 GUC(推荐默认值)
下列参数应通过 ALTER ROLE agent_sql_runner SET … 或连接建立后 SET / SET LOCAL 生效(单位均为毫秒,除非使用带单位的字符串形式):
| 参数 | 生产建议 | 作用 |
|---|---|---|
default_transaction_read_only |
on |
默认只读事务 |
statement_timeout |
15000(15s);重型分析租户可单独角色调到 60000 |
终止运行过久的单条语句 |
lock_timeout |
3000(3s) |
避免排队等锁拖住 Agent 会话 |
idle_in_transaction_session_timeout |
60000(60s) |
清理 tool 异常未提交事务 |
transaction_timeout |
30000(30s,PG 14+) |
限制单事务总时长;文档注明若 ≤ statement_timeout 则较长者被忽略 |
search_path |
app_analytics(显式 schema 列表,不含 $user) |
降低搜索路径劫持风险 |
row_security |
on(默认) |
强制 RLS 生效;勿对 Agent 会话关闭 |
角色级设置示例:
ALTER ROLE agent_sql_runner SET default_transaction_read_only = on;
ALTER ROLE agent_sql_runner SET statement_timeout = '15s';
ALTER ROLE agent_sql_runner SET lock_timeout = '3s';
ALTER ROLE agent_sql_runner SET idle_in_transaction_session_timeout = '60s';
ALTER ROLE agent_sql_runner SET transaction_timeout = '30s';
ALTER ROLE agent_sql_runner SET search_path = app_analytics;
网关在每个 tool 调用事务内可再收紧(SET LOCAL 仅当前事务有效):
BEGIN READ ONLY;
SET LOCAL statement_timeout = '10s';
SELECT set_config('app.tenant_id', 'tenant-uuid', true); -- 事务级 GUC,供 RLS 使用
-- 执行单条受控 SELECT
COMMIT;
3. 连接池:PgBouncer 与 checkout 钩子
应用侧连接池(Go database/sql、HikariCP、node-pg)应在 checkout 时重置会话状态,避免上一请求残留 SET ROLE 或 search_path:
DISCARD ALL(或至少RESET ALL+CLOSE ALL);SET ROLE agent_sql_runner;- 可选:再次
SET关键 GUC(若池模式导致角色级 SET 未生效)。
PgBouncer transaction pooling 下,会话级 SET 不能假设跨事务保留;官方文档要求在事务池模式下于每个事务重新应用设置,或使用 connect_query(需评估安全与可维护性)。对 Agent SQL 工具更稳妥的是:
| 池模式 | 建议 |
|---|---|
| Session pooling | 适合 Agent 短连接 burst;注意池大小与 max_client_conn |
| Transaction pooling | 必须在每条 SQL 外包 BEGIN/COMMIT 且事务内 SET LOCAL |
| Statement pooling | 不推荐用于需要 GUC/RLS 的 Agent 查询 |
池容量建议与 Agent 并发解耦:
| 配置 | 建议值 | 说明 |
|---|---|---|
| 每租户最大并发 SQL 工具 | 2 | 防止单 run 多步刷库 |
池 max_size |
≤ 租户并发 × 副本数 | 与 max_connections 留 20% 给人工与批作业 |
| 单结果集行数上限 | 500(网关截断) | 超出则只返回前 N 行 + truncated: true |
| 单单元格字节上限 | 4 KiB | 防止 JSON 列撑爆上下文 |
| 单 tool 调用语句数 | 1 | 禁止多语句字符串;网关用解析器拒绝 ; 后第二语句 |
4. RLS 多租户:会话 GUC + 策略
PostgreSQL 行级安全文档说明:表启用 ENABLE ROW LEVEL SECURITY 后,普通 SELECT/UPDATE 须通过 policy;表属主默认不受 RLS 约束。Agent 角色必须是非属主的 SELECT 授予,策略才能生效。
ALTER TABLE app_analytics.events ENABLE ROW LEVEL SECURITY;
CREATE POLICY agent_tenant_select ON app_analytics.events
FOR SELECT TO agent_sql_runner
USING (tenant_id = current_setting('app.tenant_id', true)::uuid);
网关在事务开始时:
SELECT set_config('app.tenant_id', $1, true); -- true = LOCAL to transaction
注意:current_setting(..., true) 在缺失时返回 NULL 而非报错;生产应约定未设置 tenant GUC 则拒绝执行,由网关先校验租户上下文,而不是依赖隐式空结果。
5. 网关层语句形态(与 DB 沙箱互补)
数据库 GUC 无法把「只允许 SELECT」变成语法定理。建议在网关增加轻量规则(可用 pg_query 解析 PostgreSQL 语法树,或受限方言解析器):
| 规则 | 处置 |
|---|---|
仅允许 SelectStmt |
拒绝 INSERT/UPDATE/DELETE/Copy/Call |
拒绝子查询中的 FOR UPDATE |
避免锁争用 |
拒绝访问 pg_catalog、information_schema(可选白名单) |
降低 schema 枚举 |
强制 LIMIT 注入 |
若 AST 无 LIMIT,追加 LIMIT 500 |
只读副本上仍应使用同一套角色与 statement_timeout,避免分析查询拖垮副本延迟。
风险与边界
default_transaction_read_only 不是权限替代品。 角色若被误授 INSERT,只读事务会在执行时失败,但已暴露错误信息;应依靠 GRANT 最小化而非仅靠 GUC。
属主与 SECURITY DEFINER 绕过。 表属主不受 RLS 约束;SECURITY DEFINER 函数以定义者权限执行,可能绕过 Agent 角色的 RLS。Agent 角色不应能 EXECUTE 此类函数,除非逐函数审计。
昂贵只读查询。 statement_timeout 终止语句,但高并发短查询仍可能打满 CPU;需结合网关并发配额、副本隔离与 pg_stat_statements 告警。
COPY TO STDOUT。 在只读事务下仍可能导出大量行;除 LIMIT 外,应在网关限制结果序列化大小,并视情况拒绝 CopyStmt。
错误信息泄露。 PostgreSQL 错误含关系名、约束名;回灌模型前应脱敏或映射为错误码(与 MCP Tool Execution Error 策略一致)。
Transaction pooling 与 SET 漂移。 若未 SET LOCAL 或 checkout 未 DISCARD ALL,可能出现租户 GUC 串租;属高危配置错误,需集成测试覆盖。
解析器与方言差距。 网关 AST 规则可能漏掉新型语法或扩展语句;数据库侧角色权限仍是最后防线。
人工运维会话勿共用 Agent 角色。 ALTER ROLE … SET statement_timeout 会影响该角色发起的所有会话;运维应使用独立角色。
参考来源
- PostgreSQL Documentation — Client Connection Defaults (
statement_timeout,lock_timeout,idle_in_transaction_session_timeout,transaction_timeout,default_transaction_read_only,row_security) - PostgreSQL Documentation — Row Security Policies
- PostgreSQL Documentation —
SET ROLE - PostgreSQL Documentation —
SET TRANSACTION - PostgreSQL Documentation — Schema privileges and
search_path - PgBouncer — Pool modes and connection settings
- Model Context Protocol — Tools(Tool Execution Error 与输入校验责任)
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。