// packages/sdk/src/sql-guard.ts // 模块 SQL 通用安全规则:dev 与生产共用一份,保证模块在 stago dev 阶段 // 就能看到与线上一致的报错(fail fast),而不是发布后才被生产 sandbox 拦下。 // // 生产侧(apps/api module-host sandbox)在此基础上额外叠加平台表写入白名单; // 平台内部事务控制语句(BEGIN/COMMIT/SET LOCAL 超时)走 rawQuery,不经过本检查。 const TX_CONTROL_PATTERN = /^\s*(BEGIN|START\s+TRANSACTION|COMMIT|END|ROLLBACK|ABORT|SAVEPOINT|RELEASE(\s+SAVEPOINT)?|SET\s+TRANSACTION)\b/i; // 禁止一切 SET / SET LOCAL(防止覆盖 statement_timeout、污染共享连接池); // 超时用 db.transaction 的 timeoutMs const SET_PATTERN = /^\s*SET\b/i; const DDL_PATTERN = /^\s*(CREATE|DROP|ALTER|TRUNCATE|RENAME|COMMENT)\s/i; // ---- 受限放行:事务级临时表(2026-07-21 决策)---- // Greenplum 7 的 Postgres-based planner 会内联 CTE(无视 MATERIALIZED),重查询 // 唯一可行的物化手段是临时表。放行条件(三者缺一不可,其余 DDL 照拦): // 1. CREATE TEMP/TEMPORARY TABLE,表名必须 tmp_ 前缀; // 2. 必须声明 ON COMMIT DROP——连接是池化共享的,会话级临时表会泄漏给后续请求; // 3. CREATE INDEX 仅允许建在 tmp_ 前缀表上(临时表物化后建索引 + ANALYZE 的配套)。 const TEMP_TABLE_PATTERN = /^\s*CREATE\s+(?:TEMP|TEMPORARY)\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:pg_temp\.)?tmp_[\w$]*\b/i; const ON_COMMIT_DROP_PATTERN = /\bON\s+COMMIT\s+DROP\b/i; const TEMP_INDEX_PATTERN = /^\s*CREATE\s+INDEX\s+[\w$]+\s+ON\s+(?:pg_temp\.)?tmp_[\w$]*\s*\(/i; const ANY_TEMP_TABLE_PATTERN = /^\s*CREATE\s+(?:TEMP|TEMPORARY)\s+TABLE\b/i; function isAllowedTempTableDdl(sql: string, moduleName: string): boolean { if (ANY_TEMP_TABLE_PATTERN.test(sql)) { if (!TEMP_TABLE_PATTERN.test(sql)) { throw new Error( `[${moduleName}] Temp table names must use the tmp_ prefix (e.g. CREATE TEMP TABLE tmp_xxx ... ON COMMIT DROP)`, ); } if (!ON_COMMIT_DROP_PATTERN.test(sql)) { throw new Error( `[${moduleName}] Temp tables must declare ON COMMIT DROP (pooled connections are shared; session-scoped temp tables would leak)`, ); } return true; } return TEMP_INDEX_PATTERN.test(sql); } /** * 校验模块业务 SQL 是否允许执行:拦事务控制语句、SET、DDL * (事务级 tmp_ 临时表除外)。dev 与生产共用,规则或文案变更两边自动同步。 */ export function assertModuleSqlAllowed(sql: string, moduleName: string): void { if (TX_CONTROL_PATTERN.test(sql)) { throw new Error( `[${moduleName}] Transaction control statements are not allowed; use db.transaction(...)`, ); } if (SET_PATTERN.test(sql)) { throw new Error( `[${moduleName}] SET statements are not allowed in module handlers; use db.transaction(...) options for timeouts`, ); } if (DDL_PATTERN.test(sql) && !isAllowedTempTableDdl(sql, moduleName)) { throw new Error(`[${moduleName}] DDL statements are not allowed in module API handlers`); } }