Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ The workspace is organized into six top-level directories under `src/`:
- `wxc_common` is the **cross-platform foundation**: config parsing, models, errors, logger, `ScriptRunner` / `StatefulSandboxBackend` traits, state-aware dispatch helpers, validators, ids, ui-policy, encoding. Plus a few thin Windows API helpers shared by host tools and backends (`process_util`, `string_util`, `filesystem_dacl`, `diagnostic`). It must not depend on any `backends/*` crate.
- Each Windows containment backend lives in its own `backends/*/common` crate (e.g. `appcontainer_common`, `windows_sandbox_common`, `isolation_session_common`, `hyperlight_common`, `nanvix_runner`). Backend crates depend on `wxc_common`; there are no cross-edges between backend crates. Windows Sandbox additionally has `windows_sandbox_lifecycle`, which owns the one-shot and state-aware runners and depends on `windows_sandbox_common` for the wire protocol, plus separate daemon and guest binaries.
- `learning_mode_core` is the cross-platform learning-mode denial model and output layer. It owns denial types, summaries, analyzer abstractions, plain-JSON document emission, and the serializable output-pointer type, and must not depend on any `backends/*` crate.
- `learning_mode_windows` (`backends/learning_mode/windows`) is a Windows-only backend support crate for the AppInfo-brokered Learning Mode APIs in `processmodel.dll`. It runtime-resolves the Learning Mode trace and process security-environment exports, owns their typed handle/lifecycle wrappers, decodes sealed ETL traces through `learning_mode_core`, and depends on `wxc_common` plus `learning_mode_core`; runner integration consumes it from the AppContainer backend layer.
- `learning_mode_windows` (`backends/learning_mode/windows`) is a Windows-only backend support crate for the AppInfo-brokered Learning Mode APIs in `processmodel.dll`. It runtime-resolves the Learning Mode trace and process security-environment exports, owns their typed handle/lifecycle wrappers, decodes sealed ETL traces through `learning_mode_core`, and depends on `wxc_common` plus `learning_mode_core`; runner integration consumes it from the AppContainer backend layer. The trace contract is `HRESULT Start` + retryable `HRESULT Stop` + infallible `Close`: `Stop` never consumes the trace handle, and every started trace must be closed exactly once (closing without stopping is the early-exit discard path). The process security-environment contract is `HRESULT Create` + infallible by-value `Close` and consumes a PSEC 1.0 FlatBuffer, not the legacy SBOX buffer; generated PSEC bindings live in `core/generated/process_security_environment_specification`.
- `wxc`, `lxc`, and `mxc_darwin` are thin binary crates (`wxc-exec` / `lxc-exec` / `mxc-exec-mac`) that wire up CLI args (`clap`), load/validate config, handle maintenance modes (`--probe`, `--delete`, `--setup-*`, `--audit`), and **delegate all backend dispatch to `mxc_engine`**. They contain no `match request.containment` of their own. `wxc-exec` additionally owns the Windows Ctrl-C / DACL-cleanup / `--audit` PLM-trace / telemetry orchestration around the engine call.
- `mxc_engine` is the **single execution engine** β€” the one home for "given an `ExecutionRequest`, run it". It owns: run-to-completion backend selection (`run` / `resolve_runner`, covering **all** backends, incl. the Windows ProcessContainer BaseContainer/AppContainer BFS/DACL fallback tiers via `appcontainer_common::dispatcher::dispatch_with_fallback`, and every experimental backend, feature-gated); streaming (`spawn` β†’ `Box<dyn SandboxProcess>`); state-aware lifecycle dispatch (`run_state_aware`, including Windows Sandbox and IsolationSession); host probing (`platform_support` / `PlatformSupport`); and config building (`build_request`, `SandboxPolicy` + sections, `available_tools_policy`/`user_profile_policy`/`temporary_files_policy`). It depends on the backend crates (cfg-split: appcontainer/windows_sandbox lifecycle/isolation_session/wslc/nanvix on Windows, bubblewrap/lxc/nanvix on Linux, seatbelt on macOS) so it can't live in `wxc_common`. Both the executor binaries and `mxc-sdk` call into it. `ResolvedRunner` carries the boxed runner plus (Windows only) the optional `DaclManager` guard, so `wxc-exec` can park the guard for its signal handler.
- `mxc-sdk` is the **public Rust SDK** β€” a thin facade over `mxc_engine`. Build a `SandboxRequest` with `build_request`, then either `run(request)` (run-to-completion; returns an `Output` with the `WaitOutcome`, captured `stdout`/`stderr`, warnings, and optional structured output metadata) or `spawn_sandbox(request)` (returns a `Sandbox` handle for live bidirectional stdio β€” `take_stdin`/`take_stdout`/`take_stderr`, `kill()`, `wait()` returning a `WaitOutcome` (`Exited(i32)` / `TimedOut`) as `io::Result`, `output_metadata()` after terminal completion, or `wait_with_output()`). It re-exports the engine's config-building surface (`build_request`, `mxc_sdk::policy::{SandboxPolicy sections}`, discovery helpers) and `platform_support`; `mod sandbox` (wrapping the engine's `SandboxProcess` in `Sandbox`) is its only local module. No pty is ever allocated. Streaming supports Seatbelt (macOS), Bubblewrap (Linux), and Windows ProcessContainer (AppContainer + BaseContainer); other backends return `ErrorCode::UnsupportedContainment`.
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/Versioning.Checks.Job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
- name: Check schema is in sync with the Rust wire model (codegen)
run: node scripts/versioning/check-schema-codegen.js

