fix(email): suppress login/reset tokens in disabled-email logs (BUNYIP-204) #247

Merged
Claude-Run merged 1 commit from fix/bunyip-204-suppress-token-logging into main 2026-06-26 07:27:00 +02:00
Member

When email sending was disabled (EmailConfig.enabled == false), send_magic_link, send_password_reset, and send_email_change_verify logged the full URL with the raw single-use token at INFO. Because EmailConfig::from_env sets enabled = (is_production && has_smtp) || force_enabled, a production deployment without SMTP_HOST configured (or left as the default localhost) ran this path and wrote live account-takeover bearer tokens to the application log.

The disabled-email branches now log only non-secret context (email + link type) at INFO with the token suppressed. The full URL is logged only at DEBUG and only when the new EMAIL_LOG_TOKENS opt-in is set; that flag defaults off and is forced off in production (EmailConfig::from_env ignores it when is_production), so a token-bearing URL can never reach a production log.

Separately, Config::from_env_inner now fails fast: a production environment with email disabled returns the new ConfigError::EmailDisabledInProduction instead of silently degrading to the dev path. The check runs before encryption-key loading, so it surfaces the misconfiguration directly. Operators must set SMTP_HOST (not localhost) or EMAIL_ENABLED=true.

#BUNYIP-204

When email sending was disabled (EmailConfig.enabled == false), send_magic_link, send_password_reset, and send_email_change_verify logged the full URL with the raw single-use token at INFO. Because EmailConfig::from_env sets enabled = (is_production && has_smtp) || force_enabled, a production deployment without SMTP_HOST configured (or left as the default localhost) ran this path and wrote live account-takeover bearer tokens to the application log. The disabled-email branches now log only non-secret context (email + link type) at INFO with the token suppressed. The full URL is logged only at DEBUG and only when the new EMAIL_LOG_TOKENS opt-in is set; that flag defaults off and is forced off in production (EmailConfig::from_env ignores it when is_production), so a token-bearing URL can never reach a production log. Separately, Config::from_env_inner now fails fast: a production environment with email disabled returns the new ConfigError::EmailDisabledInProduction instead of silently degrading to the dev path. The check runs before encryption-key loading, so it surfaces the misconfiguration directly. Operators must set SMTP_HOST (not localhost) or EMAIL_ENABLED=true. #BUNYIP-204
fix(email): suppress login/reset tokens in disabled-email logs (BUNYIP-204)
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 1m28s
Check / fmt + clippy + build + tests (pull_request) Successful in 9m50s
Create release / Create release from merged PR (pull_request) Has been skipped
6f2483b794
When email sending was disabled (EmailConfig.enabled == false), send_magic_link, send_password_reset, and send_email_change_verify logged the full URL with the raw single-use token at INFO. Because EmailConfig::from_env sets enabled = (is_production && has_smtp) || force_enabled, a production deployment without SMTP_HOST configured (or left as the default localhost) ran this path and wrote live account-takeover bearer tokens to the application log.

The disabled-email branches now log only non-secret context (email + link type) at INFO with the token suppressed. The full URL is logged only at DEBUG and only when the new EMAIL_LOG_TOKENS opt-in is set; that flag defaults off and is forced off in production (EmailConfig::from_env ignores it when is_production), so a token-bearing URL can never reach a production log.

Separately, Config::from_env_inner now fails fast: a production environment with email disabled returns the new ConfigError::EmailDisabledInProduction instead of silently degrading to the dev path. The check runs before encryption-key loading, so it surfaces the misconfiguration directly. Operators must set SMTP_HOST (not localhost) or EMAIL_ENABLED=true.

#BUNYIP-204
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!247
No description provided.