Skip to content

fix(migrations): pin pg_temp last in migration search_path#13

Merged
tjgreen42 merged 7 commits into
mainfrom
fix/migration-search-path-pg-temp
Jun 1, 2026
Merged

fix(migrations): pin pg_temp last in migration search_path#13
tjgreen42 merged 7 commits into
mainfrom
fix/migration-search-path-pg-temp

Conversation

@tjgreen42

@tjgreen42 tjgreen42 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Each duroxide migration was applied with SET LOCAL search_path TO <schema>, which left pg_temp at its implicit highest-priority position. Migrations reference objects by unqualified name (e.g. CREATE TABLE instances, CREATE INDEX ... ON orchestrator_queue) and rely on the search_path to resolve them to the target schema. With pg_temp first, a same-named temporary object could be resolved instead of the intended schema object while the migration runs with elevated DDL privileges.

Migrations now run with SET LOCAL search_path TO <schema>, pg_temp, pinning the temporary-object schema to the lowest priority so it can no longer shadow unqualified references. (Schema-qualified references were never at risk.)

This is defense-in-depth following the PostgreSQL search_path hardening guidance (CVE-2018-1058): pg_temp is per-session, so there is no live escalation path for the trusted, in-repo SQL the runner executes today — but it protects against future reuse of the migration connection or SECURITY DEFINER migrations.

Changes

  • src/migrations.rs: append , pg_temp; extract the statement into a testable migration_search_path_stmt helper with an explanatory comment (incl. why pg_catalog is left implicit).
  • New unit test migration_search_path_pins_pg_temp_last.
  • Docs: migrations/README.md and the 0002 migration comment.
  • CHANGELOG.md: ## [0.1.35] Security entry; Cargo.toml bumped 0.1.34 → 0.1.35.

Notes

  • schema_name is already validated (^[A-Za-z_][A-Za-z0-9_]*$) at provider construction (0.1.34), so direct interpolation remains safe.
  • No schema or behavioral change for well-behaved callers.

Verification

  • cargo fmt --check clean
  • cargo test --lib → 39 passed (incl. the new test)
  • cargo clippy --lib clean (one pre-existing unrelated warning, untouched)

Each migration was applied with `SET LOCAL search_path TO <schema>`, which
left pg_temp at its implicit highest-priority position. A temporary object
could shadow the schema-qualified objects a migration references while it runs
with elevated DDL privileges — a privilege-escalation vector.

Migrations now run with `SET LOCAL search_path TO <schema>, pg_temp`, pinning
the temporary-object schema to the lowest priority. The statement is built by a
new `migration_search_path_stmt` helper covered by a unit test. Bumps the crate
to 0.1.35 and updates migration docs.
@tjgreen42 tjgreen42 force-pushed the fix/migration-search-path-pg-temp branch from 533420b to 1967c53 Compare June 1, 2026 18:47
tjgreen42 and others added 3 commits June 1, 2026 21:59
Address code-review feedback: the per-session nature of pg_temp means there
is no live privilege-escalation path for the trusted SQL the migration runner
executes today, so reframe the source comments and CHANGELOG as
defense-in-depth following the PostgreSQL search_path guidance (CVE-2018-1058).
Also document why pg_catalog is intentionally left implicit in
migration_search_path_stmt.
Schema-qualified references cannot be shadowed by search_path; the actual risk
is to the unqualified object references migrations rely on the search_path to
resolve (e.g. CREATE TABLE instances, CREATE INDEX ... ON orchestrator_queue).
Reword the helper/call-site comments, CHANGELOG, and migrations README to say
"unqualified references" instead of "schema-qualified objects".
…al gap

Address remaining code-review feedback on the pg_temp search_path hardening:

- Add an #[ignore]-gated behavioral test (tests/migration_search_path_tests.rs)
  that proves the runner's `SET LOCAL search_path TO <schema>, pg_temp` resolves
  an unqualified reference to the schema object, and demonstrates that the legacy
  statement (pg_temp implicit first) is shadowed by a temp object. The existing
  unit test only locks the emitted string; this verifies its PostgreSQL semantics.
- Document the residual gap in `migration_search_path_stmt`: a name existing only
  in pg_temp still resolves there, so migrations should reference only objects
  they define in <schema>.
- Note in the CHANGELOG that existing deployments need no re-migration.
- Correct the unit-test comment to say "unqualified" (not "schema-qualified")
  references, matching the corrected threat model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tjgreen42 tjgreen42 marked this pull request as ready for review June 1, 2026 22:11

@pinodeca pinodeca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving - feel free to override my recommendations. Opus 4.7 said "ship it", so maybe it's fine, but my prompt was simply "review this feature branch".

Comment thread Cargo.toml Outdated
Comment thread src/migrations.rs Outdated
tjgreen42 and others added 3 commits June 1, 2026 16:12
The assert_eq! on the full statement already covers the pg_temp
ordering, so the additional ends_with check was redundant.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per repo release procedure (prompts/publish-crate.md), the Cargo.toml
version bump and a versioned CHANGELOG heading are part of the dedicated
publish step, not feature PRs. Revert to 0.1.34 and move the migration
search_path security note under an [Unreleased] section so it is picked
up at the next release.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tjgreen42 tjgreen42 merged commit e30553a into main Jun 1, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants