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!("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}