1 分钟

面向 AI 应用构建器的 PostgreSQL 数据库访问

为 AI 应用构建器配置 PostgreSQL 数据库访问,采用只读发现、限定范围的凭据、已批准的迁移和安全连接池。

面向 AI 应用构建器的 PostgreSQL 数据库访问

AI 应用构建器可以连接现有的 PostgreSQL 数据库,而不拥有其架构,但前提是你要在 PostgreSQL 中真正落实这条边界。提示词里写着「不要修改生产环境」并不是控制措施。独立角色、事务默认设置、明确的迁移审查和架构检查才是控制措施。

安全的做法将数据库工作分成三条路径。发现阶段读取元数据和获准的数据样本。应用只读取和写入它需要的数据表和操作。架构变更在人工批准确切 SQL 后,通过单独的迁移身份执行。我见过团队为了省事,把三条路径合并为一个所有者凭据,后来才发现代理把一个看似合理的列名当成了重新设计线上数据表的许可。便利只持续了一个下午,善后却花了很久。

发现阶段应从设计上只读

发现连接需要足够的权限来理解允许的架构,而不需要有权限改进它。创建一个登录角色,它不能创建数据库或角色,不能绕过行级安全策略,也不能从权限很广的组中继承意外权限。PostgreSQL 默认不会给新角色这些权限,但明确声明能让审查者看清意图。

CREATE ROLE app_discovery
  LOGIN
  NOSUPERUSER
  NOCREATEDB
  NOCREATEROLE
  NOINHERIT
  NOBYPASSRLS
  CONNECTION LIMIT 3
  PASSWORD 'replace-through-secret-manager';

ALTER ROLE app_discovery SET default_transaction_read_only = on;
GRANT CONNECT ON DATABASE customer_portal TO app_discovery;
GRANT USAGE ON SCHEMA app TO app_discovery;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_discovery;

default_transaction_read_only 会阻止保持默认设置的会话执行常规写入。它很有用,但不能单独依赖。如果客户端改了事务设置,真正将该角色限制住的是缺少 INSERTUPDATEDELETETRUNCATECREATE 和所有权。不要把该角色加入应用所有者组,也不要让它拥有任何架构。

在构建器连接前,应检查已有授权。下面的查询会为每项数据表权限生成一行,审查者可以发现 SELECT 之外的权限:

SELECT table_schema, table_name, privilege_type
FROM information_schema.role_table_grants
WHERE grantee = 'app_discovery'
ORDER BY table_schema, table_name, privilege_type;

健康的结果类似 app | invoices | SELECT。空结果可能表示发现阶段看不到所需数据表,结尾是 UPDATE 的行则说明角色权限过大。还要用 has_schema_privilege 检查架构权限,并用 has_database_privilege 检查数据库权限,因为数据表授权不会显示该角色能否在别处创建对象。

不要把生产快照当作共享所有者凭据的理由。副本仍可能包含客户数据,而拥有所有权的代理可以把它改得面目全非,之后的比较也失去价值。每个环境都应为发现阶段提供专用身份。

目录检查必须限定在允许列表内

构建器只能发现获批的架构,并记录 PostgreSQL 实际报告的内容。information_schema 提供数据表、列、约束和权限的可移植视图。pg_catalog 则展示 PostgreSQL 特有的细节,例如索引、类型、生成表达式和行级安全。两者都比 LLM 对典型客户数据表的记忆更可靠。

先建立允许列表,例如 appreporting。拒绝将 pg_cataloginformation_schema、临时架构、扩展架构和每个未列出的租户架构作为应用目标。查询应在数据库、角色和 SQL 三个层面过滤,仅在提示词层面设允许列表,后续对话中可能就会丢失。

SELECT
  c.table_schema,
  c.table_name,
  c.ordinal_position,
  c.column_name,
  c.data_type,
  c.is_nullable,
  c.column_default