- name: Check PSEC generated contract (provenance + drift)
run: node scripts/versioning/check-psec-codegen.js

- name: Check SDK wire types are in sync with the Rust wire model (codegen)
run: node scripts/versioning/check-sdk-types-codegen.js

Expand Down
30 changes: 27 additions & 3 deletions docs/learning-mode/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,33 @@ surfacing the resulting denials to the caller. Its `mode` selects how each
ungranted access is handled while it is recorded:

> **Host requirement.** `captureDenials` requires a feature-enabled Windows
> build exposing the BaseContainer security-environment and Learning Mode APIs.
> It is not supported by the AppContainer fallback tiers; unsupported hosts
> return `backend_unavailable`.
> build exposing the complete official V2 API set:
> `StartLearningModeTrace`, `StopLearningModeTrace`,
> `CloseLearningModeTrace`, `CreateProcessSecurityEnvironment`,
> `QueryProcessSecurityEnvironmentSupport`, and
> `CloseProcessSecurityEnvironment`. It is not supported by the AppContainer
> fallback tiers; unsupported hosts return `backend_unavailable`.
>
> Internal validation confirmed that build `26657.1002` exposes only the
> incompatible earlier contract and is rejected, while build `26663.1000`
> exposes the complete V2 contract. These are validation points, not a public
> Windows release-floor commitment; callers should rely on the runtime probe.
>
Comment thread
richiemsft marked this conversation as resolved.
> `captureDenials` cannot be combined with `processContainer.leastPrivilege`;
> the Windows process security-environment API used for capture does not expose
> an LPAC token option, so MXC rejects that combination rather than silently
> weakening the requested policy.
>
> `captureDenials` also cannot currently be combined with `network.proxy`.
> The V2 process security-environment proxy contract requires a separate proxy
> AppContainer peer identity; MXC rejects the combination until that peer is
> provisioned by the capture launch path.
>
> `filesystem.deniedPaths` requires
> `QueryProcessSecurityEnvironmentSupport` to advertise
> `PSE_SUPPORT_FS_DENY`. When the bit is absent, capture fails as
> `backend_unavailable`; it cannot fall back to AppContainer or host-DACL
> enforcement.

