From 6e8ee69b15477d498a9df187d96bb2333467ee04 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Wed, 22 Jul 2026 14:21:41 -0300 Subject: [PATCH 1/3] fix(ci): pin `jobserver` for MSRV --- .github/workflows/cont_integration.yml | 2 ++ README.md | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index 5fe5477..277300b 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -34,6 +34,7 @@ jobs: cargo update -p openssl --precise "0.10.78" cargo update -p openssl-sys --precise "0.9.114" cargo update -p zeroize --precise "1.8.2" + cargo update -p jobserver --precise "0.1.34" - name: Test run: cargo test --verbose --all-features @@ -74,6 +75,7 @@ jobs: cargo update -p openssl --precise "0.10.78" cargo update -p openssl-sys --precise "0.9.114" cargo update -p zeroize --precise "1.8.2" + cargo update -p jobserver --precise "0.1.34" - name: Check features run: cargo check --verbose ${{ matrix.features }} diff --git a/README.md b/README.md index b9f554e..421c3a9 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ To build with the MSRV you will need to pin dependencies by running: cargo update -p openssl --precise "0.10.78" cargo update -p openssl-sys --precise "0.9.114" cargo update -p zeroize --precise "1.8.2" +cargo update -p jobserver --precise "0.1.34" ``` ## License From ad472c6063eb897be50821e7c10cc4071bb78b8c Mon Sep 17 00:00:00 2001 From: Edward Houston Date: Thu, 2 Jul 2026 11:51:02 +0200 Subject: [PATCH 2/3] fix: redact authorization token from `call` trace log - introduce `Request::redacted`, which returns a copy of the request with the authorization token replaced by a `` placeholder - trace the redacted request in `RawClient::call` instead of the raw wire bytes Signed-off-by: Leonardo Lima --- src/raw_client.rs | 2 +- src/types.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/raw_client.rs b/src/raw_client.rs index e2a5b9c..5ebd4ea 100644 --- a/src/raw_client.rs +++ b/src/raw_client.rs @@ -803,7 +803,7 @@ impl RawClient { let req = req.with_auth(authorization); let mut raw = serde_json::to_vec(&req)?; - trace!("==> {}", String::from_utf8_lossy(&raw)); + trace!("==> {}", serde_json::to_string(&req.redacted())?); raw.extend_from_slice(b"\n"); let mut stream = self.stream.lock()?; diff --git a/src/types.rs b/src/types.rs index 68e3d36..b6a2c9a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -62,6 +62,9 @@ impl Display for EstimationMode { } } +/// Placeholder that replaces the authorization token in log output. +pub(crate) const REDACTED_AUTHORIZATION: &str = ""; + #[derive(Serialize, Clone)] /// A request that can be sent to the server pub struct Request<'a> { @@ -104,6 +107,18 @@ impl<'a> Request<'a> { self.authorization = authorization; self } + + /// Returns a copy of this [`Request`] with the authorization token replaced by + /// [`REDACTED_AUTHORIZATION`], safe to log. + pub(crate) fn redacted(&self) -> Self { + Self { + authorization: self + .authorization + .as_ref() + .map(|_| REDACTED_AUTHORIZATION.to_string()), + ..self.clone() + } + } } #[doc(hidden)] @@ -531,7 +546,7 @@ impl From for Error { mod tests { use crate::ScriptStatus; - use super::{Param, Request}; + use super::{Param, Request, REDACTED_AUTHORIZATION}; #[test] fn script_status_roundtrip() { @@ -595,4 +610,34 @@ mod tests { assert!(parsed["params"].is_array()); assert_eq!(parsed["params"][0], "test-scripthash"); } + + #[test] + fn test_request_redacted_with_authorization() { + let req = Request::new_id( + 42, + "blockchain.scripthash.get_balance", + vec![Param::String("test-scripthash".to_string())], + ) + .with_auth(Some("Bearer secret-token".to_string())); + + let json = serde_json::to_string(&req.redacted()).unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); + + assert_eq!(parsed["authorization"], REDACTED_AUTHORIZATION); + assert!(!json.contains("secret-token")); + assert_eq!(parsed["jsonrpc"], "2.0"); + assert_eq!(parsed["id"], 42); + assert_eq!(parsed["method"], "blockchain.scripthash.get_balance"); + assert_eq!(parsed["params"][0], "test-scripthash"); + } + + #[test] + fn test_request_redacted_without_authorization() { + let req = Request::new_id(1, "server.version", vec![]); + + assert_eq!( + serde_json::to_string(&req.redacted()).unwrap(), + serde_json::to_string(&req).unwrap() + ); + } } From 77f9c784f461392024aafb67ae96b55062434431 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Wed, 22 Jul 2026 15:21:36 -0300 Subject: [PATCH 3/3] fix: redact authorization token from `batch_call` trace log - collect the batch requests into a Vec so the wire bytes and the trace log both derive from the same source - serialize each request through `Request::redacted` in the trace line Co-Authored-By: Claude Fable 5 Signed-off-by: Leonardo Lima --- src/raw_client.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/raw_client.rs b/src/raw_client.rs index 5ebd4ea..b800402 100644 --- a/src/raw_client.rs +++ b/src/raw_client.rs @@ -912,7 +912,7 @@ impl ElectrumApi for RawClient { } fn batch_call(&self, batch: &Batch) -> Result, Error> { - let mut raw = Vec::new(); + let mut requests = Vec::new(); let mut missing_responses = Vec::new(); let mut answers = BTreeMap::new(); @@ -951,15 +951,27 @@ impl ElectrumApi for RawClient { self.waiting_map.lock()?.insert(req.id, sender); - raw.append(&mut serde_json::to_vec(&req)?); - raw.extend_from_slice(b"\n"); + requests.push(req); } if missing_responses.is_empty() { return Ok(vec![]); } - trace!("==> {}", String::from_utf8_lossy(&raw)); + let mut raw = Vec::new(); + for req in &requests { + raw.append(&mut serde_json::to_vec(req)?); + raw.extend_from_slice(b"\n"); + } + + trace!( + "==> {}", + requests + .iter() + .map(|req| serde_json::to_string(&req.redacted())) + .collect::, _>>()? + .join("\n") + ); let mut stream = self.stream.lock()?; stream.write_all(&raw)?;