use assert_cmd::Command;
use std::fs;
use std::path::Path;
type TestResult = Result<(), Box<dyn std::error::Error>>;
const MANIFEST: &str = r#"
version = 1
canonical = "json-schema-2020-12"
authoring = "zod"
[[contracts]]
name = "user"
source = "contracts/user.zod.ts"
emit = ["zod"]
[[mappings]]
glob = "app/api/**/*.ts"
contracts = ["user"]
require = "boundary-validation"
[gates]
suppression_comments = "deny"
protected_paths = ["pushkin.toml"]
retrieval_paths = ["crates/**/*.rs"]
retrieval_tool = "mcp__codebase-retrieval__codebase-retrieval"
"#;
const GATED: &str = "crates/pushkin-cli/src/existing.rs";
const NEW_GATED: &str = "crates/pushkin-cli/tests/brand_new.rs";
const UNGATED: &str = "docs/notes.md";
const RULE_RAW_READ: &str = "pushkin.retrieval.raw_read";
fn repo() -> Result<tempfile::TempDir, Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
fs::write(dir.path().join("pushkin.toml"), MANIFEST)?;
fs::create_dir_all(dir.path().join("contracts"))?;
fs::write(
dir.path().join("contracts/user.zod.ts"),
"export const user = 1;\n",
)?;
fs::create_dir_all(dir.path().join("crates/pushkin-cli/src"))?;
fs::write(dir.path().join(GATED), "fn existing() {}\n")?;
fs::create_dir_all(dir.path().join("crates/pushkin-cli/tests"))?;
fs::create_dir_all(dir.path().join("docs"))?;
fs::write(dir.path().join(UNGATED), "notes\n")?;
Ok(dir)
}
fn bash(command: &str) -> String {
serde_json::json!({
"session_id": "f57",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": command },
})
.to_string()
}
fn hook(dir: &Path, payload: &str) -> Result<String, Box<dyn std::error::Error>> {
let output = Command::cargo_bin("pushkin")?
.current_dir(dir)
.write_stdin(payload.to_owned())
.env("PUSHKIN_DAEMON", "off")
.args(["hook", "claude"])
.output()?;
Ok(format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
))
}
#[test]
fn a_redirect_creating_a_gated_file_is_not_a_read() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &bash(&format!("cat > {NEW_GATED}")))?;
assert!(
!output.contains(RULE_RAW_READ),
"`>` writes to this path; nothing is read from it: {output}"
);
Ok(())
}
#[test]
fn an_append_redirect_is_not_a_read() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &bash(&format!("cat >> {NEW_GATED}")))?;
assert!(!output.contains(RULE_RAW_READ), "{output}");
Ok(())
}
#[test]
fn a_redirect_attached_to_its_path_is_not_a_read() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &bash(&format!("cat >{NEW_GATED}")))?;
assert!(!output.contains(RULE_RAW_READ), "{output}");
Ok(())
}
#[test]
fn a_numbered_redirect_is_not_a_read() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &bash(&format!("cat 2> {NEW_GATED}")))?;
assert!(!output.contains(RULE_RAW_READ), "{output}");
Ok(())
}
#[test]
fn a_redirect_over_an_existing_gated_file_is_not_a_read() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &bash(&format!("echo hi > {GATED}")))?;
assert!(!output.contains(RULE_RAW_READ), "{output}");
Ok(())
}
#[test]
fn a_plain_read_of_a_gated_file_is_still_denied() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &bash(&format!("cat {GATED}")))?;
assert!(
output.contains(RULE_RAW_READ),
"an ordinary read is untouched by this fix: {output}"
);
Ok(())
}
#[test]
fn a_read_whose_output_is_redirected_is_still_denied_on_the_source() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &bash(&format!("cat {GATED} > {UNGATED}")))?;
assert!(
output.contains(RULE_RAW_READ),
"the SOURCE is still read; only the destination is exempt: {output}"
);
Ok(())
}
#[test]
fn input_redirection_of_a_gated_file_is_still_denied() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &bash(&format!("cat < {GATED}")))?;
assert!(
output.contains(RULE_RAW_READ),
"`<` reads; only `>` writes: {output}"
);
Ok(())
}
#[test]
fn a_recursive_search_into_a_gated_directory_is_still_denied() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &bash("grep -rn thing crates/"))?;
assert!(output.contains(RULE_RAW_READ), "{output}");
Ok(())
}
#[test]
fn a_write_verb_is_unaffected() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &bash(&format!("rm {GATED}")))?;
assert!(!output.contains(RULE_RAW_READ), "{output}");
Ok(())
}
#[test]
fn a_heredoc_body_mentioning_a_gated_path_is_not_a_read() -> TestResult {
let dir = repo()?;
let command = format!("cat > {UNGATED} <<'EOF'\n// see {GATED} for the original\nEOF\n");
let output = hook(dir.path(), &bash(&command))?;
assert!(
!output.contains(RULE_RAW_READ),
"the body is content being WRITTEN, not a path being read: {output}"
);
Ok(())
}