From a4fcea1afebf530182dc04c59f3d7cbd4cf6325d Mon Sep 17 00:00:00 2001 From: Jacob Kiers Date: Fri, 21 Nov 2025 17:04:31 +0100 Subject: [PATCH] Mask details in debug traces. --- banks2ff/src/core/models.rs | 79 ++++++++++++++++++++++++++++++++++++- banks2ff/src/core/sync.rs | 4 +- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/banks2ff/src/core/models.rs b/banks2ff/src/core/models.rs index 6adba01..d4c623a 100644 --- a/banks2ff/src/core/models.rs +++ b/banks2ff/src/core/models.rs @@ -1,7 +1,8 @@ use rust_decimal::Decimal; use chrono::NaiveDate; +use std::fmt; -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] pub struct BankTransaction { /// Source ID (GoCardless transactionId) pub internal_id: String, @@ -23,9 +24,83 @@ pub struct BankTransaction { pub counterparty_iban: Option, } -#[derive(Debug, Clone, PartialEq)] +impl fmt::Debug for BankTransaction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BankTransaction") + .field("internal_id", &self.internal_id) + .field("date", &self.date) + .field("amount", &"[REDACTED]") + .field("currency", &self.currency) + .field("foreign_amount", &self.foreign_amount.as_ref().map(|_| "[REDACTED]")) + .field("foreign_currency", &self.foreign_currency) + .field("description", &"[REDACTED]") + .field("counterparty_name", &self.counterparty_name.as_ref().map(|_| "[REDACTED]")) + .field("counterparty_iban", &self.counterparty_iban.as_ref().map(|_| "[REDACTED]")) + .finish() + } +} + +#[derive(Clone, PartialEq)] pub struct Account { pub id: String, pub iban: String, pub currency: String, } + +impl fmt::Debug for Account { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Account") + .field("id", &self.id) + .field("iban", &"[REDACTED]") + .field("currency", &self.currency) + .finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rust_decimal::Decimal; + + #[test] + fn test_bank_transaction_debug_masks_sensitive_data() { + let tx = BankTransaction { + internal_id: "test-id".to_string(), + date: NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(), + amount: Decimal::new(12345, 2), // 123.45 + currency: "EUR".to_string(), + foreign_amount: Some(Decimal::new(67890, 2)), // 678.90 + foreign_currency: Some("USD".to_string()), + description: "Test transaction".to_string(), + counterparty_name: Some("Test Counterparty".to_string()), + counterparty_iban: Some("DE1234567890".to_string()), + }; + + let debug_str = format!("{:?}", tx); + assert!(debug_str.contains("internal_id")); + assert!(debug_str.contains("date")); + assert!(debug_str.contains("currency")); + assert!(debug_str.contains("foreign_currency")); + assert!(debug_str.contains("[REDACTED]")); + assert!(!debug_str.contains("123.45")); + assert!(!debug_str.contains("678.90")); + assert!(!debug_str.contains("Test transaction")); + assert!(!debug_str.contains("Test Counterparty")); + assert!(!debug_str.contains("DE1234567890")); + } + + #[test] + fn test_account_debug_masks_iban() { + let account = Account { + id: "123".to_string(), + iban: "DE1234567890".to_string(), + currency: "EUR".to_string(), + }; + + let debug_str = format!("{:?}", account); + assert!(debug_str.contains("id")); + assert!(debug_str.contains("currency")); + assert!(debug_str.contains("[REDACTED]")); + assert!(!debug_str.contains("DE1234567890")); + } +} diff --git a/banks2ff/src/core/sync.rs b/banks2ff/src/core/sync.rs index ead1a62..1eb77e2 100644 --- a/banks2ff/src/core/sync.rs +++ b/banks2ff/src/core/sync.rs @@ -27,14 +27,14 @@ where let end_date = cli_end_date.unwrap_or_else(|| Local::now().date_naive() - chrono::Duration::days(1)); for account in accounts { - let span = tracing::info_span!("sync_account", iban = %account.iban); + let span = tracing::info_span!("sync_account", account_id = %account.id); let _enter = span.enter(); info!("Processing account..."); let dest_id_opt = destination.resolve_account_id(&account.iban).await?; let Some(dest_id) = dest_id_opt else { - warn!("Account {} not found in destination. Skipping.", account.iban); + warn!("Account {} not found in destination. Skipping.", account.id); continue; };