Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions crates/openshell-sandbox/data/sandbox-policy.rego
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ binary_allowed(policy, exec) if {
glob.match(b.path, ["/"], p)
}

# Binary matching: empty binaries list means no binary restriction.
# When a policy declares endpoints without specifying which binaries may
# access them, any binary is permitted (the endpoint itself is the gate).
binary_allowed(policy, _) if {
count(object.get(policy, "binaries", [])) == 0
}

user_declared_binary_allowed(policy, exec) if {
some b
b := policy.binaries[_]
Expand All @@ -187,6 +194,10 @@ user_declared_binary_allowed(policy, exec) if {
glob.match(b.path, ["/"], p)
}

user_declared_binary_allowed(policy, _) if {
count(object.get(policy, "binaries", [])) == 0
}

# --- Network action (allow / deny) ---
#
# These rules are mutually exclusive by construction:
Expand Down
57 changes: 57 additions & 0 deletions crates/openshell-sandbox/src/opa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5165,4 +5165,61 @@ network_policies:
let input = l7_input("h.test", 80, "HEAD", "/protected");
assert!(!eval_l7(&engine, &input));
}

#[test]
fn empty_binaries_allows_any_binary() {
let engine = test_engine();
let input = NetworkInput {
host: "open.example.com".into(),
port: 443,
binary_path: PathBuf::from("/any/random/binary"),
binary_sha256: "unused".into(),
ancestors: vec![],
cmdline_paths: vec![],
};
let decision = engine.evaluate_network(&input).unwrap();
assert!(
decision.allowed,
"Expected allow with empty binaries list, got deny: {}",
decision.reason
);
assert_eq!(decision.matched_policy.as_deref(), Some("open_endpoint"));
}

#[test]
fn unresolved_identity_allowed_with_empty_binaries() {
let engine = test_engine();
let input = NetworkInput {
host: "open.example.com".into(),
port: 443,
binary_path: PathBuf::from("<unresolved>"),
binary_sha256: String::new(),
ancestors: vec![],
cmdline_paths: vec![],
};
let decision = engine.evaluate_network(&input).unwrap();
assert!(
decision.allowed,
"Expected allow for unresolved identity with empty binaries, got deny: {}",
decision.reason
);
}

#[test]
fn unresolved_identity_denied_with_specific_binaries() {
let engine = test_engine();
let input = NetworkInput {
host: "api.anthropic.com".into(),
port: 443,
binary_path: PathBuf::from("<unresolved>"),
binary_sha256: String::new(),
ancestors: vec![],
cmdline_paths: vec![],
};
let decision = engine.evaluate_network(&input).unwrap();
assert!(
!decision.allowed,
"Expected deny for unresolved identity when policy requires specific binary"
);
}
}
24 changes: 18 additions & 6 deletions crates/openshell-sandbox/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1400,13 +1400,25 @@ fn evaluate_opa_tcp(
let identity = match resolve_process_identity(pid, peer_port, identity_cache) {
Ok(id) => id,
Err(err) => {
return deny(
err.reason,
err.binary,
err.binary_pid,
err.ancestors,
vec![],
// In container/K8s deployments, network namespace setup can cause
// ephemeral port mismatches between peer_addr and /proc/net/tcp.
// Fall through to OPA with an unresolved identity — policies without
// binary restrictions (empty binaries list) will still allow.
warn!(
port = peer_port,
reason = %err.reason,
binary = ?err.binary,
binary_pid = ?err.binary_pid,
ancestors = ?err.ancestors,
"identity resolution failed; using unresolved identity",
);
ResolvedIdentity {
bin_path: PathBuf::from("<unresolved>"),
binary_pid: 0,
ancestors: vec![],
cmdline_paths: vec![],
bin_hash: String::new(),
}
}
};

Expand Down
5 changes: 5 additions & 0 deletions crates/openshell-sandbox/testdata/sandbox-policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,8 @@ network_policies:
- { host: gitlab.com, port: 443 }
binaries:
- { path: /usr/bin/glab }

open_endpoint:
name: open_endpoint
endpoints:
- { host: open.example.com, port: 443 }
Loading