From 7e70dfba547e0efbf43169edd3f295ecb21364be Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 20 Oct 2026 14:08:07 -0500 Subject: [PATCH 1/2] Escape untrusted strings in LNURL-auth logs The LNURL-auth flow logged the decoded callback URL, its tag query parameter, and the server-supplied failure reason verbatim. All three are controlled by whoever produced the LNURL or runs the auth server, so embedded newlines or terminal escape sequences could forge log lines or tamper with an operator's terminal. Render these values through PrintableString, which replaces control characters, as is already done for other untrusted strings. This was found by Project Loupe. This change was made with the help of Claude Code. --- src/lnurl_auth.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/lnurl_auth.rs b/src/lnurl_auth.rs index 1ce44a7c35..44655a09fc 100644 --- a/src/lnurl_auth.rs +++ b/src/lnurl_auth.rs @@ -14,6 +14,7 @@ use bitcoin::hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine}; use bitcoin::secp256k1::{All, Message, Secp256k1, SecretKey}; use bitreq::Client; use lightning::util::logger::Logger as LdkLogger; +use lightning_types::string::PrintableString; use serde::{Deserialize, Serialize}; use crate::logger::{log_debug, log_error, Logger}; @@ -86,7 +87,11 @@ impl LnurlAuth { Error::InvalidLnurl })?; - log_debug!(self.logger, "Starting LNURL-auth process for URL: {lnurl_auth_url}"); + log_debug!( + self.logger, + "Starting LNURL-auth process for URL: {}", + PrintableString(&lnurl_auth_url) + ); // Parse the URL to extract domain and parameters let url = bitreq::Url::parse(&lnurl_auth_url).map_err(|e| { @@ -112,7 +117,11 @@ impl LnurlAuth { })?; if tag != "login" { - log_error!(self.logger, "Invalid tag parameter in LNURL-auth URL: {tag}"); + log_error!( + self.logger, + "Invalid tag parameter in LNURL-auth URL: {}", + PrintableString(tag) + ); return Err(Error::InvalidLnurl); } @@ -158,7 +167,11 @@ impl LnurlAuth { Ok(()) } else { let reason = response.reason.unwrap_or_else(|| "Unknown error".to_string()); - log_error!(self.logger, "LNURL-auth authentication failed: {reason}"); + log_error!( + self.logger, + "LNURL-auth authentication failed: {}", + PrintableString(&reason) + ); Err(Error::LnurlAuthFailed) } } From 7c215978b52f6540f6695ce3d22dd536a8192d5d Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 20 Oct 2026 14:12:13 -0500 Subject: [PATCH 2/2] Log only the domain when starting LNURL-auth The full LNURL-auth callback URL was written to the debug log. It carries the k1 challenge and may also include userinfo credentials or service-specific session parameters, which then end up in log files or custom log sinks. Log just the domain instead, which is all that's needed to tell which service the node is authenticating with. This was found by Project Loupe. This change was made with the help of Claude Code. --- src/lnurl_auth.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lnurl_auth.rs b/src/lnurl_auth.rs index 44655a09fc..9827d7c0c6 100644 --- a/src/lnurl_auth.rs +++ b/src/lnurl_auth.rs @@ -87,12 +87,6 @@ impl LnurlAuth { Error::InvalidLnurl })?; - log_debug!( - self.logger, - "Starting LNURL-auth process for URL: {}", - PrintableString(&lnurl_auth_url) - ); - // Parse the URL to extract domain and parameters let url = bitreq::Url::parse(&lnurl_auth_url).map_err(|e| { log_error!(self.logger, "Invalid LNURL-auth URL: {e}"); @@ -101,6 +95,10 @@ impl LnurlAuth { let domain = url.base_url(); + // Only log the domain, the full URL carries the k1 challenge and potentially other + // credentials. + log_debug!(self.logger, "Starting LNURL-auth process for domain: {domain}"); + // Enforce HTTPS for non-localhost URLs per LNURL spec. let is_localhost = domain == "localhost" || domain == "127.0.0.1" || domain == "[::1]"; if url.scheme() != "https" && !is_localhost {