Skip to main content

vtcode_core/cli/
input_hardening.rs

1use 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}