use std::sync::{Arc, Mutex}; use tracing_subscriber::fmt::MakeWriter; /// Shared test writer that collects output for verification #[derive(Debug, Clone)] struct TestWriter { buf: Arc>>, } impl TestWriter { fn new() -> Self { Self { buf: Arc::new(Mutex::new(Vec::new())), } } fn get_output(&self) -> String { let buf = self.buf.lock().unwrap(); String::from_utf8_lossy(&buf).to_string() } } impl std::io::Write for TestWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.buf.lock().unwrap().extend_from_slice(buf); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } impl<'a> MakeWriter<'a> for TestWriter { type Writer = TestWriter; fn make_writer(&'a self) -> Self::Writer { self.clone() } } /// Test that basic security expectations are met - this is a smoke test /// for the ANSI escaping functionality using public APIs only #[test] fn test_error_ansi_escaping() { use std::fmt; #[derive(Debug)] struct MaliciousError(&'static str); impl fmt::Display for MaliciousError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl std::error::Error for MaliciousError {} let writer = TestWriter::new(); let subscriber = tracing_subscriber::fmt::Subscriber::builder() .with_writer(writer.clone()) .with_ansi(false) .without_time() .with_target(false) .with_level(false) .finish(); tracing::subscriber::with_default(subscriber, || { let malicious_error = MaliciousError("\x1b]0;PWNED\x07\x1b[2J\x08\x0c\x7f"); // This demonstrates that errors are logged - the actual escaping // is tested by our internal unit tests tracing::error!(error = %malicious_error, "An error occurred"); }); let output = writer.get_output(); // Just verify that something was logged assert!( output.contains("An error occurred"), "Error message should be logged" ); } /// Test that ANSI escape sequences in log messages are properly escaped #[test] fn test_message_ansi_escaping() { let writer = TestWriter::new(); let subscriber = tracing_subscriber::fmt::Subscriber::builder() .with_writer(writer.clone()) .with_ansi(false) .without_time() .with_target(false) .with_level(false) .finish(); tracing::subscriber::with_default(subscriber, || { let malicious_input = "\x1b]0;PWNED\x07\x1b[2J\x08\x0c\x7f"; // This should not cause ANSI injection tracing::info!("User input: {}", malicious_input); }); let output = writer.get_output(); // Verify ANSI sequences are escaped assert!( !output.contains('\x1b'), "Message output should not contain raw ESC characters" ); assert!( !output.contains('\x07'), "Message output should not contain raw BEL characters" ); } /// Test that JSON formatter properly escapes ANSI sequences #[cfg(feature = "json")] #[test] fn test_json_ansi_escaping() { let writer = TestWriter::new(); let subscriber = tracing_subscriber::fmt::Subscriber::builder() .json() .with_writer(writer.clone()) .finish(); tracing::subscriber::with_default(subscriber, || { let malicious_input = "\x1b]0;PWNED\x07\x1b[2J"; // JSON formatter should escape ANSI sequences tracing::info!("Testing: {}", malicious_input); tracing::info!(user_input = %malicious_input, "Field test"); }); let output = writer.get_output(); // JSON should escape ANSI sequences as Unicode escapes assert!( !output.contains('\x1b'), "JSON output should not contain raw ESC characters" ); assert!( !output.contains('\x07'), "JSON output should not contain raw BEL characters" ); } /// Test that pretty formatter properly escapes ANSI sequences #[cfg(feature = "ansi")] #[test] fn test_pretty_ansi_escaping() { let writer = TestWriter::new(); let subscriber = tracing_subscriber::fmt::Subscriber::builder() .pretty() .with_writer(writer.clone()) .with_ansi(false) .without_time() .with_target(false) .finish(); tracing::subscriber::with_default(subscriber, || { let malicious_input = "\x1b]0;PWNED\x07\x1b[2J"; // Pretty formatter should escape ANSI sequences tracing::info!("Testing: {}", malicious_input); }); let output = writer.get_output(); // Verify ANSI sequences are escaped assert!( !output.contains('\x1b'), "Pretty output should not contain raw ESC characters" ); assert!( !output.contains('\x07'), "Pretty output should not contain raw BEL characters" ); } /// Comprehensive test for ANSI sanitization that prevents injection attacks #[test] fn ansi_sanitization_prevents_injection() { let writer = TestWriter::new(); let subscriber = tracing_subscriber::fmt::Subscriber::builder() .with_writer(writer.clone()) .with_ansi(false) .without_time() .with_target(false) .with_level(false) .finish(); #[derive(Debug)] struct MaliciousError { content: String, } impl std::fmt::Display for MaliciousError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // This Display implementation contains ANSI escape sequences write!(f, "Error: {}", self.content) } } tracing::subscriber::with_default(subscriber, || { // Test 1: Field values should remain properly escaped by Debug (baseline) let malicious_field_value = "\x1b]0;PWNED\x07\x1b[2J"; tracing::error!(malicious_field = malicious_field_value, "Field test"); // Test 2: Message content vulnerability should be mitigated let malicious_error = MaliciousError { content: "\x1b]0;PWNED\x07\x1b[2J".to_string(), }; tracing::error!("{}", malicious_error); }); let output = writer.get_output(); // Field values should contain escaped sequences like \u{1b} assert!( output.contains("\\u{1b}"), "Field values should be escaped by Debug formatting" ); // Message content should be sanitized assert!( output.contains("\\x1b"), "Message content should be sanitized" ); assert!( !output.contains("\x1b]0;PWNED"), "Message content should not contain raw ANSI sequences" ); assert!( !output.contains("\x07"), "Message content should not contain raw control characters" ); } /// Test that C1 control characters (\x80-\x9f) are also properly escaped #[test] fn test_c1_control_characters_escaping() { let writer = TestWriter::new(); let subscriber = tracing_subscriber::fmt::Subscriber::builder() .with_writer(writer.clone()) .with_ansi(false) .without_time() .with_target(false) .with_level(false) .finish(); tracing::subscriber::with_default(subscriber, || { // Test C1 control characters that can be used in 8-bit terminal escape sequences let c1_controls = "\u{80}\u{85}\u{90}\u{9b}\u{9c}\u{9d}\u{9e}\u{9f}"; // Various C1 controls including CSI // This should escape C1 control characters to prevent 8-bit escape sequences tracing::info!("C1 controls: {}", c1_controls); }); let output = writer.get_output(); // Verify C1 control characters are escaped assert!( !output.contains('\u{80}'), "Output should not contain raw C1 control characters" ); assert!( !output.contains('\u{9b}'), "Output should not contain raw CSI character" ); assert!( !output.contains('\u{9c}'), "Output should not contain raw ST character" ); // Should contain Unicode escapes for C1 characters assert!( output.contains("\\u{80}") || output.contains("\\u{8"), "Should contain escaped C1 characters" ); }