- `mode: "block"` (default) maps onto `learningModeLogging`
(deny-and-record) β€” the app / user-configurable flow.
Expand Down
24 changes: 24 additions & 0 deletions docs/process-container/os-version-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ available bounds what policy can be enforced.
- **T3 (AppContainer + DACL)** is the universal fallback and enforces
filesystem policy via host path ACEs on every release.

## Learning Mode denial capture

`processContainer.captureDenials` uses a separate V2 process
security-environment path rather than the T1/T2/T3 fallback chain. The host
must expose the complete official V2 export set:

- `StartLearningModeTrace`
- `StopLearningModeTrace`
- `CloseLearningModeTrace`
- `CreateProcessSecurityEnvironment`
- `QueryProcessSecurityEnvironmentSupport`
- `CloseProcessSecurityEnvironment`

Unsupported or earlier-contract hosts fail as `backend_unavailable`; capture
never falls back to AppContainer or host-DACL enforcement. Internal validation
confirmed the earlier contract on build `26657.1002` is rejected and the full
V2 contract on build `26663.1000` is accepted. These builds are validation
points, not a public release-floor commitment; runtime export probing is the
source of truth.

Capture is incompatible with `processContainer.leastPrivilege` and
`network.proxy`. `filesystem.deniedPaths` is accepted only when
`QueryProcessSecurityEnvironmentSupport` advertises `PSE_SUPPORT_FS_DENY`.

## Filesystem policy

| Aspect | 23H2 | 24H2 | 25H2 | 25H2+ |
Expand Down
2 changes: 2 additions & 0 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ production configs and the dev schema when working on experimental features:
} // dir must already exist; a unique per-run id is stamped
// into the stem (denials.<run-id>.json) and the actual
// path printed on stderr. Omit outputPath for a managed temp file.
// captureDenials cannot be combined with leastPrivilege.
// captureDenials cannot currently be combined with network.proxy.
},

