Skip to content

fix(persistence): reject unsafe persistent session_replication_role login defaults #588

Description

@seonghobae

Finding

TEPP now fails closed on session-local trigger suppression through direct SET, PostgreSQL set_config, and writable pg_settings, including the bounded Unicode-identifier cases tracked through #587. One PostgreSQL configuration surface remains outside that execution-context authority: persistent login defaults.

PostgreSQL 18 defines these as first-class configuration surfaces:

  • ALTER ROLE { role | ALL } [ IN DATABASE database ] SET parameter = value stores a role/session default; SET ... FROM CURRENT stores the current session value. The value is applied when a subsequent session logs in.
  • ALTER DATABASE ... SET parameter = value supplies a database default for subsequent sessions.
  • ALTER SYSTEM SET parameter = value writes the cluster-wide default to postgresql.auto.conf; it becomes effective after configuration reload/restart as applicable and cannot be rolled back.

session_replication_role=replica disables ordinary/default triggers and rules. A migration that leaves tepp_app_runtime, every role (ALL), the target database, or the cluster with a persistent replica default can therefore make a later TEPP application session bypass append-only/retention trigger enforcement even though the migration's own session ends in ordinary execution mode and role attributes remain NOSUPERUSER/NOBYPASSRLS.

Primary PostgreSQL 18 authority:

Required ordinary-forward RED → repair

Add public validate_migration_catalog() contracts before changing production code.

Unsafe persistent defaults must fail closed:

  • ALTER ROLE tepp_app_runtime SET session_replication_role = replica;
  • ALTER ROLE tepp_app_runtime IN DATABASE <db> SET session_replication_role TO replica;
  • ALTER ROLE ALL SET session_replication_role = replica and the database-scoped ALL equivalent;
  • ALTER ROLE tepp_app_runtime SET session_replication_role FROM CURRENT because the persisted value cannot be proven ordinary-trigger-safe from syntax alone;
  • ALTER DATABASE <db> SET session_replication_role = replica;
  • ALTER SYSTEM SET session_replication_role = replica.

Controls:

  • direct origin and local defaults preserve ordinary trigger execution;
  • unrelated parameters remain unrelated;
  • role/database mutations inside an explicit transaction that rolls back are non-durable and must not poison final state;
  • ALTER SYSTEM is not given a rollback control because PostgreSQL forbids it inside transaction blocks;
  • strings/comments/dollar bodies containing marker text remain opaque;
  • identifier-prefix/lookalike objects do not impersonate the protected role/parameter/database surfaces.

Repair boundary

Do not add another raw-SQL lexer or one-off string-search authority. Consume the existing normalized + committed transaction projection and extend the first-class execution-context/default-state model. Preserve #576–#587 semantics for direct SET/set_config/pg_settings, Unicode fail-closed behavior, role final-state logic, and current rollback projection.

Long-term aggregate should distinguish at least:

  • session-current execution mode;
  • role-specific and role+database login defaults;
  • database/global defaults and precedence;
  • FROM CURRENT, DEFAULT/RESET, and statement ordering/final state;
  • reload/restart activation for system defaults;
  • transaction durability;
  • protected trigger/rule dependency state.

A conservative interim fail-closed boundary is acceptable only if it is explicit about unsupported final-state transitions; do not silently certify a historical unsafe default merely because a later unrelated statement exists.

Keep this issue open until the surviving post-#538 non-force-restacked exact head has hosted Rust/Live PostgreSQL, current-topology rustdoc 100%, authored line/branch/edge acceptance, security/CodeQL, qualifying independent review, and immutable release evidence. No force push, destructive rebase, self-approval, skip/xfail, coverage-denominator manipulation, or gate weakening.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions