From 50299bacbbfe525a66eea09b0fb0dbeb59d1e8fc Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Fri, 25 Sep 2026 00:34:22 +0800 Subject: [PATCH] fix: redact user-controlled log fields --- crates/xmedia-bot/src/config.rs | 6 +++--- crates/xmedia-bot/src/handlers/callback.rs | 11 +++++++++-- crates/xmedia-bot/src/handlers/inline.rs | 2 +- crates/xmedia-bot/src/handlers/mod.rs | 9 +++++---- crates/xmedia-bot/src/handlers/urls.rs | 10 +++++++--- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/crates/xmedia-bot/src/config.rs b/crates/xmedia-bot/src/config.rs index 01adf84..6e16a2d 100644 --- a/crates/xmedia-bot/src/config.rs +++ b/crates/xmedia-bot/src/config.rs @@ -34,7 +34,7 @@ impl Config { fn parse_u64(name: &str, default: u64) -> u64 { match env::var(name) { Ok(v) => v.parse::().unwrap_or_else(|_| { - log::warn!("invalid {name}={v:?}; using default {default}"); + log::warn!("invalid {name}; using default {default}"); default }), Err(_) => default, @@ -46,7 +46,7 @@ impl Config { fn parse_opt(name: &str) -> Option { env::var(name).ok().and_then(|s| { s.parse::().ok().or_else(|| { - log::warn!("invalid {name}={s:?}"); + log::warn!("invalid {name}"); None }) }) @@ -68,7 +68,7 @@ impl Config { }) .collect(); if !bad.is_empty() { - log::warn!("BOT_ADMIN: ignoring non-numeric ids: {bad:?}"); + log::warn!("BOT_ADMIN: ignoring {} non-numeric id(s)", bad.len()); } ids }) diff --git a/crates/xmedia-bot/src/handlers/callback.rs b/crates/xmedia-bot/src/handlers/callback.rs index 662332d..b4b7814 100644 --- a/crates/xmedia-bot/src/handlers/callback.rs +++ b/crates/xmedia-bot/src/handlers/callback.rs @@ -202,14 +202,21 @@ async fn handle_callback( } }) .await; - log::info!("template '{name}' applied to prompt {prompt_message_id}"); + log::info!( + "template '{}' applied to prompt {prompt_message_id}", + super::log_escape(name) + ); } // Nothing was applied, so nothing is recorded either: the // prompt keeps rendering through whatever it used before, and // the toast says why (a silently "successful" press left the // caption unchanged). super::EditOutcome::Failed(reason) => { - log::error!("template '{name}' could not be applied: {reason}"); + log::error!( + "template '{}' could not be applied: {}", + super::log_escape(name), + super::log_escape(&reason) + ); answer = Some(format!("Could not apply the template: {reason}")); } } diff --git a/crates/xmedia-bot/src/handlers/inline.rs b/crates/xmedia-bot/src/handlers/inline.rs index c4009e8..e860f41 100644 --- a/crates/xmedia-bot/src/handlers/inline.rs +++ b/crates/xmedia-bot/src/handlers/inline.rs @@ -155,7 +155,7 @@ async fn answer_inline_query( // The query is user input: `debug` keeps only its normalized key, the // text itself is `trace` (same split as the message handler). log::debug!("inline query [key={}]", log_key(&query.query)); - log::trace!("inline query: {}", query.query); + log::trace!("inline query: {}", super::log_escape(&query.query)); let Some(key) = x_media::site::cache_key(&query.query) else { answer(ctx.sender, query.id, Vec::new()).await?; return Ok(true); diff --git a/crates/xmedia-bot/src/handlers/mod.rs b/crates/xmedia-bot/src/handlers/mod.rs index eed85a9..de3cb25 100644 --- a/crates/xmedia-bot/src/handlers/mod.rs +++ b/crates/xmedia-bot/src/handlers/mod.rs @@ -222,14 +222,15 @@ pub(crate) async fn handle_message( .text() .map(|t| { let end = t.floor_char_boundary(120.min(t.len())); - &t[..end] + log_escape(&t[..end]) }) - .unwrap_or(""); + .unwrap_or_else(|| std::borrow::Cow::Borrowed("")); // Per-request detail: who and where at `debug`; the message text itself is // user data and only ever appears at `trace`, so a `debug` log can be // shared without leaking what people pasted. log::debug!( - "message from {sender} in {} (private={is_private})", + "message from {} in {} (private={is_private})", + log_escape(&sender), message.chat.id ); log::trace!("message text: {text_preview}"); @@ -249,7 +250,7 @@ pub(crate) async fn handle_message( log::debug!( "command from {}: {}", message.chat.id, - text.split_whitespace().next().unwrap_or("") + log_escape(text.split_whitespace().next().unwrap_or("")) ); log::trace!("command text: {text_preview}"); execute_command(ctx, bot, &message, command).await?; diff --git a/crates/xmedia-bot/src/handlers/urls.rs b/crates/xmedia-bot/src/handlers/urls.rs index ae7333c..0f42d5e 100644 --- a/crates/xmedia-bot/src/handlers/urls.rs +++ b/crates/xmedia-bot/src/handlers/urls.rs @@ -609,7 +609,7 @@ async fn url_media_inner( } log::debug!("fetching [key={}]", log_key(url)); - log::trace!("fetching {url}"); + log::trace!("fetching {}", super::log_escape(url)); // One fetch per post at a time: a concurrent duplicate of this link waits // for *this* fetch instead of running its own. let outcome = match x_media::site::cache_key(url) { @@ -624,11 +624,15 @@ async fn url_media_inner( // The URL itself is user data, so only `trace` names the link; // `debug` just records that the message was looked at. log::debug!("no site pattern matches the link; ignoring"); - log::trace!("no site pattern matches {url}"); + log::trace!("no site pattern matches {}", super::log_escape(url)); } // Retries exhausted: notify the user (Rust-only requirement 3). Err(e) => { - log::error!("fetch [key={}]: {e}", log_key(url)); + log::error!( + "fetch [key={}]: {}", + log_key(url), + super::log_escape(&e.to_string()) + ); let _ = reply(ctx.sender, chat_id, reply_to, fetch_error_message(e)).await; } Ok(Some(fetched)) => {