Fix UTF-8 unsafe truncation in sandbox log capture (#359)

This commit is contained in:
Lawyered
2026-03-04 15:25:26 +01:00
committed by GitHub
parent 9b47dbbaed
commit 31a4330f24
+51 -28
View File
@@ -63,6 +63,29 @@ pub struct ContainerRunner {
proxy_port: u16,
}
/// Append `text` into `buffer` up to `limit` bytes without breaking UTF-8.
///
/// Returns `true` when truncation occurred.
fn append_with_limit(buffer: &mut String, text: &str, limit: usize) -> bool {
if text.is_empty() {
return false;
}
if buffer.len() >= limit {
return true;
}
let remaining = limit - buffer.len();
if text.len() <= remaining {
buffer.push_str(text);
return false;
}
let end = crate::util::floor_char_boundary(text, remaining);
buffer.push_str(&text[..end]);
true
}
impl ContainerRunner {
/// Create a new container runner.
pub fn new(docker: Docker, image: String, proxy_port: u16) -> Self {
@@ -393,23 +416,11 @@ impl ContainerRunner {
match result {
Ok(LogOutput::StdOut { message }) => {
let text = String::from_utf8_lossy(&message);
if stdout.len() + text.len() > half_max {
truncated = true;
let remaining = half_max.saturating_sub(stdout.len());
stdout.push_str(&text[..remaining.min(text.len())]);
} else {
stdout.push_str(&text);
}
truncated |= append_with_limit(&mut stdout, &text, half_max);
}
Ok(LogOutput::StdErr { message }) => {
let text = String::from_utf8_lossy(&message);
if stderr.len() + text.len() > half_max {
truncated = true;
let remaining = half_max.saturating_sub(stderr.len());
stderr.push_str(&text[..remaining.min(text.len())]);
} else {
stderr.push_str(&text);
}
truncated |= append_with_limit(&mut stderr, &text, half_max);
}
Ok(_) => {}
Err(e) => {
@@ -439,23 +450,11 @@ impl ContainerRunner {
match result {
Ok(LogOutput::StdOut { message }) => {
let text = String::from_utf8_lossy(&message);
if stdout.len() < half_max {
let remaining = half_max.saturating_sub(stdout.len());
stdout.push_str(&text[..remaining.min(text.len())]);
if text.len() > remaining {
truncated = true;
}
}
truncated |= append_with_limit(&mut stdout, &text, half_max);
}
Ok(LogOutput::StdErr { message }) => {
let text = String::from_utf8_lossy(&message);
if stderr.len() < half_max {
let remaining = half_max.saturating_sub(stderr.len());
stderr.push_str(&text[..remaining.min(text.len())]);
if text.len() > remaining {
truncated = true;
}
}
truncated |= append_with_limit(&mut stderr, &text, half_max);
}
Ok(_) => {}
Err(e) => {
@@ -577,6 +576,30 @@ fn unix_socket_candidates_from_env(
mod tests {
use super::*;
#[test]
fn append_with_limit_truncates_on_utf8_boundary() {
let mut out = String::new();
let truncated = append_with_limit(&mut out, "ab🙂cd", 5);
assert!(truncated);
assert_eq!(out, "ab");
}
#[test]
fn append_with_limit_marks_truncated_when_full() {
let mut out = "abc".to_string();
let truncated = append_with_limit(&mut out, "z", 3);
assert!(truncated);
assert_eq!(out, "abc");
}
#[test]
fn append_with_limit_appends_without_truncation() {
let mut out = String::new();
let truncated = append_with_limit(&mut out, "hello", 10);
assert!(!truncated);
assert_eq!(out, "hello");
}
#[cfg(unix)]
#[test]
fn test_unix_socket_candidates_include_rootless_paths() {