Source code

Revision control

Copy as Markdown

Other Tools

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<Mutex<Vec<u8>>>,
}
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<usize> {
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"
);
}