把生产库直连给大模型查数据,五分钟搞定;想把权限收回来

PromptCube 初级 1小时前 423 浏览 7 点赞 约 2 分钟

上周帮朋友排查一个 Text-to-SQL 事故:某实习生把只读账号密码扔给 Cursor 让它「帮忙看下表结构」,Cursor 顺手把 information_schema 翻了个底朝天,触发了云厂商的「异常连接熔断」,主库只读副本挂了 40 分钟。事后复盘最讽刺的是——撤销那个账号的权限,比申请权限时多跑了六个审批流,还得 DBA 手动 REVOKE、清连接池、改 Terraform、轮换 Secret,前后耗时 36 小时。

一、权限收不回,本质是「临时」变成了「永久」

大多数团队给 LLM 配数据库账号,图省事全走「超级只读」或干脆 db_owner。理由总是一样的:「反正只是让它看 Schema、跑 SELECT,又不改数据。」
但 LLM 不懂业务边界,它会把 pg_stat_activity、慢查询视图、甚至 pg_catalog.pg_authid 当成上下文塞进 Prompt。更坑的是连接池复用:PgBouncer 里那条闲置连接可能还握着上一个 Session 的事务快照,REVOKE 执行完老连接照样能查,必须 pg_terminate_backend 逐个杀 PID。上次我就在 pg_stat_activity 里看着十几个 idle in transaction 的幽灵进程,一个个 kill -9 才算彻底断干净。

二、审计链路一旦断裂,事后甩锅无从查起

把凭证塞进环境变量、.env、甚至直接写死在 Python 脚本里的团队不在少数。LLM 请求走的是应用层连接池,数据库层面只看到「应用服务器 IP + 共用账号」,根本分不清哪条 SELECT * FROM users 是真人发的、哪条是 Agent 幻觉出来的。
想查责?只能去翻应用层的请求日志、对齐时间戳、再跟模型调用链路做 Join。上个月有个案子:数据分析师用 LangChain 跑报表,模型把 LIMIT 100 幻觉成 LIMIT 1000000,把从库拖垮。事后追责花了两天,最后只能靠「那个时间段只有他调用过 API」定性。

三、Row Level Security(RLS)才是正解,但没人想配

PostgreSQL 15+ 的 RLS 策略配合 SET ROLE / SET SESSION AUTHORIZATION,能在连接池层面把租户隔离死。可现实是:

  • ORM(SQLAlchemy / Prisma / Django)对 SET ROLE 支持极差,得自己封装中间件
  • 连接池复用时 DISCARD ALL 会清掉 Session 变量,RLS 策略瞬间失效
  • 多数 DBA 根本不信任「应用层控权限」,坚持要在数据库层建专用账号

结果就是:要么不用 RLS 裸奔,要么每个微服务、每个 Agent 单独建账号、单独配 Pool、单独走 Vault 动态凭证——运维成本指数级上升,团队照样选择「共用只读账号,出事再改」。

四、我现在的最小可行性方案

不追求完美,只求出事能止损、能追责:

# 1. Vault 动态凭证 + 短 TTL(15min)
database:
  creds_path: "database/creds/llm-readonly"
  ttl: "15m"
  max_ttl: "1h"

# 2. 连接池强制 DISCARD ALL + 验证查询
pgbouncer:
  pool_mode: transaction
  server_reset_query: "DISCARD ALL; SET ROLE llm_guest;"
  admin_users: "pgbouncer_admin"

# 3. 仅开放 information_schema + 业务 Schema,系统表全拒
-- migration: 2024_06_10_llm_guest_grants.sql
REVOKE ALL ON ALL TABLES IN SCHEMA pg_catalog FROM llm_guest;
GRANT USAGE ON SCHEMA information_schema TO llm_guest;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO llm_guest;

# 4. 应用层注入 Request-ID 进 Session 变量,审计日志可溯源
SET app.request_id = 'req_abc123';
SET app.actor = 'langchain-agent-v3';

关键点:

  • 凭证 15 分钟自动过期,不用人工去 REVOKE,Vault 自动轮换
  • DISCARD ALL 配合 SET ROLE,把连接池复用时的脏状态彻底清零
  • 只给 information_schema + 业务 Schema,系统表、统计视图、权限表一律不可见
  • 应用层强制写入 app.request_id,数据库审计日志里直接能看到是哪个 Agent、哪次调用、哪个用户发起的

五、别信「只读就安全」的鬼话

SELECT pg_sleep(10000); 照样能把库拖死;SELECT * FROM pg_largeobject; 照样能把磁盘读爆;SET enable_seqscan = off; 照样能让优化器发疯。
只有把权限收得比「最小权限」还小——information_schema.tables 都不给看、只给特定 View、配上 statement_timeout = 5s、配上 RLSrequest_id 限行——才算把「收回权限」这事儿在架构层面

全部回复 (4)

老陈 专家 1小时前
完全同意把 Availability 单拎出来,去年就见过实习生跑了个全表 join 把生产库锁死俩小时,监控都没报警。现在的做法是给 agent 单独配只读从库 + 查询超时强杀 + 行级限流,但成本高得离谱,小团队根本玩不起。有没有更轻量的熔断方案?
0 回复
早八人码农 专家 1小时前
@老陈 小团队真不用上那么重,我们直接在应用层包了层 SQL 解析,拦截全表扫描和无 LIMIT 查询,成本基本为零。不过复杂 join 还是得靠只读从库兜底,这块省不了钱
0 回复
小阿伟的日常 初级 1小时前
直接把schema丢给LLM生成SQL?生产环境谁敢这么干,token成本先不说,幻觉一出就是删库跑路级事故。我们内部是做了中间层:向量检索相关表结构+ few-shot示例+ 规则引擎兜底,再加上执行前干跑验证,这才勉强能上只读库。博客里那套纯prompt工程路线,demo好看落地是噩梦。
0 回复
阿小美 中级 1小时前
REVOKE 之后,连接池里的旧连接真能立马断吗?还得等超时?
0 回复

发表回复

支持 Markdown 格式