## Summary - Replace the character-stripping approach (`removeSqlDDLInjection`) with standard PostgreSQL `escapeIdentifier` and `escapeLiteral` functions across all workspace schema manager services - Add missing identifier escaping to `createForeignKey` (was the only method in the FK manager without it) - Add allowlist validation for index WHERE clauses and FK action types - Harden tsvector expression builder with proper identifier quoting ## Context The workspace schema managers build DDL dynamically from metadata (table names, column names, enum values, etc.). The previous approach stripped all non-alphanumeric characters — safe but lossy (silently corrupts values with legitimate special characters). The new approach uses PostgreSQL's standard escaping: - **Identifiers**: double internal `"` and wrap → `"my""table"` (same algorithm as `pg` driver's `escapeIdentifier`) - **Literals**: double internal `'` and wrap → `'it''s a value'` (same algorithm as `pg` driver's `escapeLiteral`) `removeSqlDDLInjection` is kept only for name generation (e.g., `computePostgresEnumName`) where stripping to `[a-zA-Z0-9_]` is the correct behavior. ## Files changed | File | What | |------|------| | `remove-sql-injection.util.ts` | Added `escapeIdentifier` + `escapeLiteral` | | `validate-index-where-clause.util.ts` | New — allowlist for partial index WHERE clauses | | 5 schema manager services | Replaced strip+manual-quote with `escapeIdentifier`/`escapeLiteral` | | `build-sql-column-definition.util.ts` | `escapeIdentifier` for column names, validated `generatedType` | | `sanitize-default-value.util.ts` | `escapeLiteral` instead of stripping | | `serialize-default-value.util.ts` | `escapeLiteral` for values, `escapeIdentifier` for enum casts | | `get-ts-vector-column-expression.util.ts` | `escapeIdentifier` for field names in expressions | | `sanitize-default-value.util.spec.ts` | Updated tests for escape behavior | ## Test plan - [x] All 64 existing tests pass across 6 test suites - [x] `lint:diff-with-main` passes - [x] TypeScript typecheck — no new errors - [ ] Verify workspace sync-metadata still works end-to-end - [ ] Verify custom object/field creation works - [ ] Verify enum field option changes work Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>