Skip to content

Deploy: separate the migration/owner DB role from the runtime app role (no DDL at request-time) #263

Description

@mforce

Deployment-readiness gap (#244), BLOCKER (codex) / high (agents).

Problem

One database credential does everything. The runtime connection serves every request context (Program.cs:192) and runs schema DDL at boot, because Database:MigrateOnStartup defaults true (Program.cs:523-524). Compose uses the same POSTGRES_USER (image owner/superuser) for the app and the database (docker-compose.yml). So a compromised API process — an RCE or a missed injection sink — holds DDL-level privilege and can DROP/ALTER the schema, not merely read/write rows.

Fix

  • Run migrations from a pre-deploy job with a dedicated owner/migrator credential.
  • Set Database:MigrateOnStartup=false in production.
  • Give the runtime role only USAGE on the schema + DML/sequence privileges — no DDL.

Pairs naturally with the #245 migration squash (one migrator run per deploy).

Verify

Prove the runtime role cannot CREATE/ALTER/DROP schema objects (attempt one, expect a permission error), while the app still serves reads/writes.

Part of the #244 deployment-readiness audit; tracked in epic #15.

Activity

  1. added a commit that references this issue on Jul 30, 2026
  2. mforce commented on Jul 30, 2026

    @mforce
    OwnerAuthor

    App-side landed in PR #288 (merged): the migrate run-then-exit CLI verb, appsettings.Production.json with Database:MigrateOnStartup=false so the request-serving process never runs DDL, the compose one-shot migrate service that app waits on (service_completed_successfully), and the /health/ready 503-while-pending backstop. The CLI verbs were also extracted into a testable command layer (Cli/) in the same PR.

    This does not close #263. The remaining work is host-specific and gated on the hosting decision (#244): create a least-privilege runtime DB role (GRANT USAGE ON SCHEMA public + DML/sequence privileges, no DDL), add ALTER DEFAULT PRIVILEGES for the migrator role so future-migration objects auto-grant to the runtime role, and point the migrate job at a separate owner/migrator credential. Keeping this open to track that role split.

  3. mforce commented on Jul 30, 2026

    @mforce
    OwnerAuthor

    Reopening — this was auto-closed by accident. GitHub's keyword parser matched the string closes #263 inside the #288 squash-merge commit message, in a sentence that actually meant the opposite ("role split is the host follow-up that closes #263" — i.e. not yet done).

    #288 delivered only the app-side (migrate verb, MigrateOnStartup=false, migrate-then-serve compose topology, /health/ready backstop). The actual privilege separation — a least-privilege runtime role (GRANT USAGE+DML, no DDL), ALTER DEFAULT PRIVILEGES for the migrator, and a separate owner/migrator credential for the migrate job — is a host-specific deploy step that hasn't been done (the compose stack still uses one owner credential for both). Per AGENTS.md, the role split is what closes this. Keeping it open under the #244 audit until then.

  4. reopened this on Jul 30, 2026
  5. mforce commented on Jul 30, 2026

    @mforce
    OwnerAuthor

    Closing as a duplicate of the deploy-side work now tracked in a separate deployment/ops repo (least-privilege runtime DB role + separate migrator credential).

    The app-side is complete and merged (PR #288): the migrate run-then-exit verb, MigrateOnStartup=false in Production, migrate-before-serve ordering, and the /health/ready backstop. The remaining privilege separation (create the runtime role, ALTER DEFAULT PRIVILEGES, point the migrate job at an owner credential) is host DB provisioning and lives in that separate deployment repo.

  6. added a commit that references this issue on Oct 7, 2026
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

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions