vtcode_core/cli/
input_hardening.rs1use anyhow::{Result, bail};
2
3fn is_disallowed_control_char(ch: char) -> bool {
4 if matches!(ch, '\n' | '\r' | '\t') {
5 return false;
6 }
7 ch.is_control()
8}
9
10fn first_disallowed_control_char(value: &str) -> Option<char> {
11 value.chars().find(|ch| is_disallowed_control_char(*ch))
12}
13
14pub fn validate_agent_safe_text(field_name: &str, value: &str) -> Result<()> {
15 if let Some(ch) = first_disallowed_control_char(value) {
16 bail!(
17 "Invalid {}: contains unsupported control character U+{:04X}",
18 field_name,
19 ch as u32
20 );
21 }
22 Ok(())
23}
24
25#[cfg(test)]
26mod tests {
27 use super::validate_agent_safe_text;
28
29 #[test]
30 fn allows_printable_text() {
31 validate_agent_safe_text("prompt", "hello world").unwrap();
32 }
33
34 #[test]
35 fn allows_newline_tab_and_carriage_return() {
36 validate_agent_safe_text("prompt", "line1\nline2\r\n\tindent").unwrap();
37 }
38
39 #[test]
40 fn rejects_nul() {
41 let err =
42 validate_agent_safe_text("prompt", "hello\0world").expect_err("nul should be rejected");
43 assert!(err.to_string().contains("U+0000"));
44 }
45
46 #[test]
47 fn rejects_other_control_characters() {
48 let err = validate_agent_safe_text("prompt", "hello\u{0007}world")
49 .expect_err("bell should be rejected");
50 assert!(err.to_string().contains("U+0007"));
51 }
52}