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!("Invalid {}: contains unsupported control character U+{:04X}", field_name, ch as u32);
17 }
18 Ok(())
19}
20
21#[cfg(test)]
22mod tests {
23 use super::validate_agent_safe_text;
24
25 #[test]
26 fn allows_printable_text() {
27 validate_agent_safe_text("prompt", "hello world").unwrap();
28 }
29
30 #[test]
31 fn allows_newline_tab_and_carriage_return() {
32 validate_agent_safe_text("prompt", "line1\nline2\r\n\tindent").unwrap();
33 }
34
35 #[test]
36 fn rejects_nul() {
37 let err = validate_agent_safe_text("prompt", "hello\0world").expect_err("nul should be rejected");
38 assert!(err.to_string().contains("U+0000"));
39 }
40
41 #[test]
42 fn rejects_other_control_characters() {
43 let err = validate_agent_safe_text("prompt", "hello\u{0007}world").expect_err("bell should be rejected");
44 assert!(err.to_string().contains("U+0007"));
45 }
46}