Skip to content
Open
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
66 changes: 66 additions & 0 deletions docs/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,72 @@ The MXC ↔ OS contract therefore reports: which feature failed, whether it was
version mismatch or a runtime/capability unavailability (e.g. Hyper-V off), and
what the user should do (upgrade OS, enable feature, change the config).

## Trust model and the outer clamp

MXC's authorization model and the (optional) unbypassable upper bound on what a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The section's opening line advertises "the (optional) unbypassable upper bound on what a config can relax," but the body correctly establishes that no in-repo unbypassable clamp ships: every "Yes" row is OS infrastructure out of scope, and the only in-repo row is "No β€” bypassable by a local admin / Deferred." The lead sentence promises something the section then retracts. Suggest tightening to e.g. "…and the platform-dependent external bounds that can constrain it."

config can relax.

### Authorization model: one principal, secure-and-loud defaults

For the agent/SDK consumers MXC is built for, the same principal authors **both**
the config JSON and the command line. There is no second, more-privileged channel
to grant capabilities from, so MXC does **not** require a mandatory second-channel
authorization for boundary-relaxing fields.

> **Precondition.** This holds only where the invoker and the config author are
> the same trust principal. MXC does not enforce that. An embedder that runs
> `wxc-exec` with a config derived from lower-trust input (an end user, a remote
> service, model output) has two principals, and the model above does not apply
> to it β€” such an embedder must validate or constrain the config itself before
> passing it in. See [the outer clamp](#the-outer-clamp-optional-platform-asymmetric)
> for the mechanism that would enforce a ceiling independently of the config.

Given that precondition, safety rests on three in-repo mechanisms, all
implemented:

1. **Secure defaults.** Every boundary defaults closed: `network.defaultPolicy`
is `block`, `ui.disable` is `true`, clipboard/injection off, etc. (see
[the schema reference](schema.md) for each field's default). A minimal config
is a tight sandbox.
2. **Explicit + loud relaxation.** Fields that open a network, UI, capability,
or Seatbelt boundary beyond their secure default are logged at parse time as
`SECURITY: boundary relaxed: …`, so those relaxations are never silent β€” they

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 states relaxations "are never silent," but log_boundary_relaxations deliberately skips blockedHosts. Under the secure defaultPolicy=block, a non-empty blockedHosts flips networking OFF->ON (allow-all-except) on the Hyperlight and NanVix. Shall we update this to note per-host blockedHosts filtering on the micro-VM backends is a known audit gap? File bugs to fix the backends so defaultPolicy=block stays authoritative?

are auditable in the diagnostic log. Filesystem path grants and the Seatbelt
pty baseline are deliberately out of this audit's scope, being the request's
primary purpose rather than a relaxation.
3. **Catastrophic capabilities are removed from shipped builds.** A capability
that would defeat the sandbox wholesale rather than merely widen it β€”
currently `seatbelt.profileOverride`, which replaces the entire generated
deny-default profile β€” is **rejected by release/shipped binaries** (the parser
returns a config error, and the builder branch is compiled out), so it cannot
be honored in production at all.

### The outer clamp (optional, platform-asymmetric)

The mechanisms above bound what the *config* requests. An **outer clamp** is a
separate, optional upper bound enforced *below* the config β€” an unbypassable
ceiling a host operator can impose so that even a fully-relaxed config cannot
exceed it. Whether such a clamp can be made truly unbypassable is
**platform-asymmetric**, because it depends on an OS primitive MXC does not own:

| Platform | Clamp mechanism | Unbypassable? | Status |
|---|---|---|---|
| Windows β€” BaseContainer tier | OS sandbox broker enforces the policy inside `Experimental_CreateProcessInSandbox` | Yes β€” the broker is the kernel-side authority | OS-infra (outside this repo) |
| Windows β€” AppContainer fallback tiers (BFS / DACL) | AppContainer + `bfscfg.exe` BFS or host-side DACL ACEs; used when the BaseContainer API is absent | Partial β€” enforcement is partly host-side, not a single kernel broker | Existing backend behavior, but **not present in a stock build**: BFS requires the non-default `tier2_bfs` feature plus `bfscfg.exe` on the host, and the DACL path requires explicit config opt-in |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The AppContainer-fallback row says the DACL path "requires explicit config opt-in," but it is enabled by default: FallbackPolicy::allow_dacl_mutation defaults to true (models.rs:531) and is only changed when explicitly set (config_parser.rs:1114-1117), and Tier-3 DACL is reached automatically when BaseContainer/BFS are unavailable. So DACL is the default stock fallback and is opt-out, not opt-in - unlike BFS, which really does require the non-default tier2_bfs feature. Suggest: "BFS is absent from a stock build; the DACL path is the default stock fallback (enabled unless fallback.allowDaclMutation:false)."

| Linux (LXC) | Host LSM (AppArmor / SELinux) profile + root-owned policy file | Yes, with a host LSM; otherwise advisory | OS-infra / host config |
| Linux (Bubblewrap) | LSM, or compile-time capability removal | Partial β€” bwrap is unprivileged by design | OS-infra / build |
| macOS (Seatbelt) | Root-owned policy file + codesign / SIP | Yes, with SIP + signed binary; otherwise advisory | OS-infra (outside this repo) |
| All platforms | Root/admin-owned **clamp-policy file** read at the trust boundary, capping the boundary relaxations that the parse-time audit logs | No β€” a trust-boundary gate, bypassable by a local admin, not a kernel guarantee | Deferred (in-repo candidate) |

The truly unbypassable rows (broker, LSM, SIP) live in OS infrastructure outside
this repository and are intentionally **out of scope here**. The one piece that
*could* live in-repo β€” a root-owned clamp-policy file the parser enforces as a
ceiling on relaxations β€” is a meaningful additional gate but is **not a kernel
guarantee** (a local admin can edit the file), so it is tracked as a follow-up
rather than shipped as if it were unbypassable. Catastrophic, un-clampable
capabilities are handled instead by compile-time removal (above), which needs no
OS primitive.

## Experimental Features β€” Clarifications

**Shipping model:** The shipped schema contains **only** non-experimental
Expand Down