feat(taint): stage 1 of argument provenance — the tap records hosts fetched content named
The "derived from untrusted content" half of ActGov's invariant (no outbound action whose target came from untrusted content). Observed only; no rule reads it yet. - the tap runs a node extractor only when a payload could be a fetch (WebFetch, WebSearch, curl/wget in command position) and appends the response's URL hosts, minus the agent's own target, to untrusted-hosts.txt beside the tap — a path hook-files already protects - capped at 500, deduplicated, and the tap still always exits 0 - both tiers drain it per finished phase into a taint.hosts event - shell-tested against the generated hook with the real node; the test caught `grep -r curl docs` being read as a fetch Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
509b7ceb89
commit
597e76b261
@@ -296,6 +296,38 @@ pub async fn drain_would_deny(docker: &Docker, container: &str) -> Vec<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Hosts named in fetched content, as the tap recorded them. One event per
|
||||
/// finished phase, carrying the whole list: stage 1 of argument provenance
|
||||
/// is observation only, and this is what gets inspected before any rule is
|
||||
/// built on it.
|
||||
pub const TAINT_HOSTS: &str = "taint.hosts";
|
||||
|
||||
/// Read the tap's taint file. NOT cleared, unlike every drain above: it is
|
||||
/// the state a future `untrusted-target` rule consults for the rest of the
|
||||
/// mission, so each phase's event is the set known when that phase ended.
|
||||
pub async fn drain_taint(docker: &Docker, container: &str) -> Vec<String> {
|
||||
let argv = vec![
|
||||
"sh".to_string(),
|
||||
"-lc".to_string(),
|
||||
crate::vm_tool_tap::taint_probe(TAP_DIR),
|
||||
];
|
||||
match crate::container_exec::exec_as_root(docker, container, None, &argv, INSTALL_TIMEOUT).await
|
||||
{
|
||||
Ok(out) => crate::vm_tool_tap::parse_taint(&out.stdout),
|
||||
Err(_) => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The detail of a [`TAINT_HOSTS`] event.
|
||||
pub fn taint_detail(hosts: &[String], tier: &str) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"hosts": hosts,
|
||||
"count": hosts.len(),
|
||||
"capped": hosts.len() >= crate::vm_tool_tap::MAX_TAINT_HOSTS,
|
||||
"tier": tier,
|
||||
})
|
||||
}
|
||||
|
||||
/// The tap file inside the mission container.
|
||||
pub fn tap_file() -> String {
|
||||
format!("{TAP_DIR}/tools.jsonl")
|
||||
|
||||
Reference in New Issue
Block a user