Skip to content
Draft
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
8 changes: 8 additions & 0 deletions .agents/skills/build-openshell-mxc-windows/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ Windows MSVC for the supported deliverables:
It intentionally does not make Windows a Docker, Kubernetes, Podman, or VM
runtime host.

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

## Current Repository Shape

The Windows build lane is implemented by these tracked files:
Expand Down
6 changes: 4 additions & 2 deletions .agents/skills/build-openshell-mxc-windows/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ top-level workspace targets for check/test:
--exclude openshell-driver-vault
--exclude openshell-driver-vm
--exclude openshell-sandbox
--exclude openshell-supervisor
--exclude openshell-supervisor-process
--exclude openshell-vfio
```

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

The supervisor and supervisor-process packages now participate as top-level
native check/test targets. Their portable session, attachment, and TCP readiness
coverage does not enable a Windows isolation runtime.

## Common Errors

### Unix imports leak into Windows builds
Expand Down
3 changes: 3 additions & 0 deletions crates/openshell-cli/src/ssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,13 +359,15 @@ struct ConnectCancellation {
}

impl ConnectCancellation {
#[cfg_attr(not(unix), allow(clippy::unnecessary_wraps))] // Unix signal registration is fallible
fn new() -> Result<Self> {
Ok(Self {
#[cfg(unix)]
signals: TerminationSignals::new()?,
})
}

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

#[cfg_attr(not(unix), allow(clippy::needless_pass_by_ref_mut))] // Unix receives through mutable signals
async fn run_main_attach_supervised(
session: &SshSessionConfig,
cancellation: &mut ConnectCancellation,
Expand Down
2 changes: 1 addition & 1 deletion crates/openshell-driver-mxc/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ impl MxcComputeBackend {
/// Test-only constructor wiring the in-process mock `wxc-exec` shim.
#[cfg(test)]
pub(crate) fn new_mocked(config: MxcComputeConfig) -> Self {
let mut backend = Self::new(config);
let mut backend = Self::new("test", config);
backend.invoker = WxcExecInvoker::mocked(&backend.config.wxc_exec_path);
backend
}
Expand Down
13 changes: 13 additions & 0 deletions crates/openshell-sandbox-backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# OpenShell Sandbox Protocol backend

`OpenShellRuntimeBackend` implements the authenticated host side of the shared
Sandbox Protocol.

Backend implementations may inject a `BoundaryAuditValidator` to interpret
their opaque confirmation evidence. The default Linux validator rejects
incomplete or foreign evidence. Confirmation compares the asserted properties
with the properties derived by the selected validator; validator injection does
not bypass generation, session, resource, identity, or outer-fence checks.

Concrete platform validators belong to the implementing backend, not this
shared transport library.
70 changes: 70 additions & 0 deletions crates/openshell-sandbox-backend/src/audit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Backend-selected interpretation of opaque boundary confirmation evidence.

use openshell_isolation_interface::contract::{BackendError, BoundaryProperties};

/// Validate measured evidence and derive the properties it actually supports.
/// Implementations must reject incomplete evidence and unsupported formats.
pub trait BoundaryAuditValidator: std::fmt::Debug + Send + Sync {
/// # Errors
/// Returns an error when evidence cannot establish the boundary guarantees.
fn validate(&self, evidence: &serde_json::Value) -> Result<BoundaryProperties, BackendError>;
}

/// Default evidence interpreter for the Linux `OpenShell` sandbox.
#[derive(Debug)]
pub struct LinuxBoundaryAuditValidator;

impl BoundaryAuditValidator for LinuxBoundaryAuditValidator {
fn validate(&self, evidence: &serde_json::Value) -> Result<BoundaryProperties, BackendError> {
let audit: crate::boundary_protocol::NativeLinuxSandboxAuditEvidence =
serde_json::from_value(evidence.clone()).map_err(|error| {
BackendError::Confirm(format!("decode Linux sandbox audit evidence: {error}"))
})?;
audit.validate()?;
Ok(audit.properties())
}
}

#[cfg(test)]
mod tests {
use super::{BoundaryAuditValidator as _, LinuxBoundaryAuditValidator};

#[test]
fn default_validator_accepts_complete_linux_evidence() {
let evidence = serde_json::json!({
"capabilities": {"inheritable": 0, "permitted": 0, "effective": 0,
"bounding": 0, "ambient": 0},
"no_new_privileges": true, "sandbox_dumpable": false,
"child_dumpable": true, "core_limit_zero": true,
"native_architecture": "test", "kernel_release": "test",
"seccomp": {
"new_listener": true, "notification_round_trip": true,
"id_validation": true, "addfd_send": true,
"retained_socket_operation": true, "proc_fd_identity": true,
"task_memory_read": true, "task_memory_write": true, "cancellation": true,
"task_memory_writes_disabled": false
},
"landlock_abi": 3, "landlock_allow_deny": true,
"udp_dns_round_trip": true, "tcp_dns_round_trip": true,
"tcp_allow_round_trip": true, "tcp_deny_round_trip": true
});
let properties = LinuxBoundaryAuditValidator.validate(&evidence).unwrap();
let audit: crate::boundary_protocol::NativeLinuxSandboxAuditEvidence =
serde_json::from_value(evidence.clone()).unwrap();
assert_eq!(properties, audit.properties());
let mut incomplete = evidence;
incomplete["no_new_privileges"] = false.into();
assert!(LinuxBoundaryAuditValidator.validate(&incomplete).is_err());
}

#[test]
fn default_validator_rejects_unknown_platform_and_incomplete_evidence() {
for platform in ["windows_mxc", "unknown", "linux"] {
let evidence = serde_json::json!({"platform": platform, "evidence": {}});
assert!(LinuxBoundaryAuditValidator.validate(&evidence).is_err());
}
}
}
1 change: 1 addition & 0 deletions crates/openshell-sandbox-backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//! runtime serves the same protocol using the generated server and shared wire
//! types in this crate.

pub mod audit;
pub mod boundary_protocol;
pub mod mediation;
mod runtime;
Expand Down
28 changes: 18 additions & 10 deletions crates/openshell-sandbox-backend/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ fn begin_recovery_window(
/// Host-side `OpenShell` Sandbox Protocol implementation registered with the supervisor.
#[derive(Debug)]
pub struct OpenShellRuntimeBackend {
audit_validator: Arc<dyn crate::audit::BoundaryAuditValidator>,
ca_file_paths: Arc<std::sync::Mutex<Option<(PathBuf, PathBuf)>>>,
provider_credentials: openshell_core::provider_credentials::ProviderCredentialState,
sandbox_bearer: openshell_core::jwt::SessionBearerTokenSlot,
Expand All @@ -99,11 +100,22 @@ impl OpenShellRuntimeBackend {
sandbox_bearer: openshell_core::jwt::SessionBearerTokenSlot,
) -> Self {
Self {
audit_validator: Arc::new(crate::audit::LinuxBoundaryAuditValidator),
ca_file_paths,
provider_credentials,
sandbox_bearer,
}
}

/// Select the backend implementation that validates opaque audit evidence.
#[must_use]
pub fn with_audit_validator(
mut self,
validator: Arc<dyn crate::audit::BoundaryAuditValidator>,
) -> Self {
self.audit_validator = validator;
self
}
}

#[async_trait]
Expand Down Expand Up @@ -147,6 +159,7 @@ impl IsolationBackend for OpenShellRuntimeBackend {
));
}
Ok(Box::new(RemoteBound {
audit_validator: self.audit_validator.clone(),
client: client.clone(),
agent: sandbox.agent,
policy: sandbox.policy,
Expand Down Expand Up @@ -292,6 +305,7 @@ fn validate_control_port(port: u32) -> Result<(), BackendError> {
}

struct RemoteBound {
audit_validator: Arc<dyn crate::audit::BoundaryAuditValidator>,
client: Arc<BoundaryClient>,
agent: AgentSpec,
policy: openshell_core::policy::SandboxPolicy,
Expand Down Expand Up @@ -332,17 +346,10 @@ impl BoundBoundary for RemoteBound {
.to_string(),
));
}
let audit: crate::boundary_protocol::NativeLinuxSandboxAuditEvidence =
serde_json::from_value(confirmation.backend_audit.clone()).map_err(|error| {
BackendError::Confirm(format!(
"decode native Linux sandbox audit evidence: {error}"
))
})?;
audit.validate()?;
if confirmation.properties != audit.properties() {
let properties = self.audit_validator.validate(&confirmation.backend_audit)?;
if confirmation.properties != properties {
return Err(BackendError::Confirm(
"sandbox confirmation properties do not match native Linux audit evidence"
.to_string(),
"sandbox confirmation properties do not match validated audit evidence".to_string(),
));
}
let client = self.client.clone();
Expand Down Expand Up @@ -2958,6 +2965,7 @@ mod tests {
test_bearer(&expected_token),
));
let bound = RemoteBound {
audit_validator: Arc::new(crate::audit::LinuxBoundaryAuditValidator),
client: client.clone(),
agent: context.agent,
policy: context.policy,
Expand Down
Loading
Loading