Mikhail Shytsko:你的AI代理能关掉自己的“终止开关”

Mikhail Shytsko:你的AI代理能关掉自己的“终止开关”

💡 原文英文,约4100词,阅读约需15分钟。
📝

内容提要

为AI代理设置PostgreSQL角色时,ALTER ROLE设定的statement_timeout、只读事务等参数均属USERSET,会话可用SET或连接串options覆盖且日志无痕,REVOKE SET对USERSET参数无效。有效控制须在会话外进行:CONNECTION LIMIT、NOLOGIN、对象授权、pg_hba.conf,以及独立会话的pg_terminate_backend看门狗(按usename过滤)。

🔎

延伸解读

会话内参数控制为何形同虚设

文章通过实验表明,ALTER ROLE 设置的 statement_timeout、default_transaction_read_only 等参数均为 USERSET 上下文,会话内可用 SET 或连接串 options 覆盖,且日志无痕。REVOKE SET 对 USERSET 参数无效,因为 pg_parameter_acl 不会记录。这意味着任何依赖角色默认值来限制 AI 代理行为的做法,都只是给守规矩的客户端一个默认值,无法阻止有意绕过。

真正有效的控制点在哪里

文章指出,能实际约束会话的控制必须在会话外部求值:CONNECTION LIMIT 和 NOLOGIN 在认证时检查,pg_hba.conf 在 SQL 解析前决定访问,对象授权由服务器逐语句检查。此外,独立会话的看门狗通过 pg_terminate_backend 终止后端,按 usename 过滤而非 application_name,因为后者可被 SET 修改。这些控制共同点是会话无法触及。

超时参数的实际效果差异

文章对比了五种超时:statement_timeout 和 lock_timeout 仅取消当前语句,连接存活;idle_in_transaction_session_timeout、idle_session_timeout 和 transaction_timeout 会终止会话。transaction_timeout 在 PostgreSQL 17 引入,16 上不存在,导致由短语句组成的长事务在 16 上无法被任何每角色参数限制。因此,选择超时参数时需明确目标是取消语句还是结束会话。

只读角色与数据外泄风险

文章测试了两个公开的只读角色配方,发现它们能阻止表写入和服务器端 COPY TO PROGRAM,但无法阻止客户端 \copy ... TO 将数据导出到文件,因为那只是普通 SELECT。此外,USAGE ON ALL SEQUENCES 授予了 nextval,可推进序列。pg_read_all_data 不覆盖大对象。因此,只读角色不能防止数据外泄,控制点在于客户端主机和读取权限的精细授予。

Q&A

为什么用 ALTER ROLE 给 AI 代理设置 statement_timeout 和只读事务后,代理还能自己关掉这些限制?

因为 statement_timeout 和 default_transaction_read_only 等参数的 context 是 user(USERSET),意味着每个角色都可以在自己的会话中用 SET 覆盖它们。代理只需执行 SET default_transaction_read_only = off 或 SET statement_timeout = 0,就能在会话内解除限制,且日志中可能没有明显痕迹。

REVOKE SET ON PARAMETER statement_timeout 能阻止代理修改超时吗?

不能。statement_timeout 是 USERSET 参数,所有角色默认就有权设置。REVOKE SET ON PARAMETER 虽然会被接受,但不会在 pg_parameter_acl 中记录任何内容,也不会改变行为。该权限只对需要超级用户权限的参数(SUSET)有效。

代理如何通过连接字符串绕过角色上设置的 statement_timeout?

代理可以在连接字符串的 options 参数中传入 -c statement_timeout=0,例如 postgresql://agent@localhost:5432/t2?options=-c%20statement_timeout%3D0。这些选项在启动包中应用,会覆盖角色设置,且会话不会发出任何 SET 语句,日志中无记录。也可以通过 PGOPTIONS 环境变量实现。

PostgreSQL 中哪些超时只会取消语句,哪些会终止整个会话?

statement_timeout 和 lock_timeout 只取消当前语句,会话保持存活;idle_in_transaction_session_timeout、idle_session_timeout 和 transaction_timeout(PG17+)会终止整个会话。

pg_cancel_backend 和 pg_terminate_backend 有什么区别?在 watchdog 中应该用哪个?

pg_cancel_backend(pid) 只取消当前运行的语句,连接保持打开,代理可以继续执行下一条语句。pg_terminate_backend(pid) 会终止整个后端,客户端收到 FATAL 错误。在自动化 watchdog 中应使用双参数形式 pg_terminate_backend(pid, timeout_ms),它会等待进程真正退出,返回 true 表示会话已消失。

为什么 watchdog 应该按 usename 而不是 application_name 过滤代理会话?

application_name 是 USERSET 参数,代理可以随时用 SET 修改,从而逃避基于 application_name 的过滤。usename 在认证时记录,不是参数,无法被会话修改,因此更可靠。

只读角色能阻止数据通过 \copy 导出吗?

不能。客户端 \copy ... TO 本质上是一个 SELECT,将行写入客户端机器上的文件,只需要对表的读权限,不需要额外特权。任何能读取表的角色都可以将表导出到文件。服务器端的 COPY ... TO PROGRAM 和 large object 函数会被拒绝,但客户端 \copy 无法被只读授权阻止。

transaction_timeout 在 PostgreSQL 16 上可用吗?

不可用。transaction_timeout 在 PostgreSQL 17 中引入。在 16 上,该参数不在 pg_settings 中,设置会报 unrecognized configuration parameter,连接时传入会导致连接失败。这留下一个缺口:由短语句组成的事务不会触发 statement_timeout,如果间隙是服务器端工作而非客户端空闲,idle_in_transaction_session_timeout 也不会触发,因此事务总时长在 16 上不受任何角色级参数限制。

🏷️

标签

➡️

继续阅读