Skip to content

sec(jobs): pass pg_dump password via PGPASSWORD env, not argv - #62

Merged
mastermanas805 merged 3 commits into
masterfrom
sec/worker-pg-dump-pw-env
May 30, 2026
Merged

sec(jobs): pass pg_dump password via PGPASSWORD env, not argv#62
mastermanas805 merged 3 commits into
masterfrom
sec/worker-pg-dump-pw-env

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

Closes SEC-WORKER FINDING-1 (CWE-214, P1) + SEC-WORKER FINDING-2 (CWE-214, P1) from /tmp/qa-session/shared/SEC-INBOX.md.

Why

Both pg_dump call-sites embedded the connection password in the URL on argv:

  • platform_db_backup.go:631 — daily 02:00 UTC platform DB backup (leaks the doadmin password)
  • customer_backup_runner.go:108 — hourly Pro/Team customer backup (leaks the customer's DB password, decrypted from AES-GCM ciphertext)

argv is world-readable via /proc/<pid>/cmdline for the entire backup window (multi-minute on the platform DB). Any sidecar / debug shell / log-shipper / kube-exec process — and any crash-dump archived by kubectl describe — captures the secret.

What

Tiny helper splitPGPassword(url) → (urlWithoutPW, password, err) strips the password from URL userinfo. Both call-sites set PGPASSWORD=<pw> on cmd.Env (alongside the parent env) so libpq picks it up out-of-band. URL on argv no longer contains the password.

Fail-open posture: if URL parse fails (malformed connection_url), fall back to the original URL on argv — better than wedging every customer's backup ladder over one operator typo. Today's URLs are constructed by the provisioner from validated identifiers, so this fail-open path is purely defensive.

Verification

  • make gate green (build + vet + go test ./... -short -count=1).
  • TestSplitPGPassword — 6 subcases pass.
  • TestSplitPGPassword_NoLeak — the literal password substring MUST NOT appear in the returned URL (THE regression guard).

Production LOC delta: 62 (helper 58 + 4 imports/edits per site).

🤖 Generated with Claude Code

Closes SEC-WORKER FINDING-1 (CWE-214, P1) + FINDING-2 (CWE-214, P1).

Both pg_dump call-sites embedded the connection password in the URL on
argv:

  - platform_db_backup.go:631  — daily 02:00 UTC platform DB backup
                                  (leaks the doadmin password)
  - customer_backup_runner.go:108 — hourly Pro/Team customer backup
                                     (leaks the customer's DB password,
                                      decrypted from AES-GCM ciphertext)

argv is world-readable via /proc/<pid>/cmdline for the entire backup
window (multi-minute on the platform DB). Any sidecar / debug shell /
log-shipper / kube-exec process — and any crash-dump archived by
`kubectl describe` — captures the secret.

Fix: tiny helper `splitPGPassword(url) → (urlWithoutPW, password, err)`
strips the password from the URL userinfo. Both call-sites set
`PGPASSWORD=<pw>` on cmd.Env (alongside the parent env) so libpq picks
it up out-of-band. URL on argv no longer contains the password.

Fail-open posture: if URL parse fails (malformed connection_url), fall
back to the original URL on argv — better than wedging every customer's
backup ladder over one operator typo. Today's URLs are constructed by
the provisioner from validated identifiers so this path is purely
defensive.

Production LOC delta: 62 (helper 58 + 4 imports/edits per site).

Tests:
  - TestSplitPGPassword (6 subcases: userpass, user-only, no-userinfo,
    empty, percent-encoded password, malformed-URL-fail-open)
  - TestSplitPGPassword_NoLeak: literal password substring MUST NOT
    appear in returned URL (THE regression guard for this fix)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mastermanas805
mastermanas805 force-pushed the sec/worker-pg-dump-pw-env branch from df11c0a to 5021e00 Compare May 29, 2026 18:09
mastermanas805 and others added 2 commits May 30, 2026 19:30
Add direct tests for realPgDumpRunner.Run and defaultPgDumpExec.Dump that
exercise the SEC-WORKER FINDING-1 + FINDING-2 fix paths added in this PR:

  - pw != "" → cmd.Env carries PGPASSWORD, argv carries the stripped DSN
  - splitErr != nil → fail-open passthrough (no env, original URL on argv)

Spawns a shell-script fake pg_dump that records argv + PGPASSWORD env to
files in a TempDir, then asserts no literal password leak in argv and the
secret IS in PGPASSWORD env. Closes the diff-cover gap on lines 117-119,
126-127 (customer_backup_runner.go) and 644-647, 656-658 (platform_db_backup.go).
@mastermanas805
mastermanas805 merged commit 6034cd1 into master May 30, 2026
11 checks passed
@mastermanas805
mastermanas805 deleted the sec/worker-pg-dump-pw-env branch May 30, 2026 14:21
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.

1 participant