use serde_json::{Value, json};
use super::ContextEngine;
use lean_ctx::core::context_admission::egress::{self, EgressBody, EgressTarget, admit_request};
const AWS_KEY: &str = concat!("AK", "IAIOSFODNN7EXAMPLE");
const VALID_CARD: &str = "4111 1111 1111 1111";
const REMOTE: EgressTarget<'static> = EgressTarget {
provider: "Anthropic",
model: Some("claude-sonnet-5-5"),
upstream_base: "https://api.anthropic.com",
};
fn isolated(config: &str) -> lean_ctx::core::data_dir::IsolatedDataDir {
let data = lean_ctx::core::data_dir::isolated_data_dir();
let dir = lean_ctx::core::paths::config_dir_read_only().expect("isolated config dir");
std::fs::create_dir_all(&dir).expect("config dir");
std::fs::write(dir.join("config.toml"), config).expect("config");
data
}
fn simple_text_request(text: &str) -> Value {
json!({"model":"m","messages":[{"role":"user","content":text}]})
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[serial_test::serial(cache_telemetry)]
async fn a_governed_round_leaves_no_canary_anywhere_under_the_data_dir() {
let data = isolated("debug_log = true\n[context_gateway]\nmode = \"governed\"\n");
let canaries = [AWS_KEY, VALID_CARD];
let leaked = format!("aws_access_key_id = {AWS_KEY}\ncard = {VALID_CARD}\n");
let project = tempfile::tempdir().expect("project");
std::fs::create_dir(project.path().join(".git")).expect("project marker");
std::fs::write(project.path().join("deploy.cfg"), &leaked).expect("source file");
let engine = ContextEngine::with_project_root(project.path());
let path = project.path().join("deploy.cfg");
let read = engine
.call_tool_text(
"ctx_read",
Some(json!({"path": path.to_string_lossy(), "mode": "full"})),
)
.await
.expect("ctx_read");
let task = engine
.call_tool_text(
"ctx_session",
Some(json!({"action": "task", "value": format!("rotate {AWS_KEY}")})),
)
.await
.expect("ctx_session");
for (tool, text) in [("ctx_read", &read), ("ctx_session", &task)] {
for canary in canaries {
assert!(!text.contains(canary), "{tool} returned it: {text}");
}
}
let doc = simple_text_request(&leaked);
let outcome = admit_request(&doc, &REMOTE);
let forwarded = match &outcome.body {
EgressBody::Rewritten(masked) => masked.to_string(),
EgressBody::Refused(_) => String::new(),
EgressBody::Unchanged => panic!("governed mode forwarded the canaries"),
};
egress::finish_admission_for(
&outcome,
(!forwarded.is_empty()).then_some(forwarded.as_bytes()),
doc.to_string().len(),
None,
egress::EgressCaller::Proxy,
)
.expect("receipts stored");
let mut pending = vec![data.path().to_path_buf()];
let mut swept = 0;
while let Some(directory) = pending.pop() {
for entry in std::fs::read_dir(&directory).expect("read dir").flatten() {
let path = entry.path();
if path.is_dir() {
pending.push(path);
continue;
}
let Ok(bytes) = std::fs::read(&path) else {
continue;
};
swept += 1;
for canary in canaries {
assert!(
!bytes
.windows(canary.len())
.any(|window| window == canary.as_bytes()),
"{} holds a canary",
path.display()
);
}
}
}
assert!(swept > 0, "the round wrote nothing to sweep");
}