fix: redact user-controlled log fields

This commit is contained in:
2026-09-25 01:49:31 +08:00
parent 9d40f3bc50
commit 50299bacbb
5 changed files with 25 additions and 13 deletions
+3 -3
View File
@@ -34,7 +34,7 @@ impl Config {
fn parse_u64(name: &str, default: u64) -> u64 { fn parse_u64(name: &str, default: u64) -> u64 {
match env::var(name) { match env::var(name) {
Ok(v) => v.parse::<u64>().unwrap_or_else(|_| { Ok(v) => v.parse::<u64>().unwrap_or_else(|_| {
log::warn!("invalid {name}={v:?}; using default {default}"); log::warn!("invalid {name}; using default {default}");
default default
}), }),
Err(_) => default, Err(_) => default,
@@ -46,7 +46,7 @@ impl Config {
fn parse_opt<T: std::str::FromStr>(name: &str) -> Option<T> { fn parse_opt<T: std::str::FromStr>(name: &str) -> Option<T> {
env::var(name).ok().and_then(|s| { env::var(name).ok().and_then(|s| {
s.parse::<T>().ok().or_else(|| { s.parse::<T>().ok().or_else(|| {
log::warn!("invalid {name}={s:?}"); log::warn!("invalid {name}");
None None
}) })
}) })
@@ -68,7 +68,7 @@ impl Config {
}) })
.collect(); .collect();
if !bad.is_empty() { 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 ids
}) })
+9 -2
View File
@@ -202,14 +202,21 @@ async fn handle_callback(
} }
}) })
.await; .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 // Nothing was applied, so nothing is recorded either: the
// prompt keeps rendering through whatever it used before, and // prompt keeps rendering through whatever it used before, and
// the toast says why (a silently "successful" press left the // the toast says why (a silently "successful" press left the
// caption unchanged). // caption unchanged).
super::EditOutcome::Failed(reason) => { 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}")); answer = Some(format!("Could not apply the template: {reason}"));
} }
} }
+1 -1
View File
@@ -155,7 +155,7 @@ async fn answer_inline_query(
// The query is user input: `debug` keeps only its normalized key, the // The query is user input: `debug` keeps only its normalized key, the
// text itself is `trace` (same split as the message handler). // text itself is `trace` (same split as the message handler).
log::debug!("inline query [key={}]", log_key(&query.query)); 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 { let Some(key) = x_media::site::cache_key(&query.query) else {
answer(ctx.sender, query.id, Vec::new()).await?; answer(ctx.sender, query.id, Vec::new()).await?;
return Ok(true); return Ok(true);
+5 -4
View File
@@ -222,14 +222,15 @@ pub(crate) async fn handle_message(
.text() .text()
.map(|t| { .map(|t| {
let end = t.floor_char_boundary(120.min(t.len())); let end = t.floor_char_boundary(120.min(t.len()));
&t[..end] log_escape(&t[..end])
}) })
.unwrap_or("<no text>"); .unwrap_or_else(|| std::borrow::Cow::Borrowed("<no text>"));
// Per-request detail: who and where at `debug`; the message text itself is // 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 // user data and only ever appears at `trace`, so a `debug` log can be
// shared without leaking what people pasted. // shared without leaking what people pasted.
log::debug!( log::debug!(
"message from {sender} in {} (private={is_private})", "message from {} in {} (private={is_private})",
log_escape(&sender),
message.chat.id message.chat.id
); );
log::trace!("message text: {text_preview}"); log::trace!("message text: {text_preview}");
@@ -249,7 +250,7 @@ pub(crate) async fn handle_message(
log::debug!( log::debug!(
"command from {}: {}", "command from {}: {}",
message.chat.id, message.chat.id,
text.split_whitespace().next().unwrap_or("<empty>") log_escape(text.split_whitespace().next().unwrap_or("<empty>"))
); );
log::trace!("command text: {text_preview}"); log::trace!("command text: {text_preview}");
execute_command(ctx, bot, &message, command).await?; execute_command(ctx, bot, &message, command).await?;
+7 -3
View File
@@ -609,7 +609,7 @@ async fn url_media_inner(
} }
log::debug!("fetching [key={}]", log_key(url)); 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 // One fetch per post at a time: a concurrent duplicate of this link waits
// for *this* fetch instead of running its own. // for *this* fetch instead of running its own.
let outcome = match x_media::site::cache_key(url) { 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; // The URL itself is user data, so only `trace` names the link;
// `debug` just records that the message was looked at. // `debug` just records that the message was looked at.
log::debug!("no site pattern matches the link; ignoring"); 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). // Retries exhausted: notify the user (Rust-only requirement 3).
Err(e) => { 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; let _ = reply(ctx.sender, chat_id, reply_to, fetch_error_message(e)).await;
} }
Ok(Some(fetched)) => { Ok(Some(fetched)) => {