FROM information_schema.columns AS c
WHERE c.table_schema IN ('app', 'reporting')
ORDER BY c.table_schema, c.table_name, c.ordinal_position;

将结果保存为架构快照,其中应包含获取时间和数据库标识。快照是生成器所见内容的证据,不是永久真相。PostgreSQL 可能在发现与代码生成之间发生变化,所以部署前应比较最新指纹。实用的指纹可以对按顺序排列的数据表、列、类型、可空性、默认值、约束和索引描述进行哈希。若指纹不同,应停止并重新发现,而不是猜测哪项变更无害。

抽样读取行数据是另一项权限决定。列元数据很少包含个人数据,行样本却常常包含。代码生成时最好完全不抽样。如果确实需要示例,请提供一个移除或掩盖密钥和直接标识符的视图,并且只给该视图授予 SELECTLIMIT 10 不会让敏感查询变安全,只会让泄露的规模更小。

搜索路径也应同样处理。将它设置为获批架构加上 pg_catalog,为生成的数据表名加上架构限定,绝不要依赖 PostgreSQL 先解析到哪个对象。攻击者或粗心的迁移可能在可写架构中创建同名对象。app.orders 这类限定名称能消除歧义。

运行时角色应匹配实际用户操作

发现和运行时是不同工作。运行中的应用可能需要插入订单、更新草稿或调用经过精心设计的函数,但这不意味着应在已发现的整个架构中授予广泛写入权限。从用户操作建立权限矩阵,再把每项操作转化为最小范围的 PostgreSQL 授权。

例如,发票查看器可能需要对 app.invoicesapp.invoice_linesSELECT 权限,而备注功能需要对 app.invoice_notesSELECTINSERT 权限。它大概不需要删除发票、不需要访问密码重置记录,也不需要创建架构。只有当插入操作实际依赖序列时才授予序列使用权。PostgreSQL 将序列视为独立对象,这常会让用所有者账户测试的生成器感到意外。

视图和函数可以进一步缩小暴露面。视图能公开已批准的列,同时隐藏内部字段。SECURITY DEFINER 函数可以执行普通授权无法表达的一项受控操作,但它需要固定的 search_path、严格的输入检查,以及没有不必要权限的所有者。应把这类函数当作特权代码,而不是绕过权限模型的捷径。

行级安全策略会在共享数据表中增加数据边界,但不能替代数据表授权。PostgreSQL 会先检查角色能否执行操作,然后在行级安全策略已启用且适用时应用策略。请用确切的运行时角色测试,因为数据表所有者和带有 BYPASSRLS 的角色可以绕过策略。迁移所有者下的测试几乎不能证明最终用户能看到什么。

不要把密钥放进提示词、生成的源代码、浏览器包、构建日志或截图中。将运行时凭据保存在托管环境的密钥库,只注入服务器进程。移动应用和浏览器应用无法保守 PostgreSQL 密码,因此应调用服务器 API,而不是直接连接。分别轮换发现、运行时和迁移凭据,一个路径的泄露不应打开另外两条路径。

迁移权限属于独立的审批路径

应用构建器可以提出迁移建议,但不应通过发现或运行时会话执行它们。为迁移工作提供单独角色,或者让既有部署系统仅在一项获批任务中临时承担该角色。普通对话和预览会话中不要提供它的凭据。

审批必须涵盖确切 SQL、目标数据库身份、准备该 SQL 时使用的架构指纹,以及预期的锁定或重写行为。批准「添加客户状态」这样的自然语言描述,留下的余地太大。实际可执行的变更可能是添加可空文本列、重建大型数据表、创建枚举,或更新所有现有行。这些操作的失败方式各不相同。

我会使用一份简洁的迁移包:

  1. 变更原因,以及需要该变更的应用版本。
  2. 确切的正向 SQL,以及在确实可行时提供确切的反向 SQL。
  3. 命令可能影响的对象、权限和行数据。
  4. 预检查询、预期结果和最新架构指纹。
  5. 锁超时、语句超时、备份或快照参考,以及发布负责人。

