Skip to content

sec(database): redact plaintext credentials from logs + tighten env-var handling (closes #440, #441, #444, #446)#520

Merged
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
sec/backend-secrets-hygiene
May 20, 2026
Merged

sec(database): redact plaintext credentials from logs + tighten env-var handling (closes #440, #441, #444, #446)#520
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
sec/backend-secrets-hygiene

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 19, 2026

Summary

The stdLogger.Log function was refactored into two pure helpers (isSensitiveKey, sanitizeLogData) to keep cyclomatic complexity below the project's gocyclo limit of 10.

Test plan

Summary by CodeRabbit

  • Bug Fixes

    • Sensitive credentials (passwords, secrets, tokens) are now redacted from logs and error messages to prevent accidental exposure.
    • Database passwords no longer appear in error chains during connection failures.
  • New Features

    • Warning emitted when both plaintext and secret-based password methods are configured; the secret method takes precedence.
  • Tests

    • Added security regression tests to prevent credential leaks in logs and error handling.

Review Change Stack

…ar handling

- #440: replace fmt.Printf with log.Printf in migration runner so admin
  password activity goes to stderr (log sink) rather than stdout, which
  CloudWatch ingests into persistent log groups; also remove the phrase
  "with password" from the created/activated confirmation line to reduce
  correlation with Secrets Manager access logs
- #441: warn to stderr when both DB_PASSWORD and DB_PASSWORD_SECRET are
  set; plaintext DB_PASSWORD is visible via lambda:GetFunction without
  requiring secretsmanager:GetSecretValue; local-dev workflows still work
- #444: pass a redacted DSN ("REDACTED" placeholder) to
  pgxpool.ParseConfig so that parse errors never echo the real password
  in the error chain; inject the real password directly into
  ConnConfig.Password after successful parsing
- #446: suppress the pgx "args" key (SQL bound parameters) at debug
  level in stdLogger.Log; operators who need parameter tracing must opt
  in via DB_LOG_BIND_PARAMETERS=true

Regression tests added for all four issues.

Closes #440, #441, #444, #446
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Warning

Rate limit exceeded

@cristim has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 6 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cae31a73-635e-4efa-bd41-e57ab58fbd75

📥 Commits

Reviewing files that changed from the base of the PR and between 0e12729 and 1ee6be2.

📒 Files selected for processing (2)
  • internal/database/postgres/migrations/migrate_security_test.go
  • internal/database/security_test.go
📝 Walkthrough

Walkthrough

This PR hardens database credential handling by preventing plaintext passwords from leaking into error messages, logs, and stdout. Changes include configuration validation warnings, DSN redaction during parsing, centralized log sanitization for pgx traces, and migration output redirection from stdout to stderr.

Changes

Credential and Logging Security

Layer / File(s) Summary
Configuration validation for dual password methods
internal/database/config.go, internal/database/security_test.go
Config.validateRequiredFields emits a stderr warning when both DB_PASSWORD and DB_PASSWORD_SECRET are configured, stating that the secret-based credential will be used and plaintext password should be removed to avoid credential exposure in Lambda/ECS environment configuration.
DSN redaction and pool configuration
internal/database/connection.go, internal/database/security_test.go
buildPoolConfig redacts the plaintext password from the DSN before parsing with pgxpool.ParseConfig to prevent password leakage in parse errors, then injects the real password into poolConfig.ConnConfig.Password after successful parsing. Tests verify password is correctly set and not exposed in error messages or derived fields.
Log sanitization for pgx tracing
internal/database/connection.go, internal/database/security_test.go
New helper functions identify sensitive keys (password, secret, token) and sanitize pgx trace log data, always removing sensitive keys and conditionally stripping the args field (bound parameters) at debug level unless DB_LOG_BIND_PARAMETERS=true is set. stdLogger.Log delegates to the sanitizer to prevent credential exposure in application logs.
Migration logging refactoring to prevent stdout credential leaks
internal/database/postgres/migrations/migrate.go, internal/database/postgres/migrations/migrate_security_test.go
Replace fmt.Printf with log.Printf throughout migration operations to route status messages to stderr instead of stdout. RunMigrations, ensureAdminUser, ensureAdminUserWithPassword, and RollbackMigrations now log their outcomes without printing to stdout. Tests verify admin user creation, DSN building, and version forcing do not leak credentials to stdout.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • LeanerCloud/CUDly#444: Addresses the same DSN redaction and password injection mechanism in buildPoolConfig to prevent plaintext password leakage in pgxpool.ParseConfig errors.

Possibly related PRs

  • LeanerCloud/CUDly#393: Also modifies ensureAdminUser and ensureAdminUserWithPassword in migrate.go; this PR adjusts their logging output while the related PR changes their INSERT/upsert logic for group seed and backfill operations.

Poem

🐰 Passwords hidden, logs are clean,
DSN redacted, can't be seen.
Stderr whispers what stdout won't tell,
Credentials safe in PostgreSQL's well.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: redacting plaintext credentials from logs and tightening environment variable handling, with direct references to the issues being closed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sec/backend-secrets-hygiene

Comment @coderabbitai help to get the list of available commands and usage tips.

@cristim cristim added priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/many Affects most users effort/s Hours type/security Security finding triaged Item has been triaged labels May 19, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 19, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 19, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/database/postgres/migrations/migrate_security_test.go`:
- Around line 18-78: The test
TestEnsureAdminUserWithPassword_LogsToStderr_NotStdout currently simulates
log.Printf instead of invoking ensureAdminUserWithPassword/ensureAdminUser, so
fix the test by actually calling ensureAdminUserWithPassword (or
ensureAdminUser) inside the test and arrange inputs/mocks so execution reaches
the logging and bcrypt path but stops before any real DB access (e.g., inject a
mock DB/pool that returns an error or a nil-safe fake, or pass a configuration
that triggers validation failure before DB calls). Ensure the test still
captures stdout/stderr as before and asserts stdout is empty and the log buffer
contains the email but not the plaintext password; update the parallel test at
lines 82-114 the same way.
- Around line 119-151: The tests are no-ops because they never call the
functions under test; update TestBuildMigrateDSN_PasswordNotInLogs to actually
invoke buildMigrateDSN with a pgx-like config/DSN containing the sentinel
password (or construct the same inputs the function expects), capture log output
via log.SetOutput(&logBuf) as already done, assert the returned DSN contains the
sentinelPassword, and assert logBuf.String() does NOT contain the
sentinelPassword; likewise update TestMaybeForceVersion_NonNumericError to call
maybeForceMigrationVersion with a config containing a non-numeric version (the
same inputs the real function expects), capture log output, assert the function
returns the expected error/behavior for non-numeric input and that no sensitive
details are logged; reference buildMigrateDSN and maybeForceMigrationVersion to
locate the implementation and the test functions to change.

In `@internal/database/security_test.go`:
- Around line 149-155: Make the test force a parse error so the redaction check
actually runs: change the input passed to the parser (the call that currently
produces err) to an intentionally invalid DSN/string so parsing will fail, and
add an explicit assertion that err is non-nil (use require.Error or assert.Error
on err) before the assert.NotContains check that verifies realPassword is not
present; keep the existing NotContains assertion and allow "REDACTED" as
acceptable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52db4e78-a27d-43c5-a04b-ef0386567525

📥 Commits

Reviewing files that changed from the base of the PR and between b1ea4b1 and 0e12729.

📒 Files selected for processing (5)
  • internal/database/config.go
  • internal/database/connection.go
  • internal/database/postgres/migrations/migrate.go
  • internal/database/postgres/migrations/migrate_security_test.go
  • internal/database/security_test.go

Comment thread internal/database/postgres/migrations/migrate_security_test.go
Comment thread internal/database/postgres/migrations/migrate_security_test.go
Comment thread internal/database/security_test.go Outdated
CR major findings: the four tests were calling log.Printf directly or
reading env vars instead of invoking the actual target functions, so they
could not catch a regression back to the old behavior.

- migrate_security_test.go: refactor to call ensureAdminUserWithPassword,
  ensureAdminUser, buildMigrateDSN, and maybeForceMigrationVersion directly.
  Extract helpers (newUnreachablePool, captureLogOutput, captureStdout) to
  eliminate duplication. Use a pgxpool pointed at 127.0.0.1:1 (lazy, never
  dials) so functions log before Exec fails with connection refused.
  Pass nil migrate.Migrate for the non-numeric path (strconv.Atoi errors
  before m.Force is touched). buildMigrateDSN now receives a real
  *pgxpool.Config built from pgxpool.ParseConfig so the return value is
  asserted to contain the sentinel.
- security_test.go: add require.Error before the NotContains assertion in
  TestBuildPoolConfig_ParseError_NoPasswordLeak so the redaction check is
  non-vacuous (invalid sslmode guarantees a parse error from pgx).
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 20, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim cristim merged commit 5589c78 into feat/multicloud-web-frontend May 20, 2026
4 checks passed
@cristim cristim deleted the sec/backend-secrets-hygiene branch May 20, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/many Affects most users priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/security Security finding urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant