给 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_settings 的 source 字段能帮助定位覆盖链路。常见来源包括:
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 = user:statement_timeout、transaction_timeout、lock_timeout、两个 idle timeout、default_transaction_read_only 和 application_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、函数和序列的对象权限:没有
INSERT、UPDATE、CREATE等权限时,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 数据库接入做一次现实检查
上线前可以按这份清单验证:
- 用 Agent 实际使用的连接字符串、环境变量和连接池参数建立连接。
- 查询
pg_settings,记录每个限制参数的setting、source和context。 - 测试
SET statement_timeout = 0和SET default_transaction_read_only = off是否成功。 - 验证 Agent 角色是否拥有不必要的
CREATE、INSERT、UPDATE、DELETE、函数执行或扩展安装权限。 - 配置连接数上限,并准备一个 Agent 无法访问的监控或终止会话。
- 不把
ALTER ROLE SET、ORM 默认配置或日志中出现的角色配置当成强制隔离证明。
核心结论很直接:ALTER ROLE ... SET 只能规定会话的起点。如果 Agent 使用的会话拥有修改参数的能力,那么它可以在第一条业务语句前关闭自己的超时和只读默认值。真正需要保护的边界,应由对象权限、连接资源限制以及会话外部的运维控制来承担。