From 6ccc7b8521b9a382def7503b6adeac83abadd58d Mon Sep 17 00:00:00 2001 From: kipavy Date: Tue, 25 Aug 2026 21:59:39 +0000 Subject: [PATCH] fix(auth): stop logging account_id on registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `account_id` is the KDF salt passed to derive_keys, not an opaque identifier. Logging it at INFO put it in the container logs and anywhere those are shipped or retained, where it hands a reader offline precompute against that user's password — the one value the deep-link rules say must never be exposed. `user_id` stays on both lines and identifies the registration; the two columns are joinable in the database if an operator needs the mapping. The value still appears where the protocol requires it: the challenge and login responses return it to the account's own client, which needs the salt to derive its keys. --- src/routes/auth.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/routes/auth.rs b/src/routes/auth.rs index 1edba79..acf533b 100644 --- a/src/routes/auth.rs +++ b/src/routes/auth.rs @@ -317,10 +317,14 @@ pub async fn register( StatusCode::INTERNAL_SERVER_ERROR })?; + // `account_id` is deliberately absent: despite the name it is the KDF salt + // passed to derive_keys, so a log line carrying it hands anyone who can read + // the logs an offline precompute against that user's password. `user_id` + // identifies the registration, and the two are joinable in the database. if trial_blocked { - info!(user_id = %user_id, account_id = %body.account_id, "User registered on free tier (trial already used)"); + info!(user_id = %user_id, "User registered on free tier (trial already used)"); } else { - info!(user_id = %user_id, account_id = %body.account_id, "User registered with 14-day trial"); + info!(user_id = %user_id, "User registered with 14-day trial"); } Ok((