commit 419e6176bf98528f997ec7c99cda9da79e20eb69
parent 971bcdb70904040295c44b43c2ca92b3fdcd57da
Author: triesap <tyson@radroots.org>
Date: Mon, 13 Apr 2026 05:04:35 +0000
sdk: redact secret-bearing debug output
Diffstat:
5 files changed, 164 insertions(+), 8 deletions(-)
diff --git a/crates/sdk/src/adapters/radrootsd.rs b/crates/sdk/src/adapters/radrootsd.rs
@@ -1,3 +1,4 @@
+use core::fmt;
use core::time::Duration;
use crate::RadrootsNostrEvent;
@@ -9,7 +10,7 @@ use reqwest::header::{AUTHORIZATION, CONTENT_TYPE, HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SdkRadrootsdSignerAuthority {
pub provider_runtime_id: String,
pub account_identity_id: String,
@@ -17,7 +18,23 @@ pub struct SdkRadrootsdSignerAuthority {
pub provider_signer_session_id: Option<String>,
}
-#[derive(Debug, Clone, Serialize)]
+impl fmt::Debug for SdkRadrootsdSignerAuthority {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut debug = f.debug_struct("SdkRadrootsdSignerAuthority");
+ debug.field("provider_runtime_id", &self.provider_runtime_id);
+ debug.field("account_identity_id", &self.account_identity_id);
+ debug.field(
+ "provider_signer_session_id",
+ &self
+ .provider_signer_session_id
+ .as_ref()
+ .map(|_| "<redacted>"),
+ );
+ debug.finish()
+ }
+}
+
+#[derive(Clone, Serialize)]
pub struct SdkRadrootsdListingPublishRequest {
pub listing: RadrootsListing,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -29,6 +46,18 @@ pub struct SdkRadrootsdListingPublishRequest {
pub idempotency_key: Option<String>,
}
+impl fmt::Debug for SdkRadrootsdListingPublishRequest {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut debug = f.debug_struct("SdkRadrootsdListingPublishRequest");
+ debug.field("listing", &self.listing);
+ debug.field("kind", &self.kind);
+ debug.field("signer_session_id", &"<redacted>");
+ debug.field("signer_authority", &self.signer_authority);
+ debug.field("idempotency_key", &self.idempotency_key);
+ debug.finish()
+ }
+}
+
impl SdkRadrootsdListingPublishRequest {
pub fn from_event(
event: &RadrootsNostrEvent,
@@ -57,7 +86,7 @@ pub struct SdkRadrootsdBridgePublishResponse {
pub job: SdkRadrootsdBridgeJob,
}
-#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
+#[derive(Clone, PartialEq, Eq, Deserialize)]
pub struct SdkRadrootsdBridgeJob {
pub job_id: String,
pub command: String,
@@ -76,6 +105,28 @@ pub struct SdkRadrootsdBridgeJob {
pub acknowledged_relay_count: usize,
}
+impl fmt::Debug for SdkRadrootsdBridgeJob {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut debug = f.debug_struct("SdkRadrootsdBridgeJob");
+ debug.field("job_id", &self.job_id);
+ debug.field("command", &self.command);
+ debug.field("status", &self.status);
+ debug.field("terminal", &self.terminal);
+ debug.field("recovered_after_restart", &self.recovered_after_restart);
+ debug.field("signer_mode", &"<redacted>");
+ debug.field(
+ "signer_session_id",
+ &self.signer_session_id.as_ref().map(|_| "<redacted>"),
+ );
+ debug.field("event_kind", &self.event_kind);
+ debug.field("event_id", &self.event_id);
+ debug.field("event_addr", &self.event_addr);
+ debug.field("relay_count", &self.relay_count);
+ debug.field("acknowledged_relay_count", &self.acknowledged_relay_count);
+ debug.finish()
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RadrootsdError {
InvalidAuthHeader(String),
diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs
@@ -1,5 +1,6 @@
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
+use core::fmt;
#[cfg(feature = "std")]
use std::{string::String, vec::Vec};
@@ -60,7 +61,7 @@ pub struct SdkRelayFailure {
pub error: String,
}
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
+#[derive(Clone, PartialEq, Eq, Default)]
pub struct SdkRadrootsdPublishReceipt {
pub accepted: bool,
pub deduplicated: bool,
@@ -73,6 +74,28 @@ pub struct SdkRadrootsdPublishReceipt {
pub acknowledged_relay_count: Option<usize>,
}
+impl fmt::Debug for SdkRadrootsdPublishReceipt {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut debug = f.debug_struct("SdkRadrootsdPublishReceipt");
+ debug.field("accepted", &self.accepted);
+ debug.field("deduplicated", &self.deduplicated);
+ debug.field("job_id", &self.job_id);
+ debug.field("status", &self.status);
+ debug.field(
+ "signer_mode",
+ &self.signer_mode.as_ref().map(|_| "<redacted>"),
+ );
+ debug.field(
+ "signer_session_id",
+ &self.signer_session_id.as_ref().map(|_| "<redacted>"),
+ );
+ debug.field("event_addr", &self.event_addr);
+ debug.field("relay_count", &self.relay_count);
+ debug.field("acknowledged_relay_count", &self.acknowledged_relay_count);
+ debug.finish()
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SdkPublishError {
Config(SdkConfigError),
diff --git a/crates/sdk/src/config.rs b/crates/sdk/src/config.rs
@@ -149,13 +149,22 @@ impl Default for RadrootsdConfig {
}
}
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
+#[derive(Clone, PartialEq, Eq, Default)]
pub enum RadrootsdAuth {
#[default]
None,
BearerToken(String),
}
+impl fmt::Debug for RadrootsdAuth {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::None => f.write_str("None"),
+ Self::BearerToken(_) => f.write_str("BearerToken(\"<redacted>\")"),
+ }
+ }
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SignerConfig {
#[default]
diff --git a/crates/sdk/tests/config.rs b/crates/sdk/tests/config.rs
@@ -151,3 +151,14 @@ fn retry_policy_is_explicit_and_non_ambient() {
assert_eq!(config.network.retry_policy, RetryPolicy::None);
}
+
+#[test]
+fn sdk_config_debug_redacts_bearer_tokens() {
+ let mut config = RadrootsSdkConfig::production();
+ config.radrootsd.auth = RadrootsdAuth::BearerToken("sdk-secret-token".to_owned());
+
+ let debug = format!("{config:?}");
+
+ assert!(!debug.contains("sdk-secret-token"));
+ assert!(debug.contains("BearerToken(\"<redacted>\")"));
+}
diff --git a/crates/sdk/tests/radrootsd.rs b/crates/sdk/tests/radrootsd.rs
@@ -12,8 +12,9 @@ use radroots_sdk::listing::{
};
use radroots_sdk::{
RadrootsNostrEvent, RadrootsSdkClient, RadrootsSdkConfig, RadrootsdAuth, RadrootsdConfig,
- SdkEnvironment, SdkPublishError, SdkRadrootsdListingPublishRequest, SdkTransportMode,
- SdkTransportReceipt, SignerConfig,
+ SdkEnvironment, SdkPublishError, SdkRadrootsdBridgeJob, SdkRadrootsdBridgePublishResponse,
+ SdkRadrootsdListingPublishRequest, SdkRadrootsdPublishReceipt, SdkRadrootsdSignerAuthority,
+ SdkTransportMode, SdkTransportReceipt, SignerConfig,
};
use serde_json::{Value, json};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
@@ -256,6 +257,65 @@ fn sdk_event(
}
}
+#[test]
+fn radrootsd_debug_redacts_signer_session_values() {
+ let signer_authority = SdkRadrootsdSignerAuthority {
+ provider_runtime_id: "runtime-1".to_owned(),
+ account_identity_id: "identity-1".to_owned(),
+ provider_signer_session_id: Some("provider-session-123".to_owned()),
+ };
+ let request = SdkRadrootsdListingPublishRequest {
+ listing: sample_listing(),
+ kind: Some(30402),
+ signer_session_id: "session-123".to_owned(),
+ signer_authority: Some(signer_authority),
+ idempotency_key: Some("idem-1".to_owned()),
+ };
+ let job = SdkRadrootsdBridgeJob {
+ job_id: "job-1".to_owned(),
+ command: "bridge.listing.publish".to_owned(),
+ status: "published".to_owned(),
+ terminal: true,
+ recovered_after_restart: false,
+ signer_mode: "nip46_session:session-123".to_owned(),
+ signer_session_id: Some("session-123".to_owned()),
+ event_kind: 30402,
+ event_id: Some("event-1".to_owned()),
+ event_addr: Some("30402:seller:listing-1".to_owned()),
+ relay_count: 1,
+ acknowledged_relay_count: 1,
+ };
+ let response = SdkRadrootsdBridgePublishResponse {
+ deduplicated: false,
+ job,
+ };
+ let receipt = SdkRadrootsdPublishReceipt {
+ accepted: true,
+ deduplicated: false,
+ job_id: Some("job-1".to_owned()),
+ status: Some("published".to_owned()),
+ signer_mode: Some("nip46_session:session-123".to_owned()),
+ signer_session_id: Some("session-123".to_owned()),
+ event_addr: Some("30402:seller:listing-1".to_owned()),
+ relay_count: Some(1),
+ acknowledged_relay_count: Some(1),
+ };
+
+ let request_debug = format!("{request:?}");
+ let response_debug = format!("{response:?}");
+ let receipt_debug = format!("{receipt:?}");
+
+ assert!(!request_debug.contains("session-123"));
+ assert!(!request_debug.contains("provider-session-123"));
+ assert!(request_debug.contains("<redacted>"));
+
+ assert!(!response_debug.contains("session-123"));
+ assert!(response_debug.contains("<redacted>"));
+
+ assert!(!receipt_debug.contains("session-123"));
+ assert!(receipt_debug.contains("<redacted>"));
+}
+
#[tokio::test]
async fn radrootsd_listing_publish_accepts_sdk_built_draft() -> TestResult<()> {
let (server, request_rx) = JsonRpcServer::spawn(
@@ -441,7 +501,9 @@ fn radrootsd_listing_request_from_event_rejects_listing_draft_kind() -> TestResu
assert!(matches!(
SdkRadrootsdListingPublishRequest::from_event(&event, "session-123", None, None),
- Err(RadrootsTradeListingParseError::InvalidKind(KIND_LISTING_DRAFT))
+ Err(RadrootsTradeListingParseError::InvalidKind(
+ KIND_LISTING_DRAFT
+ ))
));
Ok(())