反向脚本不一定等于回滚。删除新加的列可以撤销目录变更,但也会销毁发布后写入的数据。PostgreSQL 的事务性 DDL 对许多目录操作有帮助,但事务无法恢复外部副作用或之后命令删除的数据。应明确标注破坏性反向操作,不要把 DOWN 当成魔法词。

设置 lock_timeout,让迁移在繁忙事务后失败,而不是一边等待一边阻塞新工作。根据已审查的操作设置 statement_timeout。在变更窗口内再次运行预检查询。如果数据表大小、冲突对象、空值数量或架构指纹与获批假设不同,就中止。代理应返回不匹配报告,而不是临时针对生产环境编造新的迁移。

绝不要因为生成的测试通过就自动批准迁移。测试通常在小而干净的架构上运行,发现不了锁队列、历史空值、特殊约束、扩展和仍在提供流量的应用版本。审批环节正是人工将生成意图与线上系统核对的时机。

连接池会改变安全评估

将规划与部署分开
在使用 Koder.ai 部署和托管前,先通过规划模式处理数据库变更。

连接池会复用数据库会话,因此会话状态可能比创建它的请求活得更久。如果某个请求运行 SET search_path、切换角色、创建临时对象或禁用超时,下一位借用者可能继承这些结果。应用要么避免可变会话状态,要么在连接归还连接池时可靠地重置它。

事务池化让边界更严格。客户端可能在每个事务后获得不同的服务器会话,这会打破对会话预处理语句、临时表、建议锁和会话级设置的假设。构建器常生成在直接连接下可用、在连接池后失效的代码,因为它们从未建模这种差异。先决定连接池采用会话模式还是事务模式,再将此模式纳入生成和测试。

部署前先规划连接预算。从数据库允许的连接数开始,为管理、迁移、监控和其他服务预留容量,再把剩余容量分配到各应用实例。如果十个实例各开二十个连接,即使流量很低,PostgreSQL 也会看到两百个潜在会话。保守的小型连接池加队列,通常比不断增加连接直到数据库拒绝服务更安全。

将服务器端超时作为兜底措施:statement_timeout 限制长语句,lock_timeout 限制等待锁的时间,idle_in_transaction_session_timeout 会清理打开事务却无所事事的会话。应为每个角色设置值,而不是相信每个生成的客户端都会记得设置。使用实际角色并通过实际连接池执行 SHOW 验证它们。

健康检查应尽量轻量。SELECT 1 能确认一次往返,但不能确认应用可访问获批数据表,也不能确认搜索路径正确。就绪检查可以用运行时角色查询一个很小且稳定的视图。不要在应用启动时执行迁移,多个实例竞争修改架构,正会制造这套设计想消除的耦合。

编造的列名应在查询运行前失败

LLM 会编造看似合理的标识符。如果提示词提到客户的显示名称,生成代码可能访问 customers.display_name,但数据库实际存的是 given_namefamily_name。数据库会拒绝该查询,这比悄悄读取错误字段好,但让生产错误充当架构验证策略仍然很糟。

从获批的目录快照生成类型化架构工件,并让它成为构建查询的唯一来源。工件中不存在的数据表或列应导致生成错误。除非任务明确进入迁移路径,否则不要让模型通过添加迁移来修复错误。缺少标识符可能意味着发现结果过期、拼写错误、环境不对,或确实有新的产品需求。每种情况都需要不同的处理方式。

静态检查应解析 SQL,并根据快照解析每个关系和列。然后针对一次性数据库,或无法写入的事务,准备语句。PostgreSQL 解析器无需依赖成功的业务数据,就能捕获未知列、歧义引用、运算符类型错误和许多错误的类型转换。集成测试应使用运行时角色,以便权限和行级策略参与其中。

