> **Plain-language companion:** [v0.44.0.md](v0.44.0.md) ## v0.44.0 — Security Hardening & Code Quality **Status: Planned.** Derived from [plans/PLAN_OVERALL_ASSESSMENT_9.md](../plans/PLAN_OVERALL_ASSESSMENT_9.md) §Dimension 7 (Security), §Dimension 2 (Code Quality). > **Release Theme** > Reduce the security and code-quality regression surface: harden SECURITY > DEFINER paths, centralize dynamic SQL, add RLS warnings, decompose large > modules, consolidate API options, and improve the parser unsafe facade. --- ### Features | ID | Title | Effort | Priority | Assessment ref | |----|-------|--------|----------|----------------| | A45-1 | Remove `public` from IVM SECURITY DEFINER search_path | M | P0 | SEC-01 | | A45-2 | Centralized SQL-building module (`src/sql_builder.rs`) | L | P0 | SEC-02, CQ-02 | | A45-3 | RLS bypass creation-time WARNING/NOTICE | S | P1 | SEC-03 | | A45-4 | Monitoring demo credential hardening | S | P1 | SEC-04 | | A45-5 | SECURITY DEFINER CI check | S | P1 | SEC-06 | | A45-6 | Document superuser requirement | S | P2 | SEC-05 | | A45-7 | Module decomposition of large files | L | P1 | CQ-01 | | A45-8 | `CreateStreamTableOptions` struct | M | P1 | CQ-07 | | A45-9 | Parser typed unsafe facade + fuzzing targets | L | P1 | CQ-05 | | A45-10 | Structured warnings in scheduler BGW paths | M | P2 | CQ-06 | | A45-11 | Replace milestone comment IDs with invariant docs | S | P2 | CQ-08 | **A45-1 — Remove `public` from IVM search_path.** In `src/ivm.rs`, remove `public` from the SECURITY DEFINER function's `SET search_path`. Schema-qualify all user-table references in generated delta SQL using the schema captured at stream-table creation time. If `public` is required for specific edge cases (user-defined operators in public), document the requirement explicitly and restrict to those cases only. **A45-2 — Centralized SQL-building module.** Create `src/sql_builder.rs` with: - `ident(name: &str) -> String` — safe identifier quoting - `qualified(schema: &str, name: &str) -> String` — schema.table - `literal(value: &str) -> String` — string literal with proper escaping - `regclass(oid: Oid) -> String` — OID to regclass cast - `spi_param(index: usize) -> String` — `$N` placeholder - `list_idents(names: &[&str]) -> String` — comma-separated identifiers Migrate all dynamic SQL construction in `src/cdc.rs`, `src/wal_decoder.rs`, `src/dvm/parser/rewrites.rs`, `src/refresh/codegen.rs`, and `src/api/helpers.rs` to use these helpers. Add a CI lint check (grep or semgrep) that flags manual `format!("... '{}'")` patterns in production Rust. **A45-3 — RLS bypass warning.** In `create_stream_table_impl` (and create-or-replace variant), check if any source table has RLS enabled. If so, emit a `WARNING` via `pgrx::warning!()` explaining that source-table RLS does not protect stream-table contents and that RLS should be applied on the stream table itself. **A45-4 — Monitoring credential hardening.** In `monitoring/docker-compose.yml`: - Replace hardcoded `POSTGRES_PASSWORD=postgres` with `${POSTGRES_PASSWORD}` - Replace Grafana `admin/admin` with environment variables - Add `monitoring/.env.example` with commented defaults - Bind services to `127.0.0.1` in examples where possible - Add prominent comments that credentials must be changed for shared environments **A45-5 — SECURITY DEFINER CI check.** Add a CI check (script or workflow step) that: - Finds all `SECURITY DEFINER` in Rust source and SQL migration files - Asserts each has a corresponding `SET search_path` - Asserts none include user-writable schemas unless explicitly justified with a comment **A45-6 — Document superuser requirement.** Add a section to `docs/SECURITY_MODEL.md` (or `INSTALL.md`) explaining: - Why `superuser = true` and `trusted = false` are required - Exact privileges needed at install time vs runtime - Why trusted install is not currently supported - Guidance for managed/hosted environments **A45-7 — Module decomposition.** Continue splitting: - `src/api/mod.rs` → extract create validation, alter logic, storage DDL - `src/scheduler/mod.rs` → extract launcher, worker entry points, job execution - `src/dvm/parser/sublinks.rs` → separate SubLink extraction from deparsing - `src/cdc.rs` → separate trigger generation from buffer management **A45-8 — `CreateStreamTableOptions` struct.** Introduce a single struct used by all `create_stream_table` variants (SQL, JSON bulk, create-or-replace). Centralize defaults, validation, serialization, and docs generation. All API paths construct this struct first, then pass it to a single implementation function. **A45-9 — Parser typed unsafe facade.** Consolidate unsafe parse-tree access behind a smaller typed facade layer that: - Maps PostgreSQL node types to safe Rust enums - Validates node type before access - Provides safe iteration over lists - Reports unsupported nodes as typed errors Add deparser round-trip fuzzing targets that generate random valid parse trees, deparse them, re-parse, and verify structural equivalence. **A45-10 — Structured warnings in scheduler BGW.** Replace `unwrap_or` silence patterns in `src/scheduler/mod.rs` and `src/scheduler/pool.rs` with structured `pgrx::warning!()` calls that include database name, query context, and error class. Add counters exposed through diagnostics. **A45-11 — Replace milestone comment IDs.** Audit all code comments containing milestone IDs (`SCAL-5`, `C2-1`, `DI-8`, etc.) and replace with current invariant descriptions. Keep milestone IDs only when they link to a live design doc. ### Test Coverage | ID | Title | Effort | Priority | Assessment ref | |----|-------|--------|----------|----------------| | T-A45-1 | IVM search_path hardening regression test | M | P0 | SEC-01 | | T-A45-2 | SQL-builder module unit tests | M | P0 | SEC-02, CQ-02 | | T-A45-3 | RLS bypass warning E2E test | S | P1 | SEC-03 | | T-A45-4 | SECURITY DEFINER CI check validation | S | P1 | SEC-06 | | T-A45-5 | CreateStreamTableOptions equivalence tests | M | P1 | CQ-07 | | T-A45-6 | Parser deparser round-trip fuzz targets | L | P1 | CQ-05 | **T-A45-1.** E2E test: create a function in `public` schema that shadows a pg_catalog function, create a stream table, verify that the IVM trigger does NOT resolve through `public`. (Previously this would resolve through public.) **T-A45-2.** Unit tests for `sql_builder` module: identifier quoting with special characters, schema-qualified names, literal escaping with quotes/backslashes/unicode, regclass round-trip, and parameter indexing. **T-A45-3.** E2E test: enable RLS on a source table, create a stream table, verify that a WARNING is emitted during creation. **T-A45-5.** Verify that all `create_stream_table` variants (SQL, JSON bulk, create-or- replace) produce identical catalog state when given the same logical options. **T-A45-6.** Fuzz targets that generate random SQL expressions, deparse through the typed facade, re-parse, and verify the round-trip produces equivalent structures. ### Conflicts & Risks - **A45-1** may break user queries that rely on unqualified resolution through `public` in stream table definitions. Add a migration note and consider a compatibility GUC if breakage is widespread. - **A45-2** is a large refactoring across many modules. Land incrementally per-module rather than as a single large commit. - **A45-7** module splits can create merge conflicts with other work. Coordinate with any in-progress feature branches. ### Exit Criteria - [ ] A45-1: IVM trigger functions do not include `public` in search_path - [ ] A45-2: All dynamic SQL in production code uses `sql_builder` helpers - [ ] A45-3: RLS bypass warning emitted on stream table creation - [ ] A45-5: SECURITY DEFINER CI check passes - [ ] A45-7: No production module exceeds 2,000 lines (target) - [ ] A45-8: All create_stream_table variants use CreateStreamTableOptions - [ ] A45-9: Parser unsafe access goes through typed facade - [ ] Extension upgrade path tested (`0.44.0 → 0.45.0`) - [ ] `just lint` passes with zero warnings - [ ] `just test-all` passes