"lxc": { // LXC-specific
Expand Down
62 changes: 62 additions & 0 deletions external/windows-sdk/ProcessSecurityEnvironment.fbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
Comment thread
richiemsft marked this conversation as resolved.
Outdated

namespace ProcessSecurityEnvironmentLayout;

struct SchemaVersion {
major:uint16;
minor:uint16;
}

table ProcessSecurityEnvironment {
version:SchemaVersion (required);
capabilities:string;
disallow_win32k_system_calls:bool = false;
ui_restrictions:uint64 = 0;
fs_read_write:[string];
fs_read_only:[string];
fs_deny:[string];
network_policy:NetworkPolicy;
}

table ProxyInfo {
url:string;
}

enum FilterAction : byte { deny, allow }
enum IpProtocol : byte { any, tcp, udp, icmpv4, icmpv6 }

table IpSubnet {
address:string;
prefix_length:ubyte = 0;
}

table DestinationRule {
subnet:IpSubnet;
except:[IpSubnet];
}

table PortRule {
protocol:IpProtocol = any;
port:uint16 = 0;
end_port:uint16 = 0;
}

table EndpointRule {
destinations:[DestinationRule];
ports:[PortRule];
}

table EndpointPolicy {
default_action:FilterAction = deny;
allow:[EndpointRule];
deny:[EndpointRule];
}

table NetworkPolicy {
proxy:ProxyInfo;
egress:EndpointPolicy;
allowed_appcontainer_peer:string;
}

root_type ProcessSecurityEnvironment;
file_identifier "PSEC";
52 changes: 52 additions & 0 deletions external/windows-sdk/ProcessSecurityEnvironment.provenance.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Provenance for the vendored ProcessSecurityEnvironment FlatBuffers schema.
#
# This file is the single source of truth for the pinned regeneration toolchain
# and the authoritative hash of the vendored `ProcessSecurityEnvironment.fbs`.
# It is consumed by:
# * src/core/generated/process_security_environment_specification/regenerate.ps1
# (validates the schema hash + pins the exact flatc version before generating)
# * scripts/versioning/check-psec-codegen.js
# (CI drift gate β€” verifies the committed schema + generated crate)
#
# Update this file whenever the vendored schema is refreshed from the OS source,
# then regenerate the bindings (see the crate README).

[source]
# The schema originates from the internal Microsoft Windows OS repository and is
# not publicly redistributable. Only the schema text (not the OS tree) is vendored.
repository = "Windows OS (internal Azure DevOps)"
# The PSEC schema is a Windows OS containment contract; there is no public OS
# revision to cite. Instead of guessing a source path/revision, we record the
# Windows build the vendored schema contract was validated against.
validated_windows_build = "10.0.26663.1000"
# Azure DevOps PR 16307987 is the RELATED Learning Mode trace ABI change β€” it is
# NOT the pull request that introduced or last modified this PSEC schema.
# Recorded for traceability only; do not represent it as the schema's origin.
related_trace_abi_pull_request = 16307987

[schema]
# SHA-256 of external/windows-sdk/ProcessSecurityEnvironment.fbs, computed over
# the LF-normalized (git-blob) content so it is checkout-independent regardless
# of autocrlf. Verified by regenerate.ps1 and the CI drift gate.
sha256 = "2bb6b5bb5eccf589ffa1d19ad1f011dcc72b3e7eacefa1f0741e123cb259bf29"

[tool]
# Exact flatc version used to generate the committed bindings. Regeneration
# pins this exact version (not a floor) so output is byte-reproducible.
# 25.12.19 is the first release carrying flatbuffers PR #8709, which stops flatc
# emitting elided lifetimes that trip the `mismatched_lifetime_syntaxes` lint
# (added in Rust 1.89).
flatc_version = "25.12.19"
flatc_release = "https://github.com/google/flatbuffers/releases/tag/v25.12.19"
generated_date = "2026-08-03"

# Exact GitHub release assets for flatc 25.12.19. The CI drift gate downloads the
# platform asset, verifies its SHA-256 against these values, unzips it, and
# regenerates into a temp directory to diff against the committed bindings.
[tool.flatc_assets.linux]
name = "Linux.flatc.binary.clang++-18.zip"
sha256 = "50c1915deeeb714f2a05c8ec795bd1af898d251a62e2774067703b29188efc90"

[tool.flatc_assets.windows]
name = "Windows.flatc.binary.zip"
sha256 = "fff9445c9db907227bc64b54cc98743084c4949282aa4e576cff6a955724ddc8"
4 changes: 2 additions & 2 deletions schemas/dev/mxc-config.schema.0.8.0-dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"CaptureDenials": {
"additionalProperties": false,
"description": "Windows denial-capture settings. The presence of the `captureDenials` object enables capture; all fields are optional.",
"description": "Windows denial-capture settings. The presence of the `captureDenials` object enables capture; all fields are optional. Capture is incompatible with `processContainer.leastPrivilege` and `network.proxy`. Explicit `filesystem.deniedPaths` requires the host's V2 process security-environment support query to advertise native deny enforcement.",
"properties": {
"mode": {
"anyOf": [
Expand Down Expand Up @@ -699,7 +699,7 @@
"type": "null"
}
],
"description": "Windows denial capture. When present, the runner records the sandboxed process's access attempts to a learning-mode ETL trace for later inspection. Requires a host that exposes the learning-mode OS API."
"description": "Windows denial capture. When present, the runner records the sandboxed process's access attempts to a learning-mode ETL trace for later inspection. Requires a host that exposes the complete official V2 Learning Mode and process security-environment API set. Cannot be combined with `leastPrivilege` or `network.proxy`; `filesystem.deniedPaths` additionally requires the V2 deny-support capability."
},
"learningMode": {
"description": "AppContainer learning mode (deny-and-record): failed access checks are logged for diagnostics while the accesses stay denied; containment is unchanged. Distinct from the allow-all `permissiveLearningMode` capability, which is injected internally by the `--audit` CLI flag or dedicated denial-capture configuration.",
Expand Down
Loading
Loading