失败报告应提供足够细节,供人工做出决定。包括 SQL 位置、无法解析的标识符、相邻的有效标识符、快照指纹和目标数据库身份。建议很有用,但自动模糊替换很危险。仅因名称相近就把 billing_address_id 换成 shipping_address_id,可能生成有效 SQL,却带来错误的业务含义。

对于动态筛选和排序,将公开 API 名称映射到一组封闭的限定 SQL 表达式。绝不要把模型提供的标识符直接拼进 SQL,即使值通过参数传递也不行。参数保护的是值,不是数据表或列名。如果用户可选择排序字段,就把 created 转换为 app.orders.created_at 之类的已知表达式,拒绝所有未知标记。

架构漂移应停止发布,而不是触发创造性协调。重新生成快照,展示差异,并重复测试。这样的延迟可能显得较真,却比部署一段只存在于对话记录中的数据库理解要便宜得多。

破坏性 SQL 需要拒绝策略和证据

选择应用运行地点
当数据所在地规则影响部署时,Koder.ai 可按国家运行应用。

构建器应在任何人执行前对 SQL 分类。阻止 DROPTRUNCATE、没有已审查条件的宽泛 DELETEUPDATE、所有权变更、权限提升、扩展变更,以及针对未获批架构的命令。把 ALTER TABLE 视为需要审查,而不是自动安全。列类型变更或新增非空约束可能扫描或重写数据,并持有影响重大的锁。

单靠文本匹配不可靠,因为 SQL 包含注释、带引号的标识符、函数,以及多种表达副作用的方式。应使用了解 PostgreSQL 的解析器解析语句、检查其语法树,同时依靠数据库角色拒绝禁止操作。分类器能改善审查,权限负责执行边界。两者都不应独自承担全部责任。

当迁移依赖真实数据表形状或数据分布时,使用从最近且妥善保护的快照恢复的预发布数据库。在那里应用确切迁移包,记录耗时和锁观察结果,用运行时凭据运行应用测试,然后丢弃该环境。不要在预发布与生产之间悄悄编辑 SQL。任何编辑都会产生新的工件,需要新的指纹和审批。

日志应将提案与执行关联起来,但不能记录密钥或敏感行。记录谁批准了不可变迁移工件、其摘要、目标身份、开始与完成状态,以及 PostgreSQL 错误详情。保留生成的差异和预检结果。代理对话是有用背景,却不是审计记录,因为用户可以分支、重试和改写指令。

快照和回滚控制能缩短恢复时间,却不能让破坏性 SQL 变得可接受。快照可能将整个数据库恢复到更早的时间点,而真正需求可能只是恢复一个被删除的列,恢复也可能丢弃快照之后的合法写入。应单独测试恢复流程,并记录谁有权调用它。

当我使用 Koder.ai 为一个会接触既有数据库的应用工作时,我会一直停留在规划模式,直到审查完导出的源代码和提出的数据库边界。快照和回滚是恢复控制,不是跳过审查的许可。任何构建器都应遵循同一原则:产品便利必须置于数据库强制措施之后。

架构变更必须容忍应用版本混用

从错误版本中恢复
当应用变更失败时使用快照和回滚恢复,同时依靠 PostgreSQL 权限保护架构。

迁移只有在发布窗口中旧应用和新应用都能运行时才安全。生产环境很少会在一个瞬间从一个版本切换到另一个版本。请求可能仍到达旧实例,新实例已经启动,队列任务可能携带较旧的负载,回滚也可能让昨天的代码面对今天的架构。只验证最终代码与最终架构的应用构建器,会错过这种重叠。

优先采用增量变更。添加可空列、新数据表或索引,不要立即移除旧路径。部署可读取两种表示形式的代码,并在适用时写入新表示形式。通过单独审查的任务回填现有行,观察错误和延迟,再让新字段成为权威来源。在有证据表明没有运行中的代码使用旧字段或约束后,再在后续发布中移除它。

