Skip to content

[FIR-112]: macOS structural confinement strategy decision: parity path vs ESF-native model#141

Open
Emulator000 wants to merge 11 commits into
mainfrom
dario/fir-112-macos-structural-confinement-strategy-decision-parity-path
Open

[FIR-112]: macOS structural confinement strategy decision: parity path vs ESF-native model#141
Emulator000 wants to merge 11 commits into
mainfrom
dario/fir-112-macos-structural-confinement-strategy-decision-parity-path

Conversation

@Emulator000

@Emulator000 Emulator000 commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Thinking Path

  • OpenFirma is a runtime enforcement boundary: every outbound agent call should pass through a local Sidecar that evaluates Cedar policies and allows or denies the action.
  • This branch touches firma-run, the runtime wrapper responsible for selecting a sandbox backend, preparing network confinement artifacts, launching the wrapped process, and reporting enforcement proof metadata.
  • The macOS vz backend previously had a proxy-only default and an intermediate sandbox-exec network-deny mode, but the stronger VZ guest path was still only a planned mechanism.
  • The project needs a macOS strategy that moves toward Linux-like structural invariants without over-claiming what sandbox-exec or Endpoint Security Framework can provide alone.
  • This pull request adds a concrete Rust-side VZ guest launch contract path while keeping the actual Apple Virtualization.framework runner as a separate, explicit implementation boundary.
  • It also updates the macOS strategy documentation, capability matrix, ESF boundaries, risk scoring, follow-up milestones, and E2E expectations so runtime/security reviewers can evaluate the direction cleanly.
  • The benefit is a clearer macOS parity path: firma run now has fail-closed guest-mode preflight, structural proof metadata, routing/DNS contract wiring, and documentation that distinguishes implemented contract behavior from still-required VM runner enforcement.

What Changed

  • Added FIRMA_RUN_VZ_GUEST=1 mode for the macOS vz backend.
  • Added fail-closed validation for VZ guest runner and guest image artifact paths:
    • FIRMA_RUN_VZ_GUEST_RUNNER
    • FIRMA_RUN_VZ_GUEST_KERNEL
    • FIRMA_RUN_VZ_GUEST_INITRD
    • FIRMA_RUN_VZ_GUEST_ROOTFS
  • Added macos_vz_guest enforcement proof behavior with structural=true when VZ guest mode is selected.
  • Added a versioned vz-guest-launch.json contract containing:
    • sandbox ID and runtime directory
    • runner path
    • guest kernel/initrd/rootfs paths
    • command, args, cwd, env, mounts, identity mode, seccomp artifact path
    • proxy URL, DNS stub address, attribution headers
    • required runtime invariants
  • Added VZ guest runner spawning via --launch-contract <path>.
  • Wired macOS structural DNS stub support for both sandbox-exec structural mode and VZ guest mode.
  • Updated runtime proof expectations for macOS structural env gates.
  • Added focused tests for:
    • VZ mode precedence
    • VZ guest contract generation
    • macos_vz_guest serialization
    • DNS stub exposure for both macOS structural mechanisms
  • Updated docs and retrieval notes:
    • macOS strategy decision memo
    • sandbox boundary docs
    • firma run guide
    • macOS E2E assertion schema
    • llms.txt
  • Documented ESF as selected host hardening/audit, not standalone structural network confinement.
  • Added explicit decision summary, risk/complexity scoring, follow-up implementation cards, and definition-of-done status for the macOS strategy.

Verification

cargo test -p firma-run

Result:

test result: ok. 145 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
integration tests: all passed
doc-tests firma_run: ok
npx -y node@22.12.0 ./node_modules/astro/bin/astro.mjs build

Result:

31 page(s) built
build complete

Also checked that internal card IDs were not present in the changed docs/runtime scope:

rg -n "FIR-112|FIR_112|FIR 112|FIR-72" docs-site examples crates/firma-run/src

Result:

no matches

make check was not run locally for this branch; verification was scoped to the changed crate and docs build to avoid unnecessary full-workspace runtime.

Security Considerations

  • This does not touch the Sidecar hot path (normalizer → Stage 1 → Stage 2).
  • No network calls were added on the policy enforcement hot path.
  • Fail-closed behavior is preserved for VZ guest setup:
    • missing guest mode artifacts fail before launch
    • relative artifact paths are rejected
    • non-file artifacts are rejected
    • non-executable runner paths are rejected on Unix
    • missing proxy/DNS contract values fail before spawning the runner
  • This adds a new trust boundary between firma run and the operator-provided VZ runner.
    • firma run owns validation, contract generation, proof metadata, and process supervision.
    • The VZ runner owns Virtualization.framework lifecycle and in-guest route/DNS enforcement.
  • Capability token issuance, revocation, and scope matching are not changed.
  • Cedar policy evaluation is not changed.
  • Audit signing is not changed.
  • Claim boundaries are documented: VZ guest mode is experimental until runner/guest enforcement and hardware E2E evidence prove the required invariants.

