Skip to content

sec: plaintext admin password echo'd to stdout during DB migration startup #440

Description

@cristim

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

  1. Deploy with DB_AUTO_MIGRATE=true and ADMIN_PASSWORD_SECRET pointing to a non-empty secret.
  2. Check CloudWatch log group for the Lambda function or the container's stdout.
  3. 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

Activity

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions