Repository navigation
sec: plaintext admin password echo'd to stdout during DB migration startup #440
Description
Summary
internal/database/postgres/migrations/migrate.go calls fmt.Printf with the admin email and a message that confirms whether the password was set. More critically, when ADMIN_PASSWORD (or its Secrets Manager value) is passed to ensureAdminUserWithPassword, the function prints to stdout before and after the password operation:
// Line 110
fmt.Printf("Ensuring admin user exists with password: %s\n", email)
// Line 136
fmt.Printf("Admin user created/activated with password: %s\n", email)
// Line 138
fmt.Printf("Admin user already has a password set: %s (skipping)\n", email)
These messages do not print the password itself. However, any operator that sees these messages in CloudWatch/GCP Cloud Logging knows that a cleartext admin password (not just an ARN reference) was processed at that moment, and can correlate the log timestamp with the Secrets Manager access log to identify which secret version was active.
More significantly: the admin password value arrives at ensureAdminUserWithPassword as a Go string resolved from Secrets Manager. If any future error path (e.g., bcrypt failure) emits fmt.Errorf("failed to hash admin password: %w", err) and that error is subsequently wrapped into a log line that includes the arguments (such as log.Fatalf("%v", err) or %+v), the password itself could propagate into logs. The current code wraps with %w only, which is safe, but the path is fragile.
Risk
- CloudWatch (AWS) or Cloud Logging (GCP/Azure) persist stdout output for 30–90 days.
- Internal team members with log access would see migration-time admin password activity.
- Severity is high due to the admin account privilege level.
Reproduction
- Deploy with
DB_AUTO_MIGRATE=trueandADMIN_PASSWORD_SECRETpointing to a non-empty secret. - Check CloudWatch log group for the Lambda function or the container's stdout.
- Observe
"Admin user created/activated with password: admin@example.com"indicating a cleartext password was just processed.
Remediation
- Replace
fmt.Printfwithlog.Printf(goes to stderr, not stdout). - Use a structured log at DEBUG level instead, or simply omit the confirmation message for the password-set path (the no-password path already logs at a safe level).
- Ensure the bcrypt error path never includes the plaintext password in its
%w-wrapped error string.
Files
/internal/database/postgres/migrations/migrate.golines 110, 136, 138
Activity
sec(database): redact plaintext credentials from logs + tighten env-v…
test(database): #440 route admin group backfill log to stderr + cover it
test(database): exercise #440 stdout-leak guard + SQL backfill for #351…
cristim commentedon May 28, 2026
Verified shipped: PR #520 (merged 2026-05-20, commit 5589c78) replaced all fmt.Printf calls in migrate.go with log.Printf (stderr), and changed 'Admin user created/activated with password: ...' to 'Admin user created/activated: ...' to avoid inferring plaintext password was processed. PR #579 added an integration test exercising the stdout-leak guard. Both commits present on feat/multicloud-web-frontend.