这个过程比生成一条 ALTER TABLE 语句更久,但它能隔离故障。如果新代码在移除前行为异常,旧路径还在。如果回填落后,可以暂停而不用拖住应用发布。如果部署回滚,旧应用仍认识数据库。多一次发布的代价,远低于在回滚时才发现旧二进制文件查询的列已经被迁移删除。

重命名需要格外小心,因为 PostgreSQL 会立即更改名称。生成器可能建议把 customer_ref 重命名为 customer_id,因为新名称更易读。迁移一旦提交,旧实例就会失败。应添加 customer_id,通过应用代码或经过严格审查的窄范围触发器保持两个字段同步,迁移读取方,等旧写入方消失后再移除 customer_ref。临时重复是带有移除条件的显性债务,立即重命名则是隐性的发布耦合。

默认值和非空约束同样可能隐藏工作。在批准 SET NOT NULL 前,统计现有空值,并证明每个活跃写入方都会提供值。对于大型或繁忙的数据表,审查所用 PostgreSQL 版本如何验证该约束以及会获取哪些锁。构建器应报告这些前提,而不是从没有代表性流量的架构中推断它们。

数据回填不应放在无边界的架构事务中。通过获批工作进程分批更新行,记录稳定游标的进度,并让重试具备幂等性。幂等意味着执行两次仍产生预期状态,不只是 PostgreSQL 接受第二次查询。对于派生值,如果之后的代码可能采用不同算法,应记录派生版本。

发布包应说明四个兼容性要点:

  1. 迁移前允许运行的最旧应用版本。
  2. 旧版本和新版本都接受的架构状态。
  3. 允许进行破坏性清理发布的信号。
  4. 新代码在数据变化后回滚时的恢复路径。

在这些过渡期间,生成的查询应避免 SELECT *。添加一列可能改变扫描成本、结果解码、位置映射和数据暴露,即使旧 SQL 仍可解析。请明确列出限定列,并从同一架构快照生成解码器。这样审查源代码时也能清楚看到哪些数据跨越了数据库边界。

成熟的迁移工具常在数据表中记录已应用版本,但版本号本身不能证明兼容性。应记录确切 SQL 工件的摘要,因为两个文件即使名称相同也可能包含不同命令。执行器应拒绝已记录版本却拥有不同摘要的迁移,也应在缺少必需前置迁移时拒绝后续迁移。

不要让每个应用实例在启动时运行迁移。即使迁移工具使用建议锁,启动现在也依赖特权凭据以及架构工作在健康检查超时前完成。应在一项发布任务中执行迁移,等待其记录结果,然后用不能修改架构的身份启动运行时实例。如果发布系统无法分开这些阶段,应先修复发布系统,再向应用授予所有者权限。

测试这个时间线,而不是只测试终点:旧代码配旧架构、旧代码配扩展后的架构、新代码配扩展后的架构,以及新写入后回滚的代码。清理操作稍后单独测试。这套矩阵能捕获语法上有效却无法在运行中回退的变更。

用负向测试证明边界

安全设计不完整,除非被禁止的操作在测试中确实失败。以发现角色连接,尝试插入、创建数据表和 SET TRANSACTION READ WRITE。以运行时角色连接,尝试访问未授权数据表、读取受行级安全保护的跨租户数据,以及修改架构。预期结果应是 PostgreSQL 权限错误,而不是代理日志中的承诺。

也要运行正向测试。发现阶段仍必须能读取每一项允许的目录条目。运行时必须通过连接池执行每项获批的用户操作。迁移执行只能通过审批路径成功。一个阻碍产品正常工作的边界,会在事故中诱使某人用所有者凭据替换它。

在应用源代码旁保留一份简短的访问契约。其中应说明数据库、允许的架构、发现范围、运行时操作、连接池模式、超时策略、迁移审批人、架构指纹方法和禁止语句。在持续检查中将实际授权与这份契约对比。PostgreSQL 授权漂移就是配置漂移,即使没人修改应用代码。