Risks

  • The VZ guest contract is a new interface. Future runner implementation must preserve the JSON contract semantics or introduce versioned migration.
  • macos_vz_guest reports structural mode when guest mode is selected, but the actual confinement guarantee still depends on the configured runner and guest image enforcing the contract correctly.
  • macOS hardware E2E coverage is still required before this path should become the default macOS structural claim.
  • Guest lifecycle work remains: stdio, TTY, signals, exit status, terminal resize, guest image lifecycle, and route proof.
  • ESF remains a separate hardening/audit track; it should not be used to claim network structural parity by itself.

Model Used

  • OpenAI GPT-5, Codex coding agent + Claude Code
  • Tool/code-execution mode enabled for local repository inspection, patching, formatting, test execution, and docs build verification

Checklist

  • Thinking path traces from project context down to this specific change
  • make check passes locally (fmt + toml-fmt + lint + test + build + audit + deny)
  • No .unwrap(), .expect(), panic!(), or unsafe introduced outside of test code
  • All error paths are fail-closed — errors produce DENY, not silent ALLOW
  • No network calls added on the hot path (normalizer → Stage 1 → Stage 2)
  • Tests added or updated for the changed behaviour
  • If this touches the normalizer or action-class registry, mapping table tests are updated
  • If this changes wire format, config schema, or CLI behaviour, docs are updated under docs/ and docs-site/
  • Security considerations are documented above
  • Model used is specified with version and capability details
  • I will address all reviewer comments before requesting merge

@Emulator000 Emulator000 self-assigned this Jun 2, 2026
}

