diff --git a/crates/openshell-sandbox/data/sandbox-policy.rego b/crates/openshell-sandbox/data/sandbox-policy.rego index 7e9986ab..66b86419 100644 --- a/crates/openshell-sandbox/data/sandbox-policy.rego +++ b/crates/openshell-sandbox/data/sandbox-policy.rego @@ -129,22 +129,11 @@ binary_allowed(policy, exec) if { b.path == ancestor } -# Binary matching: cmdline exact path (script interpreters — e.g. node runs claude script). -# When /usr/local/bin/claude has shebang #!/usr/bin/env node, the exe is /usr/bin/node -# but cmdline contains /usr/local/bin/claude as an argv entry. -binary_allowed(policy, exec) if { - some b - b := policy.binaries[_] - not contains(b.path, "*") - cp := exec.cmdline_paths[_] - b.path == cp -} - -# Binary matching: glob pattern against path, any ancestor, or any cmdline path. +# Binary matching: glob pattern against path or any ancestor. binary_allowed(policy, exec) if { some b in policy.binaries contains(b.path, "*") - all_paths := array.concat(array.concat([exec.path], exec.ancestors), exec.cmdline_paths) + all_paths := array.concat([exec.path], exec.ancestors) some p in all_paths glob.match(b.path, ["/"], p) } diff --git a/crates/openshell-sandbox/src/opa.rs b/crates/openshell-sandbox/src/opa.rs index 705f0e62..3fd07480 100644 --- a/crates/openshell-sandbox/src/opa.rs +++ b/crates/openshell-sandbox/src/opa.rs @@ -730,10 +730,10 @@ mod tests { let input = NetworkInput { host: "api.anthropic.com".into(), port: 443, - binary_path: PathBuf::from("/usr/bin/node"), + binary_path: PathBuf::from("/usr/local/bin/claude"), binary_sha256: "unused".into(), ancestors: vec![], - cmdline_paths: vec![PathBuf::from("/usr/local/bin/claude")], + cmdline_paths: vec![], }; let decision = engine.evaluate_network(&input).unwrap(); assert!( @@ -828,10 +828,10 @@ mod tests { let input = NetworkInput { host: "API.ANTHROPIC.COM".into(), port: 443, - binary_path: PathBuf::from("/usr/bin/node"), + binary_path: PathBuf::from("/usr/local/bin/claude"), binary_sha256: "unused".into(), ancestors: vec![], - cmdline_paths: vec![PathBuf::from("/usr/local/bin/claude")], + cmdline_paths: vec![], }; let decision = engine.evaluate_network(&input).unwrap(); assert!( @@ -885,10 +885,10 @@ mod tests { let input = NetworkInput { host: "api.anthropic.com".into(), port: 443, - binary_path: PathBuf::from("/usr/bin/node"), + binary_path: PathBuf::from("/usr/local/bin/claude"), binary_sha256: "unused".into(), ancestors: vec![], - cmdline_paths: vec![PathBuf::from("/usr/local/bin/claude")], + cmdline_paths: vec![], }; let decision = engine.evaluate_network(&input).unwrap(); assert!(decision.allowed); @@ -902,10 +902,10 @@ mod tests { let input = NetworkInput { host: "api.anthropic.com".into(), port: 443, - binary_path: PathBuf::from("/usr/bin/node"), + binary_path: PathBuf::from("/usr/local/bin/claude"), binary_sha256: "unused".into(), ancestors: vec![], - cmdline_paths: vec![PathBuf::from("/usr/local/bin/claude")], + cmdline_paths: vec![], }; let decision = engine.evaluate_network(&input).unwrap(); assert!(decision.allowed); @@ -1098,9 +1098,8 @@ network_policies: } #[test] - fn cmdline_path_matches_script_binary() { - // Simulates: node runs /usr/local/bin/my-tool (a script with shebang) - // exe = /usr/bin/node, cmdline contains /usr/local/bin/my-tool + fn cmdline_path_does_not_grant_identity() { + // cmdline is attacker-controlled and must not grant binary identity. let cmdline_data = r" network_policies: script_test: @@ -1120,12 +1119,7 @@ network_policies: cmdline_paths: vec![PathBuf::from("/usr/local/bin/my-tool")], }; let decision = engine.evaluate_network(&input).unwrap(); - assert!( - decision.allowed, - "Expected allow via cmdline path match, got deny: {}", - decision.reason - ); - assert_eq!(decision.matched_policy.as_deref(), Some("script_test")); + assert!(!decision.allowed); } #[test] @@ -1156,7 +1150,7 @@ network_policies: } #[test] - fn cmdline_glob_pattern_matches() { + fn cmdline_glob_pattern_does_not_match() { let glob_data = r#" network_policies: glob_test: @@ -1170,17 +1164,13 @@ network_policies: let input = NetworkInput { host: "example.com".into(), port: 443, - binary_path: PathBuf::from("/usr/bin/node"), + binary_path: PathBuf::from("/usr/local/bin/claude"), binary_sha256: "unused".into(), ancestors: vec![], - cmdline_paths: vec![PathBuf::from("/usr/local/bin/claude")], + cmdline_paths: vec![], }; let decision = engine.evaluate_network(&input).unwrap(); - assert!( - decision.allowed, - "Expected glob to match cmdline path, got deny: {}", - decision.reason - ); + assert!(decision.allowed, "Expected glob to match executable path"); } #[test] @@ -1190,10 +1180,10 @@ network_policies: let input = NetworkInput { host: "api.anthropic.com".into(), port: 443, - binary_path: PathBuf::from("/usr/bin/node"), + binary_path: PathBuf::from("/usr/local/bin/claude"), binary_sha256: "unused".into(), ancestors: vec![], - cmdline_paths: vec![PathBuf::from("/usr/local/bin/claude")], + cmdline_paths: vec![], }; let decision = engine.evaluate_network(&input).unwrap(); assert!(