角色变更、新建数据表、数据库恢复、连接池升级和托管变更后,都应重新检查。默认权限会影响未来对象:授予 SELECT ON ALL TABLES 只覆盖当前数据表,不覆盖之后创建的数据表。决定新对象应在审查前保持不可见,还是通过严格配置的默认权限纳入范围。我更倾向于默认不可见,因为显式授权会迫使新表进入访问讨论。

测试计划还应包含撤销。禁用发现凭据,并确认运行时流量继续;禁用运行时凭据,并确认迁移工具不会悄悄换用更强身份。然后在连接仍活跃时轮换每个密钥,观察连接池是否在预期时间窗口内淘汰旧会话。修改密码不会终止已认证会话,因此轮换流程需要明确的连接池回收或 PostgreSQL 会话终止策略。

在这些测试中审查错误消息,防止意外泄露。PostgreSQL 错误可能包含关系名称、SQL 片段、约束名称和提供的值。将详细错误发送到受限的服务器日志,向客户端返回稳定的公开错误,绝不要把整个生产错误流反馈到代理对话中。构建器修复代码需要语句位置和经过清理的数据库响应,不需要客户值。

最后一项测试能发现大量不安全的集成:完全移除迁移凭据,再运行应用测试套件。如果正常启动、健康检查、预览或请求处理失败,说明架构所有权已泄漏到运行时路径。在将构建器连接到生产环境前修复这种耦合。AI 应用构建器可以使用并不拥有的数据库,但当生成代码忘记这项约定时,PostgreSQL 必须能够说「不」。

常见问题

AI 应用构建器可以使用我现有的 PostgreSQL 数据库吗?

可以,前提是构建器通过专用角色连接,并且只发现获批的架构。将发现、运行时查询和迁移放在不同的权限路径上,连接工具不等于授予它架构所有权。

只读 PostgreSQL 用户能保证数据绝不会被修改吗?

只有 SELECT 权限且不拥有对象的角色才是主要控制手段。default_transaction_read_only 可以增加保护,但不能用来弥补过宽的授权或继承成员资格。

我应该把数据库所有者密码交给构建器吗?

不能。所有者凭据会打破边界,让生成的 SQL 修改权限、数据表和数据。请为发现、运行时和受控迁移任务分别创建凭据。

应用构建器怎样才能安全了解我的架构?

让它通过权限受限的角色查询获批的 information_schemapg_catalog 视图,然后保存带指纹的快照。除非已专门准备好脱敏视图,否则不要抽样读取行数据。

当 AI 编造了 PostgreSQL 列名时会怎样?

应在部署前针对类型化架构快照让生成失败。报告未知名称和附近的有效名称,但由人工判断应修改代码、重新发现架构,还是走已批准的迁移。

应用可以从浏览器或移动应用直接连接数据库吗?

不应直接连接 PostgreSQL,因为这些客户端无法保守数据库密码。将数据库访问放在服务器进程中,让浏览器或移动应用调用其 API。

生成的应用需要连接池吗?

通常需要,但要有意识地配置。限制总会话数,选择会话模式或事务模式,重置可变状态,并让生成的代码通过与生产环境相同的连接池接受测试。

PostgreSQL 迁移可以安全回滚吗?

部分目录变更可以在事务中干净地撤销,但数据丢失和外部副作用无法如此处理。应分别审查正向和撤销 SQL,并把快照当作恢复工具,而不是变更安全的证明。

如何阻止构建器修改未获批准的数据表?

使用架构允许列表、限定名称、最小授权、解析后的 SQL 策略和负向权限测试。即使模型或策略检查器出错,PostgreSQL 角色也必须拒绝该操作。

应用构建器应该多久重新发现一次架构?

当保存的指纹发生变化,以及迁移、恢复或环境变更后,都应重新发现架构。发布期间不要静默刷新,应展示差异并针对新快照重新验证。

Related posts