Skip to content
Open
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
34 changes: 28 additions & 6 deletions src/core/mxc_engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,9 @@ use wxc_common::sandbox_process::{SandboxProcess, StreamCloser};
pub fn spawn(request: &SandboxRequest) -> Result<Box<dyn SandboxProcess>, Error> {
let mut logger = Logger::new(Mode::Buffer);
let process = dispatch::spawn_runner(&request.inner, &mut logger).map_err(Error::from)?;
let mut warnings = process.warnings().to_vec();
for warning in logger.take_warnings() {
if !warnings.contains(&warning) {
warnings.push(warning);
}
}
let mut warnings = request.policy_warnings.clone();
append_unique(&mut warnings, process.warnings().iter().cloned());
append_unique(&mut warnings, logger.take_warnings());
if warnings.is_empty() {
Ok(process)
} else {
Expand All @@ -80,6 +77,14 @@ pub fn spawn(request: &SandboxRequest) -> Result<Box<dyn SandboxProcess>, Error>
}
}

fn append_unique(warnings: &mut Vec<String>, additional: impl IntoIterator<Item = String>) {
for warning in additional {
if !warnings.contains(&warning) {
warnings.push(warning);
}
}
}

/// A streaming process paired with security warnings emitted during spawn.
struct ProcessWithWarnings {
inner: Box<dyn SandboxProcess>,
Expand Down Expand Up @@ -131,3 +136,20 @@ impl SandboxProcess for ProcessWithWarnings {
self.inner.stderr_closer()
}
}

#[cfg(test)]
mod tests {
use super::append_unique;

#[test]
fn append_unique_preserves_order_and_removes_duplicates() {
let mut warnings = vec!["policy".to_string()];

append_unique(
&mut warnings,
["backend", "policy", "spawn"].map(str::to_string),
);

assert_eq!(warnings, ["policy", "backend", "spawn"]);
}
}
49 changes: 48 additions & 1 deletion src/core/mxc_engine/src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,10 @@ pub struct SandboxRequest {
/// The internal execution model. `pub(crate)` so the SDK's own modules and
/// unit tests can map/inspect it, while it stays out of the public API.
pub(crate) inner: ExecutionRequest,
/// Security warnings emitted while the policy was parsed and validated.
/// Spawn-time warnings are merged with these before the public SDK sees the
/// resulting sandbox handle.
pub(crate) policy_warnings: Vec<String>,
}

impl SandboxRequest {
Expand Down Expand Up @@ -844,7 +848,11 @@ pub fn build_request_with_containment(
// `script_code` before running), so tolerate a missing command.
let inner = wxc_common::config_parser::load_request_from_value(config, &mut logger, true)
.map_err(|e| MxcError::malformed_request(format!("failed to build request: {e}")))?;
Ok(SandboxRequest { inner })
let policy_warnings = logger.take_warnings();
Ok(SandboxRequest {
inner,
policy_warnings,
})
}

/// Construct the wire-format `ContainerConfig` JSON value for the supported
Expand Down Expand Up @@ -1314,6 +1322,45 @@ mod tests {
assert!(request.inner.policy.allow_local_network);
}

#[test]
fn build_request_retains_policy_security_warnings() {
let policy = policy_with_network(NetworkSection {
allow_outbound: true,
..Default::default()
});
let request = build_request_with_containment(
&policy,
&Containment::Wslc(WslcSection::default()),
None,
)
.expect("relaxed policy should build");

assert!(
request
.policy_warnings
.iter()
.any(|warning| warning.contains("network.defaultPolicy=allow")),
"parser warning must survive on the SDK request: {:?}",
request.policy_warnings
);
}

#[test]
fn build_request_secure_defaults_have_no_policy_warnings() {
let request = build_request_with_containment(
&minimal_policy(),
&Containment::Wslc(WslcSection::default()),
None,
)
.expect("secure-default policy should build");

assert!(
request.policy_warnings.is_empty(),
"secure defaults must stay quiet: {:?}",
request.policy_warnings
);
}

#[cfg(target_os = "macos")]
#[test]
fn seatbelt_extra_mach_lookups_and_keychain_round_trip() {
Expand Down
Loading
Loading