你的 AI Agent 可以自己关掉数据库的“熔断开关”

2026-09-11 31 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

给 AI Agent 配一个专用 PostgreSQL 角色时,很多团队会执行两条熟悉的加固命令:限制单条语句的执行时间,并让事务默认只读。

ALTER ROLE agent SET statement_timeout = '2s';
ALTER ROLE agent SET default_transaction_read_only = on;

SELECT rolname, rolconfig
FROM pg_roles
WHERE rolname = 'agent';

从管理员视角看,角色配置已经被限制:查询最多运行两秒,事务默认不能写入。但这份安全感有一个关键前提:客户端不能在连接建立时覆盖这些参数。

ALTER ROLE SET 不是不可变策略

ALTER ROLE ... SET 写入的是会话启动时的默认值,不是数据库层面的强制约束。连接创建后,会话本身仍然可以修改这些参数。

下面的连接可以直接关闭只读默认值:

SET default_transaction_read_only = off;

SHOW default_transaction_read_only;

CREATE TABLE app.via_set (i int);
INSERT INTO app.via_set VALUES (1);

同样,statement_timeout 也可以被会话改写:

SET statement_timeout = 0;
SELECT pg_sleep(4);

在角色默认值为 statement_timeout = '2s' 的普通连接中,pg_sleep(4) 会在大约两秒后被取消;但执行上述 SET 后,四秒睡眠就能正常完成。

可以用 pg_settings 看清楚这个参数到底来自哪里:

SELECT name, setting, source, context
FROM pg_settings
WHERE name IN ('statement_timeout', 'default_transaction_read_only')
ORDER BY name;

典型结果会显示:

          name           | setting | source  | context
-------------------------+---------+---------+---------
 default_transaction_read_only | off | session | user
 statement_timeout       | 0       | session | user

这里最值得注意的是 context = user。它表示普通会话用户可以设置该参数。ALTER ROLE SET 影响的是初始值,而不是会话之后能否改变它。

更安静的绕过方式:启动包参数

直接执行 SET 至少会留下 SQL 痕迹。PostgreSQL 的 libpq 客户端还支持通过连接参数 options,在启动包中传递 -c 参数:

postgresql://agent@localhost:5432/t2?options=-c%20statement_timeout%3D0%20-c%20default_transaction_read_only%3Doff

连接建立时,客户端参数会在角色设置加载后应用。因此服务器端的 rolconfig 不会改变,但新会话看到的设置已经不同:

SELECT name, setting, source, context
FROM pg_settings
WHERE name = 'statement_timeout';

SHOW default_transaction_read_only;

结果可能是:

name               | setting | source | context
-------------------+---------+--------+---------
statement_timeout  | 0       | client | user

这时执行:

SELECT pg_sleep(4);

不会在两秒处取消,而是完整运行约四秒。整个过程中,客户端不需要向服务器发送一条显式的 SET statement_timeout 语句。

对于 libpq 兼容客户端,还可以使用 PGOPTIONS

PGOPTIONS="-c statement_timeout=0 -c default_transaction_read_only=off" \
  psql "postgresql://agent@localhost:5432/t2"

psql、许多 Python PostgreSQL 客户端以及其他基于 libpq 的工具都可能读取这个环境变量。实际部署中,除了检查 SQL 和 ORM 配置,也应检查容器环境变量、连接池参数和 Secret 中的 DSN。

参数来源比参数值更重要

pg_settingssource 字段能帮助定位覆盖链路。常见来源包括:

  • user:来自角色或数据库级默认配置。
  • session:当前会话执行了 SET
  • client:客户端在连接启动参数中传入了设置。

客户端启动参数的优先级高于角色的 user 默认值,所以即使管理员看到:

statement_timeout=2s

这个事实也不能证明 Agent 的每个连接都真的受到两秒限制。审计时应同时记录以下信息:

SELECT
  current_user,
  current_database(),
  inet_client_addr(),
  name,
  setting,
  source,
  context
FROM pg_settings
WHERE name IN (
  'statement_timeout',
  'transaction_timeout',
  'lock_timeout',
  'idle_in_transaction_session_timeout',
  'idle_session_timeout',
  'default_transaction_read_only',
  'application_name'
)
ORDER BY name;

摘要中提到的这些参数都具有 context = userstatement_timeouttransaction_timeoutlock_timeout、两个 idle timeout、default_transaction_read_onlyapplication_name。它们适合用作默认行为或运行时提示,但不应被当成无法绕过的隔离边界。

GRANT SET 的误解

PostgreSQL 支持参数级权限控制,但它只在通常需要超级用户权限的参数上有实际意义。对 statement_timeout 撤销 SET 权限,并不会自动生成一条有效的 pg_parameter_acl 记录,也不能阻止普通 Agent 会话修改这个参数。

换句话说,下面这种思路不能解决问题:

REVOKE SET ON PARAMETER statement_timeout FROM agent;

在设计 Agent 数据库权限时,应先确认目标参数的权限语义,而不是看到 REVOKE 成功执行就认为限制已经生效。

真正有效的边界放在哪里

在相同的测试环境中,几类约束仍然有效,但它们依赖的是数据库对象和其他会话,而不是 Agent 自己承诺遵守的会话参数:

  • CONNECTION LIMIT:限制角色可建立的连接数量。
  • 对表、Schema、函数和序列的对象权限:没有 INSERTUPDATECREATE 等权限时,Agent 即使关闭只读默认值,也无法获得这些权限。
  • 第二个受信任的管理会话调用 pg_terminate_backend:目标 Agent 会话无法替自己执行这类外部终止操作。

例如,可以把 Agent 角色设计成没有 Schema 创建权限,并把业务表权限单独授予:

CREATE ROLE agent LOGIN PASSWORD 'replace-this-password'
  CONNECTION LIMIT 5;

REVOKE ALL ON SCHEMA public FROM agent;
REVOKE CREATE ON SCHEMA app FROM agent;
GRANT USAGE ON SCHEMA app TO agent;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO agent;

ALTER ROLE agent SET statement_timeout = '2s';
ALTER ROLE agent SET default_transaction_read_only = on;

这段配置中的超时和只读设置仍然可能被会话覆盖,但对象权限不会因为 SET default_transaction_read_only = off 就凭空增加。若 Agent 需要执行少量写操作,应只授予明确的表、列或存储过程权限,并把高风险操作放到由受控服务调用的函数中。

给 Agent 数据库接入做一次现实检查

上线前可以按这份清单验证:

  1. 用 Agent 实际使用的连接字符串、环境变量和连接池参数建立连接。
  2. 查询 pg_settings,记录每个限制参数的 settingsourcecontext
  3. 测试 SET statement_timeout = 0SET default_transaction_read_only = off 是否成功。
  4. 验证 Agent 角色是否拥有不必要的 CREATEINSERTUPDATEDELETE、函数执行或扩展安装权限。
  5. 配置连接数上限,并准备一个 Agent 无法访问的监控或终止会话。
  6. 不把 ALTER ROLE SET、ORM 默认配置或日志中出现的角色配置当成强制隔离证明。

核心结论很直接:ALTER ROLE ... SET 只能规定会话的起点。如果 Agent 使用的会话拥有修改参数的能力,那么它可以在第一条业务语句前关闭自己的超时和只读默认值。真正需要保护的边界,应由对象权限、连接资源限制以及会话外部的运维控制来承担。


相关推荐