(sql: string, index: number)
| 176 | } |
| 177 | |
| 178 | function validateWriteSql(sql: string, index: number): string { |
| 179 | const normalized = normalizeUserSql(sql, index); |
| 180 | const upper = normalized.toUpperCase(); |
| 181 | const allowed = ["INSERT", "UPDATE", "DELETE", "REPLACE"]; |
| 182 | const blocked = ["SELECT", "WITH", "EXPLAIN", "PRAGMA"]; |
| 183 | |
| 184 | if (blocked.some((kw) => upper.startsWith(kw))) { |
| 185 | fail( |
| 186 | `Statement ${index}: use db-query for SELECT/read statements. db-exec is for writes only.`, |
| 187 | ); |
| 188 | } |
| 189 | if (upper.startsWith("CREATE") || upper.startsWith("ALTER")) { |
| 190 | fail( |
| 191 | `Statement ${index}: schema changes are not allowed through db-exec. Additive schema changes must go through reviewed migrations/startup code, not ad-hoc agent SQL.`, |
| 192 | ); |
| 193 | } |
| 194 | if (!allowed.some((kw) => upper.startsWith(kw))) { |
| 195 | fail( |
| 196 | `Statement ${index}: only ${allowed.join(", ")} statements are allowed. ` + |
| 197 | `Dangerous operations like DROP, ATTACH, VACUUM, DETACH, CREATE, and ALTER are blocked.`, |
| 198 | ); |
| 199 | } |
| 200 | assertNoSensitiveFrameworkTables(normalized, "write"); |
| 201 | return normalized; |
| 202 | } |
| 203 | |
| 204 | function convertQuestionMarksToPostgresParams(sql: string): string { |
| 205 | let index = 0; |
no test coverage detected