Skip to content

Commit 8708b4b

Browse files
committed
refactor(runtime): decouple supervisor access and boundary audit validation
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
1 parent 76cfd0e commit 8708b4b

16 files changed

Lines changed: 575 additions & 126 deletions

File tree

‎.agents/skills/build-openshell-mxc-windows/SKILL.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ Windows MSVC for the supported deliverables:
2121
It intentionally does not make Windows a Docker, Kubernetes, Podman, or VM
2222
runtime host.
2323

24+
The supervisor and supervisor-process libraries participate in native Windows
25+
checks and tests. Their gateway session, boundary attachment, and TCP readiness
26+
are portable; only the optional Unix SSH/readiness socket adapters are gated.
27+
This is compile and control-plane coverage, not Windows isolation qualification.
28+
Preserve readiness gating on authenticated gateway acceptance and reconnection.
29+
Shared Sandbox Protocol audit validation defaults to strict Linux evidence;
30+
concrete platform validators must be selected by the implementing backend.
31+
2432
## Current Repository Shape
2533

2634
The Windows build lane is implemented by these tracked files:

‎.agents/skills/build-openshell-mxc-windows/reference.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,6 @@ top-level workspace targets for check/test:
111111
--exclude openshell-driver-vault
112112
--exclude openshell-driver-vm
113113
--exclude openshell-sandbox
114-
--exclude openshell-supervisor
115-
--exclude openshell-supervisor-process
116114
--exclude openshell-vfio
117115
```
118116

@@ -124,6 +122,10 @@ egress proxy. The Kubernetes Secrets and Vault libraries still compile as
124122
gateway dependencies; only their standalone Unix-socket binaries and
125123
package-level tests are excluded as top-level targets.
126124

125+
The supervisor and supervisor-process packages now participate as top-level
126+
native check/test targets. Their portable session, attachment, and TCP readiness
127+
coverage does not enable a Windows isolation runtime.
128+
127129
## Common Errors
128130

129131
### Unix imports leak into Windows builds

‎crates/openshell-cli/src/ssh.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,13 +359,15 @@ struct ConnectCancellation {
359359
}
360360

361361
impl ConnectCancellation {
362+
#[cfg_attr(not(unix), allow(clippy::unnecessary_wraps))] // Unix signal registration is fallible
362363
fn new() -> Result<Self> {
363364
Ok(Self {
364365
#[cfg(unix)]
365366
signals: TerminationSignals::new()?,
366367
})
367368
}
368369

370+
#[cfg_attr(not(unix), allow(clippy::needless_pass_by_ref_mut))] // Unix receives through mutable signals
369371
async fn wait<F, T>(&mut self, future: F) -> std::result::Result<T, i32>
370372
where
371373
F: Future<Output = T>,
@@ -411,6 +413,7 @@ async fn terminate_and_reap_child(child: &mut Child, signal: Signal) -> Result<i
411413
Ok(128 + signal as i32)
412414
}
413415

416+
#[cfg_attr(not(unix), allow(clippy::needless_pass_by_ref_mut))] // Unix receives through mutable signals
414417
async fn run_main_attach_supervised(
415418
session: &SshSessionConfig,
416419
cancellation: &mut ConnectCancellation,

‎crates/openshell-driver-mxc/src/driver.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ impl MxcComputeBackend {
455455
/// Test-only constructor wiring the in-process mock `wxc-exec` shim.
456456
#[cfg(test)]
457457
pub(crate) fn new_mocked(config: MxcComputeConfig) -> Self {
458-
let mut backend = Self::new(config);
458+
let mut backend = Self::new("test", config);
459459
backend.invoker = WxcExecInvoker::mocked(&backend.config.wxc_exec_path);
460460
backend
461461
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# OpenShell Sandbox Protocol backend
2+
3+
`OpenShellRuntimeBackend` implements the authenticated host side of the shared
4+
Sandbox Protocol.
5+
6+
Backend implementations may inject a `BoundaryAuditValidator` to interpret
7+
their opaque confirmation evidence. The default Linux validator rejects
8+
incomplete or foreign evidence. Confirmation compares the asserted properties
9+
with the properties derived by the selected validator; validator injection does
10+
not bypass generation, session, resource, identity, or outer-fence checks.
11+
12+
Concrete platform validators belong to the implementing backend, not this
13+
shared transport library.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Backend-selected interpretation of opaque boundary confirmation evidence.
5+
6+
use openshell_isolation_interface::contract::{BackendError, BoundaryProperties};
7+
8+
/// Validate measured evidence and derive the properties it actually supports.
9+
/// Implementations must reject incomplete evidence and unsupported formats.
10+
pub trait BoundaryAuditValidator: std::fmt::Debug + Send + Sync {
11+
/// # Errors
12+
/// Returns an error when evidence cannot establish the boundary guarantees.
13+
fn validate(&self, evidence: &serde_json::Value) -> Result<BoundaryProperties, BackendError>;
14+
}
15+
16+
/// Default evidence interpreter for the Linux `OpenShell` sandbox.
17+
#[derive(Debug)]
18+
pub struct LinuxBoundaryAuditValidator;
19+
20+
impl BoundaryAuditValidator for LinuxBoundaryAuditValidator {
21+
fn validate(&self, evidence: &serde_json::Value) -> Result<BoundaryProperties, BackendError> {
22+
let audit: crate::boundary_protocol::NativeLinuxSandboxAuditEvidence =
23+
serde_json::from_value(evidence.clone()).map_err(|error| {
24+
BackendError::Confirm(format!("decode Linux sandbox audit evidence: {error}"))
25+
})?;
26+
audit.validate()?;
27+
Ok(audit.properties())
28+
}
29+
}
30+
31+
#[cfg(test)]
32+
mod tests {
33+
use super::{BoundaryAuditValidator as _, LinuxBoundaryAuditValidator};
34+
35+
#[test]
36+
fn default_validator_accepts_complete_linux_evidence() {
37+
let evidence = serde_json::json!({
38+
"capabilities": {"inheritable": 0, "permitted": 0, "effective": 0,
39+
"bounding": 0, "ambient": 0},
40+
"no_new_privileges": true, "sandbox_dumpable": false,
41+
"child_dumpable": true, "core_limit_zero": true,
42+
"native_architecture": "test", "kernel_release": "test",
43+
"seccomp": {
44+
"new_listener": true, "notification_round_trip": true,
45+
"id_validation": true, "addfd_send": true,
46+
"retained_socket_operation": true, "proc_fd_identity": true,
47+
"task_memory_read": true, "task_memory_write": true, "cancellation": true,
48+
"task_memory_writes_disabled": false
49+
},
50+
"landlock_abi": 3, "landlock_allow_deny": true,
51+
"udp_dns_round_trip": true, "tcp_dns_round_trip": true,
52+
"tcp_allow_round_trip": true, "tcp_deny_round_trip": true
53+
});
54+
let properties = LinuxBoundaryAuditValidator.validate(&evidence).unwrap();
55+
let audit: crate::boundary_protocol::NativeLinuxSandboxAuditEvidence =
56+
serde_json::from_value(evidence.clone()).unwrap();
57+
assert_eq!(properties, audit.properties());
58+
let mut incomplete = evidence;
59+
incomplete["no_new_privileges"] = false.into();
60+
assert!(LinuxBoundaryAuditValidator.validate(&incomplete).is_err());
61+
}
62+
63+
#[test]
64+
fn default_validator_rejects_unknown_platform_and_incomplete_evidence() {
65+
for platform in ["windows_mxc", "unknown", "linux"] {
66+
let evidence = serde_json::json!({"platform": platform, "evidence": {}});
67+
assert!(LinuxBoundaryAuditValidator.validate(&evidence).is_err());
68+
}
69+
}
70+
}

‎crates/openshell-sandbox-backend/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//! runtime serves the same protocol using the generated server and shared wire
99
//! types in this crate.
1010
11+
pub mod audit;
1112
pub mod boundary_protocol;
1213
pub mod mediation;
1314
mod runtime;

‎crates/openshell-sandbox-backend/src/runtime.rs‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ fn begin_recovery_window(
7373
/// Host-side `OpenShell` Sandbox Protocol implementation registered with the supervisor.
7474
#[derive(Debug)]
7575
pub struct OpenShellRuntimeBackend {
76+
audit_validator: Arc<dyn crate::audit::BoundaryAuditValidator>,
7677
ca_file_paths: Arc<std::sync::Mutex<Option<(PathBuf, PathBuf)>>>,
7778
provider_credentials: openshell_core::provider_credentials::ProviderCredentialState,
7879
sandbox_bearer: openshell_core::jwt::SessionBearerTokenSlot,
@@ -99,11 +100,22 @@ impl OpenShellRuntimeBackend {
99100
sandbox_bearer: openshell_core::jwt::SessionBearerTokenSlot,
100101
) -> Self {
101102
Self {
103+
audit_validator: Arc::new(crate::audit::LinuxBoundaryAuditValidator),
102104
ca_file_paths,
103105
provider_credentials,
104106
sandbox_bearer,
105107
}
106108
}
109+
110+
/// Select the backend implementation that validates opaque audit evidence.
111+
#[must_use]
112+
pub fn with_audit_validator(
113+
mut self,
114+
validator: Arc<dyn crate::audit::BoundaryAuditValidator>,
115+
) -> Self {
116+
self.audit_validator = validator;
117+
self
118+
}
107119
}
108120

109121
#[async_trait]
@@ -147,6 +159,7 @@ impl IsolationBackend for OpenShellRuntimeBackend {
147159
));
148160
}
149161
Ok(Box::new(RemoteBound {
162+
audit_validator: self.audit_validator.clone(),
150163
client: client.clone(),
151164
agent: sandbox.agent,
152165
policy: sandbox.policy,
@@ -292,6 +305,7 @@ fn validate_control_port(port: u32) -> Result<(), BackendError> {
292305
}
293306

294307
struct RemoteBound {
308+
audit_validator: Arc<dyn crate::audit::BoundaryAuditValidator>,
295309
client: Arc<BoundaryClient>,
296310
agent: AgentSpec,
297311
policy: openshell_core::policy::SandboxPolicy,
@@ -332,17 +346,10 @@ impl BoundBoundary for RemoteBound {
332346
.to_string(),
333347
));
334348
}
335-
let audit: crate::boundary_protocol::NativeLinuxSandboxAuditEvidence =
336-
serde_json::from_value(confirmation.backend_audit.clone()).map_err(|error| {
337-
BackendError::Confirm(format!(
338-
"decode native Linux sandbox audit evidence: {error}"
339-
))
340-
})?;
341-
audit.validate()?;
342-
if confirmation.properties != audit.properties() {
349+
let properties = self.audit_validator.validate(&confirmation.backend_audit)?;
350+
if confirmation.properties != properties {
343351
return Err(BackendError::Confirm(
344-
"sandbox confirmation properties do not match native Linux audit evidence"
345-
.to_string(),
352+
"sandbox confirmation properties do not match validated audit evidence".to_string(),
346353
));
347354
}
348355
let client = self.client.clone();
@@ -2958,6 +2965,7 @@ mod tests {
29582965
test_bearer(&expected_token),
29592966
));
29602967
let bound = RemoteBound {
2968+
audit_validator: Arc::new(crate::audit::LinuxBoundaryAuditValidator),
29612969
client: client.clone(),
29622970
agent: context.agent,
29632971
policy: context.policy,

0 commit comments

Comments
 (0)