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
18 changes: 17 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ npm test
npm run test:integration

# C# SDK (from sdk/dotnet/)
dotnet test Microsoft.Mxc.Sdk.slnx # requires mxc_ffi built (cargo build -p mxc_ffi); resolver finds it in src/target/{debug,release}
dotnet test Microsoft.Mxc.Sdk.slnx # Debug only; requires mxc_ffi built (cargo build -p mxc_ffi); resolver finds it in src/target/{debug,release}
# the telemetry tests need the debug-only MXC_TEST_LOCALAPPDATA_OVERRIDE, so `-c Release` fails by design

# Local PowerShell helpers β€” run from repo root, require built binaries
tests\scripts\run_test_configs.ps1 # All test configs via wxc_test_driver
Expand All @@ -92,6 +93,7 @@ tests\scripts\run_windows_sandbox_one_shot_tests.ps1 # Windows Sandbox one
tests\scripts\run_windows_sandbox_state_aware_tests.ps1 # Windows Sandbox state-aware lifecycle E2E (provision/start/exec*/stop/deprovision; requires the Windows Sandbox optional feature; skips if absent)
tests\scripts\run_lxc_all_tests.sh # All LXC tests (Linux)
tests\scripts\run_bwrap_all_tests.sh # All Bubblewrap tests (Linux, requires bwrap)
tests\scripts\run_telemetry_consent_smoke_test.ps1 # Telemetry consent + policy CLI E2E (Windows; debug binary only)

# E2E test crate β€” Rust executor integration tests (from src/)
cargo test -p wxc_e2e_tests # Invokes MXC binaries directly
Expand Down Expand Up @@ -153,6 +155,7 @@ Core references:
- `docs/diagnostics.md` β€” diagnostic logging knobs (env vars, log file format)
- `docs/host-prep.md` β€” `wxc-host-prep.exe` host setup binary (`prepare-system-drive` / `unprepare-system-drive` for the AppContainer ACEs on the system-drive root, plus `prepare-null-device` / `verify-null-device` / `dump-null-device` for the `\Device\Null` security descriptor that AppContainer-based backends require). Owns elevation via embedded `requireAdministrator` manifest β€” `wxc-exec.exe` no longer self-elevates.
- `docs/sandbox-policy/v1/policy.md` β€” sandbox policy v1 specification
- `docs/telemetry/telemetry.md` β€” telemetry overview; `docs/telemetry/telemetry-consent-design.md` (Windows-only consent design and per-SDK surface) and `docs/telemetry/telemetry-policy.md` (the MDM / Group Policy ceiling)

Per-backend guides:

Expand Down Expand Up @@ -230,6 +233,18 @@ The parser deserializes JSON directly into the typed wire model (`wxc_common::wi
- macOS: `mxc-exec-mac` (Seatbelt)
- Target triples: `x86_64-pc-windows-msvc`, `aarch64-pc-windows-msvc`, `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`, `aarch64-apple-darwin`

### Telemetry consent

Telemetry is **Windows-only** and is never collected without explicit user consent. Three independent conditions must all hold before anything is emitted: the user has granted consent, the administrative (MDM / Group Policy) ceiling permits it, and the config kill-switch has not disabled it. `wxc_common::telemetry::is_enabled()` is the single place those three terms are combined β€” do not re-derive enablement anywhere else.

- **Everything fails closed.** Any error, unreadable value, corrupt file, missing native library, or ambiguity must resolve to "no telemetry" (`Undetermined` consent / `Blocked` policy), never to a permissive state.
- **Policy may restrict, but may never substitute for, consent.** The administrative policy is a deny-only ceiling: it can subtract from what a user permitted, never add to it. An administrator cannot opt a user in β€” a denied or never-asked user stays opted out even under `AllowTelemetry=3`. Keep the terms combined with `&&`; never add a policy value or config path that grants collection on its own.
- **MXC owns its own consent state.** It must never read or infer from the Windows system telemetry consent. The consent store is a per-user JSON file; the policy is `HKLM\SOFTWARE\Policies\Mxc` β†’ `AllowTelemetry` (`REG_DWORD`).
- **One definition, distributed to the bindings.** The Rust `ConsentState` / `PolicyState` enums are the source of truth; the FFI, C#, and TypeScript layers marshal the same strings. `scripts/check-telemetry-policy-parity.js` fails if the four `PolicyState` spellings drift apart and runs in the versioning checks workflow.
- **Test isolation.** The consent store and the policy key are process-global, each behind its own mutex. Use `wxc_common::telemetry::test_support::TelemetryTestEnv` whenever a test needs both; constructing `PolicyKeyGuard` and `LocalAppDataGuard` directly in the same test risks a lock-order deadlock. Both overrides are `cfg(debug_assertions)`-gated, so the smoke test refuses to run against a release binary. The `wxc_common` `test-support` feature re-exports the policy override for downstream crates' integration tests (`mxc_ffi` uses it) and must stay a dev-dependency-only feature.
- **Read-only queries must never be able to crash the host.** `NeedsConsentPrompt`/`needsTelemetryConsentPrompt` and `GetPolicy`/`getTelemetryPolicy` fail closed on *any* failure and never throw β€” including a non-`Success` FFI status, which covers a caught panic. The consent *read* and *write* still throw, because their callers must distinguish "not decided" from "could not read" and "did not persist"; when they do, they raise only the binding's documented exception type (`MxcException`), wrapping anything unexpected rather than letting a raw type escape.
- **Never swallow a failure silently.** Fail-closed return values are indistinguishable from legitimate ones, so a broken install would otherwise be invisible. Every swallowed failure is reported once per distinct failure per process (deduplicated β€” hosts poll these getters), and the reporter itself must never throw. At the FFI boundary, `catch_unwind` sites log the panic payload before returning `MXC_STATUS_PANIC`, which would otherwise be discarded.

### Package versioning

All Rust crates use `version.workspace = true` to inherit the version from `src/Cargo.toml` `[workspace.package]`. The npm SDK version in `sdk/node/package.json` and the C# SDK version (`<Version>` in `sdk/dotnet/Microsoft.Mxc.Sdk/Microsoft.Mxc.Sdk.csproj`) must match. Run `node scripts/check-version-sync.js` to validate they are in sync. When bumping the version, update `src/Cargo.toml` (workspace version), `sdk/node/package.json`, and the C# csproj in the same commit.
Expand All @@ -243,6 +258,7 @@ When changing behavior covered by existing documentation, update the relevant do
- **SDK API changes** (new exports, changed signatures, new options) β†’ update `sdk/node/README.md` and the JSDoc in `sdk/node/src/index.ts` (TypeScript SDK); the Rust `mxc-sdk` crate docs/`README.md`; and `sdk/dotnet/README.md` (C# SDK). If the `mxc_ffi` C ABI surface changes, the C# P/Invoke regenerates on the next C# build; keep the `ErrorCode` parity + bindings-codegen gates green.
- **New containment backends or major backend changes** β†’ update the relevant doc in `docs/` (e.g., `lxc-support/lxc-backend.md`, `windows-sandbox/windows-sandbox.md`)
- **Versioning or promotion changes** β†’ update `docs/versioning.md`
- **Telemetry consent or policy changes** β†’ update `docs/telemetry/telemetry-consent-design.md` and/or `docs/telemetry/telemetry-policy.md`, and keep `scripts/check-telemetry-policy-parity.js` green across all three bindings

### Policy versioning

Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/Build.Windows.Job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,32 @@ jobs:
--no-default-features
--features "${{ matrix.features }}"

- name: Test telemetry consent and policy in release mode
run: |
cargo test --locked --release --target ${{ matrix.target }} -p wxc_common telemetry::consent
cargo test --locked --release --target ${{ matrix.target }} -p wxc_common telemetry::policy

- name: Test
run: cargo test --locked --release --target ${{ matrix.target }}
--no-default-features
--features "${{ matrix.features }}"

- name: Build debug executor for isolated telemetry smoke tests
if: matrix.arch == 'x64'
run: cargo build --locked -p wxc

- name: Run isolated telemetry consent smoke test
if: matrix.arch == 'x64'
working-directory: ${{ github.workspace }}
shell: pwsh
run: tests\scripts\run_telemetry_consent_smoke_test.ps1 -BinDir src\target\debug

- name: Run isolated telemetry ETW smoke test
if: matrix.arch == 'x64'
working-directory: ${{ github.workspace }}
shell: pwsh
run: tests\scripts\run_telemetry_etw_smoke_test.ps1 -BinDir src\target\debug

- name: Upload binaries
uses: actions/upload-artifact@v4
with:
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/Versioning.Checks.Job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,17 @@ jobs:
- name: Check SDK wire types are in sync with the Rust wire model (codegen)
run: node scripts/versioning/check-sdk-types-codegen.js

- name: Check telemetry consent maintenance artifacts (codegen)
run: node scripts/versioning/check-telemetry-consent-codegen.js

- name: Check telemetry policy binding parity
run: node scripts/check-telemetry-policy-parity.js

- name: Check .NET error-code parity
run: node scripts/check-dotnet-errorcode-parity.js

- name: Check .NET native bindings (codegen)
run: node scripts/check-dotnet-bindings-codegen.js

- name: Validate config corpus against dev schema
run: node scripts/versioning/validate-configs.js
54 changes: 41 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,34 +227,62 @@ wxc-exec.exe --audit policy.json

> **Warning:** `--audit` injects `permissiveLearningMode` β€” AppContainer restrictions are **not** enforced for the duration of the run. Use only for policy authoring. It cannot be combined with `processContainer.captureDenials`; use `captureDenials.mode: "allow"` for permissive application-driven capture. `learningModeLogging` and `permissiveLearningMode` are reserved internal capability names and are rejected in `processContainer.capabilities`. See [docs/learning-mode/capabilities.md](docs/learning-mode/capabilities.md) for the three learning-mode flows.

## Telemetry (Experimental)
## Telemetry

MXC supports optional TraceLogging ETW telemetry for execution observability. When enabled, structured events (`MXC.Execution` and `MXC.Error`) are emitted to the local ETW subsystem via the Rust [`tracelogging`](https://crates.io/crates/tracelogging) crate. Every event includes common fields (Version, Channel, IsDebugging, `UTCReplace_AppSessionGuid`) as Part C custom event data.
MXC supports optional TraceLogging ETW telemetry for execution observability. When enabled, structured events (`MXC.Execution` and `MXC.Error`) are emitted by the `Microsoft.MXC` provider to the local ETW subsystem via the Rust [`tracelogging`](https://crates.io/crates/tracelogging) crate. Every event includes common fields (Version, Channel, IsDebugging, `UTCReplace_AppSessionGuid`) as Part C custom event data.

Telemetry is **experimental** and requires:
1. The `--experimental` CLI flag
2. `"experimental": { "telemetry": { "enabled": true } }` in the JSON config
Telemetry requires:
1. Top-level `"telemetry": { "enabled": true }` in the JSON config
2. Explicit per-user telemetry consent on Windows
3. An administrative policy that permits collection, when a policy is configured

The configuration flag is an additional per-run opt-in; it cannot grant consent
or bypass an administrative block. Telemetry remains off unless every applicable
gate is open. MXC does not use the Windows Diagnostics & feedback setting as a
substitute for application consent.

On non-Windows platforms, all telemetry functions are no-ops.

### Data Collection

The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft's privacy statement. Our privacy statement is located at https://go.microsoft.com/fwlink/?LinkID=824704. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.
The software may collect information about you and your use of the software and
send it to Microsoft when telemetry is enabled and all consent and policy gates
permit collection. Microsoft may use this information to provide services and
improve our products and services. Telemetry is off by default, and MXC does
not treat use of the software as consent. On Windows, the host application must
provide an appropriate notice and obtain explicit user consent before enabling
telemetry, and must provide a way to review or revoke that choice. If you use
these features to collect data from users of your applications, you must comply
with applicable law, including providing appropriate notices to your users
together with a copy of Microsoft's privacy statement. Our privacy statement
is located at https://go.microsoft.com/fwlink/?LinkID=824704. You can learn
more about data collection and use in the help documentation and our privacy
statement.

#### How to turn telemetry off

Telemetry is **off by default**. MXC emits telemetry only when **both** of the following are set, so no action is required to keep it disabled:

1. The `--experimental` CLI flag is passed, **and**
2. `"experimental": { "telemetry": { "enabled": true } }` is present in the JSON config.
Telemetry is **off by default**. MXC emits telemetry only when top-level
`"telemetry": { "enabled": true }` is present in the JSON config, so no
action is required to keep it disabled.

Omitting either (the default) turns telemetry off entirely. On non-Windows platforms all telemetry functions are no-ops.
Those settings are necessary but not sufficient: on Windows, explicit
per-user consent and an administrative policy that permits collection are also
required. Omitting any required gate (the default) turns telemetry off
entirely. On non-Windows platforms all telemetry functions are no-ops and
consent is not applicable.

#### What official builds send

Official/shipped Microsoft builds set a TraceLogging provider group GUID at build time and route `MXC.Execution` and `MXC.Error` events to Microsoft through the UTC pipeline when telemetry is enabled. **Local and open-source builds send nothing to Microsoft by default** β€” the public source ships without a provider group GUID, so events are emitted to the local ETW subsystem only and are not routed to any Microsoft collection pipeline. Internal builds that set the `MXC_TELEMETRY_PROVIDER_GROUP_GUID` environment variable at build time enable the Microsoft-routed path.
Official/shipped Microsoft builds set a TraceLogging provider group GUID at build time and route the `MXC.Execution` and `MXC.Error` events from the `Microsoft.MXC` provider to Microsoft through the UTC pipeline when telemetry is enabled. **Local and open-source builds send nothing to Microsoft by default** β€” the public source ships without a provider group GUID, so events are emitted to the local ETW subsystem only and are not routed to any Microsoft collection pipeline. Internal builds that set the `MXC_TELEMETRY_PROVIDER_GROUP_GUID` environment variable at build time enable the Microsoft-routed path.

No PII is collected. Events contain only execution metrics (duration, backend type, exit code) and a bounded error category (`error_type`). Free-form error message text is never emitted, so paths, usernames, and credentials cannot leak through telemetry. If you use the SDK to build applications, you are responsible for providing appropriate telemetry notices to your own users.
No PII is collected. Events contain only execution metrics (duration, backend
type, exit code) and a bounded error category (`error_type`). Free-form error
message text is never emitted, so paths, usernames, and credentials cannot
leak through telemetry. The SDKs expose presenter-bound consent requests,
typed consent status, and explicit withdrawal. SDK hosts render the canonical
MXC consent resource through their native UI and return the user's decision;
MXC owns persistence. See the normative
[SDK presenter requirements](docs/telemetry/telemetry-consent-design.md#sdk-presenter-requirements).

Privacy information can be found at https://privacy.microsoft.com and in the Microsoft privacy statement at https://go.microsoft.com/fwlink/?LinkID=824704.

Expand Down
8 changes: 4 additions & 4 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ production configs and the dev schema when working on experimental features:
"nestedPty": true, // Allow inner process to allocate its own pty (posix_openpt)
"keychainAccess": false // Allow Keychain via securityd / trustd / cfprefsd / lsd.*
},
"telemetry": { // Telemetry (experimental, Windows only)
"enabled": true // Emit TraceLogging ETW events via pure Rust tracelogging crate
}
},
"telemetry": { // Telemetry (Windows only)
"enabled": true // Emit TraceLogging ETW events via pure Rust tracelogging crate
}
}
```
Expand All @@ -123,7 +123,7 @@ production configs and the dev schema when working on experimental features:
> request carrying either is rejected with a parse error. `correlationVector` is
> the Microsoft Correlation Vector (MS-CV) seeded at `provision` and relayed by
> the client onto later phases (emitted under the TraceLogging `__TlgCV__` field
> when experimental telemetry is enabled). The client relays the value verbatim;
> when telemetry is enabled). The client relays the value verbatim;
> the executor validates it on each non-`provision` phase and *spins* a fresh
> child element off a mutable base, passes an already-frozen vector through
> unchanged, and reseeds a new base if the relayed value is missing or malformed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ State-aware-only fields:
| Field | Type | Required | Description |
|---|---|---|---|
| `phase` | `Phase` member | Yes | Discriminator. Absence means a one-shot request. |
| `correlationVector` | string | No. Relayed by the client onto non-`provision` phases; absent on `provision` (seeded by the executor). Rejected as a parse error on one-shot requests. | Microsoft Correlation Vector (MS-CV) seeded at `provision` and returned in its result; the client relays it verbatim into later phases so the lifecycle shares a telemetry base prefix (emitted under `__TlgCV__`). Each non-`provision` phase validates the relayed value and *spins* a fresh child element off a mutable base (keeping repeat invocations distinct), passes an already-frozen vector through unchanged, and reseeds a new base if it is absent or malformed. Ignored unless experimental telemetry is enabled. See [telemetry docs](../telemetry/telemetry.md#correlating-a-lifecycle). |
| `correlationVector` | string | No. Relayed by the client onto non-`provision` phases; absent on `provision` (seeded by the executor). Rejected as a parse error on one-shot requests. | Microsoft Correlation Vector (MS-CV) seeded at `provision` and returned in its result; the client relays it verbatim into later phases so the lifecycle shares a telemetry base prefix (emitted under `__TlgCV__`). Each non-`provision` phase validates the relayed value and *spins* a fresh child element off a mutable base (keeping repeat invocations distinct), passes an already-frozen vector through unchanged, and reseeds a new base if it is absent or malformed. Ignored unless telemetry is enabled. See [telemetry docs](../telemetry/telemetry.md#correlating-a-lifecycle). |
| `process` | `ProcessConfig` | Required for `exec`; absent otherwise. | Cross-backend execution fields. |

Cross-cutting fields available to state-aware (state-aware-only at top level β€” backends
Expand Down
Loading
Loading