// Sensitive home path masking for claude-code profile.
if claude_profile && is_absolute_sandbox_path(&home) {

@luca-iachini luca-iachini Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these gates necessary?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, both gates are intentional.

claude_profile keeps this masking scoped to the built-in claude-code profile, these deny rules block common home-secret paths like .ssh, .aws, .kube, .gnupg, and .config/gcloud, applying them to every macOS profile would be a behavior change for generic and any future profiles that may intentionally need different filesystem access, is_absolute_sandbox_path(&home) avoids generating malformed or overly broad SBPL rules when HOME is missing or relative, the generated rules are path-based, e.g. (deny file-read* (subpath "...")), so we only emit them when the home path is a macOS/SBPL absolute path.

The helper uses sandbox path semantics (/Users/...) rather than Path::is_absolute() because these profiles are tested on Windows CI too, where /Users/tester is not considered absolute by the host OS even though it is the correct path shape for the macOS sandbox profile.

Comment thread crates/firma-run/src/backend/linux_bwrap.rs Outdated
@Emulator000

Copy link
Copy Markdown
Collaborator Author

@therandomsecurityguy @TommasoAlderigi some notes before merging this one.

The proposed direction is:

  • Primary path: VZ guest-backed structural parity.
  • Secondary path: ESF as optional host hardening/audit, not as the primary network confinement mechanism.
  • Default macOS behavior: still proxy-only compatibility mode unless an experimental structural mode is explicitly enabled.
  • Claim boundary: we should not claim macOS/Linux structural parity until macOS hardware E2E evidence proves the required invariants.

The key docs are:

What is implemented in this PR

Runtime-side:

  • Adds NetworkConfinement proof metadata so the runtime can distinguish:
    • linux_network_namespace
    • macos_sandbox_network_deny
    • macos_vz_guest
    • kvm_micro_vm
    • proxy_only
  • Keeps macOS vz proxy-only by default.
  • Keeps the experimental sandbox-exec network-deny mode with explicit caveats.
  • Adds an experimental VZ guest mode gate via FIRMA_RUN_VZ_GUEST=1.
  • Adds fail-closed validation for configured VZ runner and guest image artifacts.
  • Writes a versioned vz-guest-launch.json contract containing command, env, mounts, proxy/DNS fields, attribution headers, and required invariants.
  • Starts the configured runner with --launch-contract <path>.
  • Wires the host DNS refusal stub only for macOS vz structural modes.
  • Keeps Linux bwrap on its existing namespace/DNS/proxy path.

Docs/testing:

  • Adds/updates the macOS strategy memo, capability matrix, ESF caveats, risk/complexity scoring, and milestone plan.
  • Updates sandbox/runtime docs and retrieval notes.
  • Adds/updates focused tests for proof metadata, macOS structural DNS stub routing, VZ guest contract shape, and serialization.
  • Avoids internal card IDs in public docs.

What this PR does not implement

This PR does not implement the actual Apple Virtualization.framework runner.

The VZ guest contract path is now present in firma-run, but the configured runner still needs to provide:

  • guest boot lifecycle
  • guest image management
  • virtio networking
  • guest-local proxy/DNS wiring
  • bridge-only route/firewall enforcement
  • stdio/TTY/signal/exit-code preservation
  • machine-readable route/DNS proof
  • macOS hardware E2E validation

This PR also does not implement ESF. The current recommendation is that ESF remains a separate hardening/audit track unless we explicitly decide otherwise.

Decisions I’d like reviewers to confirm

Before merging, I’d like feedback on these points:

  1. Strategy: Are we aligned that VZ guest-backed structural parity is the primary macOS path?

  2. ESF boundary: Are we aligned that ESF should be documented as host hardening/audit, not as standalone sidecar-only egress or DNS confinement?

  3. Claim language: Are the docs clear enough that default macOS vz remains proxy-only, and experimental macOS structural modes should not be marketed as full parity until E2E evidence exists?

  4. Runtime contract: Is the VZ guest launch contract the right split of responsibility between firma-run and the external runner?

  5. Proof naming: Are we happy with NetworkConfinement / network_confinement as the proof metadata name?

  6. Merge readiness: Is this PR acceptable as the strategy + Rust-side contract foundation, with the actual VZ runner implementation following in a separate PR/card?

Known follow-up work

If this PR is accepted, the next implementation work should be split roughly as:

  • Implement the signed/packageable macOS VZ runner.
  • Define guest image build/update lifecycle.
  • Implement in-guest bridge-only egress and deterministic DNS.
  • Preserve CLI/TUI UX: stdio, signals, terminal resize, exit codes.
  • Add macOS hardware E2E tests for:
    • mediated HTTP
    • policy deny
    • proxy-env-unset direct request
    • child process network call
    • raw TCP/UDP bypass attempt
    • direct DNS bypass attempt
    • startup sidecar down
    • mid-session sidecar loss
  • Decide whether ESF becomes an enterprise add-on for process lineage, config/socket protection, and audit correlation.

Please review especially the strategy/claim boundaries and whether anything is missing before we merge this as the macOS confinement decision + VZ contract foundation.

Follow-up PRs should be created after merging.

@Emulator000 Emulator000 marked this pull request as ready for review June 5, 2026 18:25
@Emulator000 Emulator000 force-pushed the dario/fir-112-macos-structural-confinement-strategy-decision-parity-path branch from 6e1ab88 to 9b17ff3 Compare June 5, 2026 18:46
@veeso veeso force-pushed the dario/fir-112-macos-structural-confinement-strategy-decision-parity-path branch from 9b17ff3 to 726141a Compare June 8, 2026 13:45
falcucci added 3 commits June 9, 2026 08:27
macOS sandbox-exec was failing because we were handing it a bad sandbox profile.

the structural experiment for the VZ backend uses TrustedBSD sandbox rules:
deny network-outbound, then allow loopback so the agent can still reach the
host proxy bridge and DNS refusal stub.

Apple documents sandbox failures in this area as network-outbound violations
in the App Sandbox diagnostics page

https://developer.apple.com/documentation/security/discovering-and-diagnosing-app-sandbox-violations

tested with:

    cargo test -p firma-run macos_vz
falcucci added a commit that referenced this pull request Jun 10, 2026
cherry-picking the VZ guest work from #141

the split is useful to isolated the guest work development
to match Apples Virtualization.framework boundaries

https://developer.apple.com/documentation/virtualization

here we specifically use FIRMA_RUN_VZ_GUEST to select the macOS vz mode.

before launch, the backend validates the configured runner and guest artifacts,
rejects missing or relative paths, checks that the runner is executable on Unix
and only then emits a macos_vz_guest proof. 

the contract carries the execution envelope the runner must enforce:

- sandbox id
- runtime dir
- runner path
- guest image paths
- command
- args
- cwd
- environment
- mounts
- identity mode
- seccomp artifact path
- proxy URL
- DNS stub address
- attribution headers
- required invariants:
  - sidecar-only egress
  - confined DNS
  - fail-closed startup/runtime
  - direct-bypass resistance
  - stdio/signal/exit preservation

**since there is still no Apple Virtualization.framework runner here**,
it servers us as a bedside contract for the future runner.

routing also learns that macOS structural modes may need a host DNS refusal
stub even when they are not using the Linux namespace path. the sandbox-exec
uses that stub on loopback. the VZ guest contract exposes the same endpoint
so the future runner can make guest DNS deterministic instead of letting the
agent fall back to ambient resolution.

*Note that the ESF remains out of this change.*

Tested with:

    cargo test -p firma-run macos_vz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants