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
475 changes: 452 additions & 23 deletions crates/firma-run/src/backend/macos_vz.rs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions crates/firma-run/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub enum NetworkConfinement {
/// macOS `TrustedBSD` MAC sandbox with `deny network-outbound` policy.
/// Provides kernel-enforced loopback-only egress without a VM guest.
MacosSandboxNetworkDeny,
/// Apple Virtualization.framework guest with isolated virtio networking.
/// Implemented as a runner launch contract owned by the macOS VZ backend.
MacosVzGuest,
/// KVM micro-VM (Firecracker). Planned as enterprise additive path.
KvmMicroVm,
/// Proxy-only compatibility mode: enforcement depends on `HTTP_PROXY`
Expand Down
109 changes: 61 additions & 48 deletions crates/firma-run/src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,10 @@ pub fn prepare_network_runtime(
#[cfg(unix)]
let host_bridge = setup_host_bridge(&effective_endpoint, identity, &mut env_overrides)?;

// macOS sandbox-exec structural mode without a Linux network namespace
// also needs a host-side DNS refusal stub. sandbox-exec reaches it on
// loopback.
// macOS structural modes without a Linux network namespace also need a
// host-side DNS refusal stub. sandbox-exec reaches it on loopback; the
// VZ guest runner receives it through the launch contract and must wire
// guest DNS to this endpoint.
#[cfg(unix)]
let host_dns_stub = maybe_start_host_dns_stub(handle, proof, &mut env_overrides)?;

Expand Down Expand Up @@ -294,10 +295,12 @@ fn setup_host_bridge(
Ok(bridge)
}

/// Start a host-side DNS refusal stub for macOS sandbox-exec structural mode.
/// Start a host-side DNS refusal stub when the active confinement mechanism is
/// one of the macOS structural paths.
///
/// On the macOS sandbox-exec structural path the wrapped process can only reach
/// loopback. The stub refuses all DNS queries so the agent cannot resolve
/// loopback. On the VZ guest path the runner must expose this endpoint as the
/// guest resolver. The stub refuses all DNS queries so the agent cannot resolve
/// external hostnames directly; it must use the proxy bridge, which the Sidecar
/// controls. This function is a no-op for all other network confinement modes.
#[cfg(unix)]
Expand All @@ -309,7 +312,10 @@ fn maybe_start_host_dns_stub(
if handle.backend != BackendKind::Vz {
return Ok(None);
}
if proof.network_confinement != NetworkConfinement::MacosSandboxNetworkDeny {
if !matches!(
proof.network_confinement,
NetworkConfinement::MacosSandboxNetworkDeny | NetworkConfinement::MacosVzGuest
) {
return Ok(None);
}
let stub = crate::dns_stub::HostDnsStubHandle::start()?;
Expand Down Expand Up @@ -1072,11 +1078,11 @@ mod non_structural_env_tests {
drop(runtime);
}

/// When the proof carries the macOS sandbox-exec structural mechanism, a host-side DNS
/// When the proof carries a macOS structural mechanism, a host-side DNS
/// refusal stub must be started and its address exposed as
/// `FIRMA_DNS_STUB_ADDR`.
#[test]
fn macos_sandbox_exec_structural_path_starts_dns_stub_and_exposes_env_var() {
fn macos_structural_paths_start_dns_stub_and_expose_env_var() {
let fake_sidecar = TcpListener::bind("127.0.0.1:0").expect("bind");
let sidecar_addr = fake_sidecar.local_addr().expect("local_addr");

Expand All @@ -1096,48 +1102,55 @@ mod non_structural_env_tests {
startup_timeout: Duration::from_secs(1),
..AutostartFlags::default()
};
let authority = ResolvedAuthority {
url: "https://authority.test".to_string(),
ca_cert_path: None,
pub_key_path: None,
supervisor: None,
};
let structural_proof = crate::backend::EnforcementProof {
backend: BackendKind::Vz,
structural: true,
fail_closed: true,
detail: "test macos sandbox network deny".to_string(),
network_confinement: crate::backend::NetworkConfinement::MacosSandboxNetworkDeny,
};
let runtime = prepare_network_runtime(
&handle,
&structural_proof,
&SidecarEndpoint::Tcp { addr: sidecar_addr },
&identity,
&flags,
authority,
)
.expect("prepare_network_runtime must succeed for macOS structural proof");

let overrides = runtime.env_overrides();
assert!(
overrides.contains_key("FIRMA_DNS_STUB_ADDR"),
"FIRMA_DNS_STUB_ADDR must be set for macOS sandbox-exec structural mode; \
got keys: {:?}",
overrides.keys().collect::<Vec<_>>()
);
for mechanism in [
crate::backend::NetworkConfinement::MacosSandboxNetworkDeny,
crate::backend::NetworkConfinement::MacosVzGuest,
] {
let authority = ResolvedAuthority {
url: "https://authority.test".to_string(),
ca_cert_path: None,
pub_key_path: None,
supervisor: None,
};
let structural_proof = crate::backend::EnforcementProof {
backend: BackendKind::Vz,
structural: true,
fail_closed: true,
detail: format!("test {mechanism:?}"),
network_confinement: mechanism.clone(),
};

let runtime = prepare_network_runtime(
&handle,
&structural_proof,
&SidecarEndpoint::Tcp { addr: sidecar_addr },
&identity,
&flags,
authority,
)
.expect("prepare_network_runtime must succeed for macOS structural proof");

let overrides = runtime.env_overrides();
assert!(
overrides.contains_key("FIRMA_DNS_STUB_ADDR"),
"FIRMA_DNS_STUB_ADDR must be set when {mechanism:?} is active; \
got keys: {:?}",
overrides.keys().collect::<Vec<_>>()
);

let stub_addr_str = overrides.get("FIRMA_DNS_STUB_ADDR").expect("present");
let stub_addr: SocketAddr = stub_addr_str
.parse()
.expect("FIRMA_DNS_STUB_ADDR must be a valid SocketAddr");
assert!(
stub_addr.ip().is_loopback(),
"DNS stub must be on loopback: {stub_addr}"
);
assert_ne!(stub_addr.port(), 0, "DNS stub must have a real port");
let stub_addr_str = overrides.get("FIRMA_DNS_STUB_ADDR").expect("present");
let stub_addr: SocketAddr = stub_addr_str
.parse()
.expect("FIRMA_DNS_STUB_ADDR must be a valid SocketAddr");
assert!(
stub_addr.ip().is_loopback(),
"DNS stub must be on loopback: {stub_addr}"
);
assert_ne!(stub_addr.port(), 0, "DNS stub must have a real port");

drop(runtime);
drop(runtime);
}
}

#[test]
Expand Down Expand Up @@ -1172,7 +1185,7 @@ mod non_structural_env_tests {
structural: true,
fail_closed: true,
detail: "miswired macOS-looking proof on non-vz backend".to_string(),
network_confinement: crate::backend::NetworkConfinement::MacosSandboxNetworkDeny,
network_confinement: crate::backend::NetworkConfinement::MacosVzGuest,
};

let runtime = prepare_network_runtime(
Expand Down
4 changes: 3 additions & 1 deletion crates/firma-run/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1190,7 +1190,9 @@ mod tests {
let result = backend.enforce_network(&handle, &handle.network_policy);
if cfg!(target_os = "macos") {
let proof = result.unwrap();
if crate::config::env_truthy("FIRMA_RUN_VZ_STRUCTURAL_NETWORK") {
if crate::config::env_truthy("FIRMA_RUN_VZ_GUEST")
|| crate::config::env_truthy("FIRMA_RUN_VZ_STRUCTURAL_NETWORK")
{
assert!(
proof.structural,
"vz backend must report structural=true when macOS structural mode is enabled"
Expand Down
4 changes: 2 additions & 2 deletions docs-site/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ OpenFirma docs highlights for LLM-based retrieval:
- Linux `bwrap` provides the current default structural confinement path. `firecracker` is planned. Default `vz` and `wsl2` are proxy-based compatibility paths that can be bypassed by non-cooperative clients.
- macOS strategy: prioritize VZ guest-based structural parity first; treat Endpoint Security Framework (ESF) as selected host hardening/audit, not as standalone sidecar-only egress or DNS confinement.
- Experimental macOS structural mode: set FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1 on macOS vz backend for sandbox-exec network-deny mode; wrapped process may only reach loopback, so other host loopback services remain a caveat; network_confinement=macos_sandbox_network_deny; experimental until macOS E2E assertions pass on hardware.
- VZ guest-backed macOS structural parity is planned as a follow-up. The current branch does not expose an executable VZ guest launch contract path.
- EnforcementProof.network_confinement distinguishes linux_network_namespace (bwrap), macos_sandbox_network_deny (sandbox-exec structural), kvm_micro_vm (planned), proxy_only (current vz/wsl2 defaults).
- Experimental macOS VZ guest mode: set FIRMA_RUN_VZ_GUEST=1 plus FIRMA_RUN_VZ_GUEST_RUNNER, FIRMA_RUN_VZ_GUEST_KERNEL, FIRMA_RUN_VZ_GUEST_INITRD, and FIRMA_RUN_VZ_GUEST_ROOTFS absolute paths. firma run validates artifact paths and runner executability, emits macos_vz_guest proof metadata, writes vz-guest-launch.json, and spawns the runner with --launch-contract. The operator-provided runner must implement the Apple Virtualization.framework lifecycle and in-guest bridge-only egress.
- EnforcementProof.network_confinement distinguishes linux_network_namespace (bwrap), macos_sandbox_network_deny (sandbox-exec structural), macos_vz_guest (VZ guest runner contract), kvm_micro_vm (planned), proxy_only (current vz/wsl2 defaults).
- Capability token non-exposure applies when capabilities are pre-seeded into the Sidecar; current `firma run --capability-file` exports capability material into the wrapped process environment for compatibility.
- Runtime logs for non-structural backends use "backend compatibility proof" with `mode=proxy_only enforced=false` instead of "backend network enforcement proof".
- `firma config` reads existing target `firma.toml` values as defaults; CLI flags override only supplied fields.
Expand Down
12 changes: 6 additions & 6 deletions docs-site/src/content/docs/concepts/macos-structural-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ This keeps OpenFirma's claim language simple:
| ESF-native strategy | Secondary host hardening and audit path, not a standalone parity model. |
| Current default claim | Proxy-only compatibility mode unless an experimental structural mode is explicitly enabled. |
| Claim graduation bar | Hardware E2E evidence for sidecar-only egress, DNS confinement, fail-closed startup/runtime, direct-bypass resistance, and interactive CLI/TUI usability. |
| Implementation boundary | `firma run` owns proof metadata, preflight, routing artifacts, and process supervision. The planned VZ runner will own Virtualization.framework lifecycle and in-guest enforcement. |
| Implementation boundary | `firma run` owns proof metadata, preflight, routing artifacts, launch contracts, and process supervision. The VZ runner owns Virtualization.framework lifecycle and in-guest enforcement. |

## Baseline Reality Check

Current macOS default behavior is compatibility mode. The backend is named `vz`, but the default implementation launches a host process through `sandbox-exec`, injects proxy environment variables, and starts a host-side proxy bridge. It reports `structural=false` and requires explicit non-structural opt-in before launch. That provides useful ergonomics, attribution, and some filesystem masking for supported profiles, but it does not remove the agent's ability to open direct sockets, use direct DNS, or spawn a child with a clean environment.

One experimental structural path exists behind an explicit environment gate. `FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1` selects the intermediate sandbox-exec network-deny mode. The stronger VZ guest-backed path remains the primary parity direction, but its launch contract and runner integration are split into follow-up implementation work.
Two experimental structural paths now exist behind explicit environment gates. `FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1` selects the intermediate sandbox-exec network-deny mode. `FIRMA_RUN_VZ_GUEST=1` selects the stronger VZ guest runner contract path, which validates configured runner and guest image artifact paths and writes a deterministic launch contract. The contract path does not by itself ship Apple Virtualization.framework lifecycle code; the configured runner must own guest boot, virtio networking, guest DNS, stdio/signal/exit plumbing, and route proof inside the VM.

There is also no direct seccomp-BPF-to-ESF translation. Linux seccomp filters operate at syscall decision points; ESF exposes a different event model oriented around endpoint security events. ESF has authorization and notification events for areas such as process execution, file access, and some IPC, and it requires the Endpoint Security entitlement. It should be evaluated as its own macOS-native control plane, not as a syscall parity layer.

Expand Down Expand Up @@ -145,9 +145,9 @@ Recommendation from scoring: pursue Option A as the primary path, keep ESF as an
| ---- | ------ | ----- |
| Proof metadata | Implemented | `NetworkConfinement` added to `EnforcementProof`; all backends updated. |
| Intermediate macOS structural mode | Experimental | `sandbox-exec` network-deny mode via `FIRMA_RUN_VZ_STRUCTURAL_NETWORK=1`. It blocks external IP egress but still allows host loopback. |
| VZ guest runner contract | Planned | Follow-up work will add fail-closed artifact validation, `macos_vz_guest` proof metadata, a versioned launch contract, and runner supervision. |
| Host DNS refusal stub | Implemented | `HostDnsStubHandle` wired into macOS sandbox-exec structural routing. |
| Unit coverage | Implemented | Focused tests cover proof types, sandbox profile generation, DNS stub behavior, and routing setup. |
| VZ guest runner contract | Experimental | `FIRMA_RUN_VZ_GUEST=1` validates runner/kernel/initrd/rootfs paths, checks runner executability, emits `macos_vz_guest` proof metadata, writes `vz-guest-launch.json`, and spawns the configured runner. |
| Host DNS refusal stub | Implemented | `HostDnsStubHandle` wired into macOS structural routing paths and exposed to the VZ guest contract. |
| Unit coverage | Implemented | Focused tests cover proof types, sandbox profile generation, guest contract generation, DNS stub behavior, and routing setup. |
| macOS E2E schema | Written | `examples/firma-run/e2e/macos-structural-assertions.md` defines the hardware validation suite. |
| VZ guest runner implementation | Planned | Signed Apple Virtualization.framework runner, guest image lifecycle, and in-guest route proof remain the strategic parity work. |
| ESF hardening | Planned | Separate enterprise hardening and audit path. |
Expand Down Expand Up @@ -214,7 +214,7 @@ Recommendation from scoring: pursue Option A as the primary path, keep ESF as an
| -------------- | --------- | ------ | ----- |
| Structural proof metadata | Baseline proof | Done | `NetworkConfinement` on `EnforcementProof`; preflight logging. |
| Intermediate macOS network-deny mode | Intermediate structural mode | Done | `sandbox-exec` network-deny structural mode + DNS stub + routing wiring. |
| VZ guest runner contract | VZ contract | Planned | Fail-closed artifact validation, `macos_vz_guest` proof, launch-contract JSON, and runner spawn path. |
| VZ guest runner contract | VZ contract | Done | Fail-closed artifact validation, `macos_vz_guest` proof, launch-contract JSON, runner spawn path. |
| VZ runner implementation | VZ runner | Planned | Apple Virtualization.framework guest lifecycle with stdio, TTY, signals, terminal resize, and exit code preservation. |
| Guest image and route proof | VZ runner | Planned | Guest image lifecycle, bridge-only route setup, DNS stub wiring, and machine-readable proof fields. |
| macOS structural E2E suite | Evidence | Planned | Hardware tests for cooperative HTTP, policy deny, direct TCP/UDP, direct DNS, proxy-env-unset children, sidecar startup failure, and mid-session sidecar loss. |
Expand Down
Loading
Loading