mirror of
https://github.com/instructkr/claude-code.git
synced 2026-04-05 18:58:48 +03:00
feat: bash validation module + output truncation parity
- Add bash_validation.rs with 9 submodules (1004 lines): readOnlyValidation, destructiveCommandWarning, modeValidation, sedValidation, pathValidation, commandSemantics, bashPermissions, bashSecurity, shouldUseSandbox - Wire into runtime lib.rs - Add MAX_OUTPUT_BYTES (16KB) truncation to bash.rs - Add 4 truncation tests, all passing - Full test suite: 270+ green
This commit is contained in:
@@ -134,8 +134,8 @@ async fn execute_bash_async(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (output, interrupted) = output_result;
|
let (output, interrupted) = output_result;
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
|
let stdout = truncate_output(&String::from_utf8_lossy(&output.stdout));
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
|
let stderr = truncate_output(&String::from_utf8_lossy(&output.stderr));
|
||||||
let no_output_expected = Some(stdout.trim().is_empty() && stderr.trim().is_empty());
|
let no_output_expected = Some(stdout.trim().is_empty() && stderr.trim().is_empty());
|
||||||
let return_code_interpretation = output.status.code().and_then(|code| {
|
let return_code_interpretation = output.status.code().and_then(|code| {
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
@@ -281,3 +281,53 @@ mod tests {
|
|||||||
assert!(!output.sandbox_status.expect("sandbox status").enabled);
|
assert!(!output.sandbox_status.expect("sandbox status").enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maximum output bytes before truncation (16 KiB, matching upstream).
|
||||||
|
const MAX_OUTPUT_BYTES: usize = 16_384;
|
||||||
|
|
||||||
|
/// Truncate output to `MAX_OUTPUT_BYTES`, appending a marker when trimmed.
|
||||||
|
fn truncate_output(s: &str) -> String {
|
||||||
|
if s.len() <= MAX_OUTPUT_BYTES {
|
||||||
|
return s.to_string();
|
||||||
|
}
|
||||||
|
// Find the last valid UTF-8 boundary at or before MAX_OUTPUT_BYTES
|
||||||
|
let mut end = MAX_OUTPUT_BYTES;
|
||||||
|
while end > 0 && !s.is_char_boundary(end) {
|
||||||
|
end -= 1;
|
||||||
|
}
|
||||||
|
let mut truncated = s[..end].to_string();
|
||||||
|
truncated.push_str("\n\n[output truncated — exceeded 16384 bytes]");
|
||||||
|
truncated
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod truncation_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn short_output_unchanged() {
|
||||||
|
let s = "hello world";
|
||||||
|
assert_eq!(truncate_output(s), s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn long_output_truncated() {
|
||||||
|
let s = "x".repeat(20_000);
|
||||||
|
let result = truncate_output(&s);
|
||||||
|
assert!(result.len() < 20_000);
|
||||||
|
assert!(result.ends_with("[output truncated — exceeded 16384 bytes]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exact_boundary_unchanged() {
|
||||||
|
let s = "a".repeat(MAX_OUTPUT_BYTES);
|
||||||
|
assert_eq!(truncate_output(&s), s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_over_boundary_truncated() {
|
||||||
|
let s = "a".repeat(MAX_OUTPUT_BYTES + 1);
|
||||||
|
let result = truncate_output(&s);
|
||||||
|
assert!(result.contains("[output truncated"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1004
rust/crates/runtime/src/bash_validation.rs
Normal file
1004
rust/crates/runtime/src/bash_validation.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
mod bash;
|
mod bash;
|
||||||
|
pub mod bash_validation;
|
||||||
mod bootstrap;
|
mod bootstrap;
|
||||||
mod compact;
|
mod compact;
|
||||||
mod config;
|
mod config;
|
||||||
|
|||||||
Reference in New Issue
Block a user