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=true and ADMIN_PASSWORD_SECRET pointing 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.Printf with log.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.go lines 110, 136, 138
Summary
internal/database/postgres/migrations/migrate.gocallsfmt.Printfwith the admin email and a message that confirms whether the password was set. More critically, whenADMIN_PASSWORD(or its Secrets Manager value) is passed toensureAdminUserWithPassword, the function prints to stdout before and after the password operation: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
ensureAdminUserWithPasswordas a Go string resolved from Secrets Manager. If any future error path (e.g., bcrypt failure) emitsfmt.Errorf("failed to hash admin password: %w", err)and that error is subsequently wrapped into a log line that includes the arguments (such aslog.Fatalf("%v", err)or%+v), the password itself could propagate into logs. The current code wraps with%wonly, which is safe, but the path is fragile.Risk
Reproduction
DB_AUTO_MIGRATE=trueandADMIN_PASSWORD_SECRETpointing to a non-empty secret."Admin user created/activated with password: admin@example.com"indicating a cleartext password was just processed.Remediation
fmt.Printfwithlog.Printf(goes to stderr, not stdout).%w-wrapped error string.Files
/internal/database/postgres/migrations/migrate.golines 110, 136, 138