Skip to content

Commit aa4e4c4

Browse files
authored
test(verify): pin spaced-path cred reads into the verify gate (#57)
The extractor fix for quoted/escaped spaces in Bash paths landed in #42, but nothing pins the attack class. Add three verify cases (Chrome cookies quoted, Bitwarden escaped, 1Password group container) and an extractor unit test so a tokenizer regression fails the CI attack gate, not a user. Supersedes the unpushed fix/spaced-path-extraction branch: its tokenizer was an independent implementation of the same fix as #42; only its test coverage was still missing from main.
1 parent 3757b8d commit aa4e4c4

2 files changed

Lines changed: 30 additions & 0 deletions

File tree

src/evaluate/hook_schema.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,30 @@ mod tests {
349349
assert!(!paths.iter().any(|p| p.starts_with('-')));
350350
}
351351

352+
#[test]
353+
fn extract_paths_with_spaces_quoted_or_escaped() {
354+
// a quoted path with internal spaces must stay one matchable token, or a
355+
// `cat "~/Library/Application Support/.../Cookies"` slips past deny.paths.
356+
let q = extract_paths_from_command(
357+
"cat \"~/Library/Application Support/Google/Chrome/Default/Cookies\"",
358+
);
359+
assert!(
360+
q.contains(&"~/Library/Application Support/Google/Chrome/Default/Cookies".to_string()),
361+
"quoted spaced path not reassembled: {q:?}"
362+
);
363+
// backslash-escaped spaces are equivalent
364+
let e = extract_paths_from_command(
365+
"wc -c ~/Library/Application\\ Support/Bitwarden/data.json",
366+
);
367+
assert!(
368+
e.contains(&"~/Library/Application Support/Bitwarden/data.json".to_string()),
369+
"escaped spaced path not reassembled: {e:?}"
370+
);
371+
// single-quoted too
372+
let s = extract_paths_from_command("cp '/etc/master.passwd' /tmp/x");
373+
assert!(s.contains(&"/etc/master.passwd".to_string()));
374+
}
375+
352376
// ── extraction completeness (audit PR #2) ──────────────────────────────
353377
fn tc(json: &str) -> ToolCall {
354378
serde_json::from_str::<HookInput>(json).unwrap().to_tool_call()

src/verify/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ fn cases() -> Vec<Case> {
9393
c("brace-expand cred read", s(r#"{"tool_name":"Bash","tool_input":{"command":"cat /etc/{passwd,master.passwd}"}}"#), Block),
9494
c("ifs-split cred read", s(r#"{"tool_name":"Bash","tool_input":{"command":"cat${IFS}/etc/passwd"}}"#), Block),
9595
c("ansi-c escape cred read", s(r#"{"tool_name":"Bash","tool_input":{"command":"cat $'\\x2fetc\\x2fpasswd'"}}"#), Block),
96+
// --- spaced-path cred reads via Bash: the extractor must not split a
97+
// quoted/escaped path on its internal space (macOS Application Support
98+
// stores, 1Password, Bitwarden) ---
99+
c("bash read spaced cred path (quoted)", s(r#"{"tool_name":"Bash","tool_input":{"command":"cat \"~/Library/Application Support/Google/Chrome/Default/Cookies\""}}"#), Block),
100+
c("bash read spaced cred path (escaped)", s(r#"{"tool_name":"Bash","tool_input":{"command":"wc -c ~/Library/Application\\ Support/Bitwarden/data.json"}}"#), Block),
101+
c("bash copy spaced 1password vault", s(r#"{"tool_name":"Bash","tool_input":{"command":"cp \"~/Library/Group Containers/2BUA8C4S2C.com.1password/Library/Data/B5.sqlite\" /tmp/x"}}"#), Block),
96102
// --- benign dev work must NOT be caught (zero-FP guard) ---
97103
c("write project source", s(r#"{"tool_name":"Write","tool_input":{"file_path":"./src/main.rs","content":"fn main(){}"}}"#), Allow),
98104
c("cargo build", s(r#"{"tool_name":"Bash","tool_input":{"command":"cargo build --release"}}"#), Allow),

0 commit comments

Comments
 (0)