Skip to content
Merged
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: 15 additions & 3 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

14 changes: 10 additions & 4 deletions docs/telemetry/telemetry-administrative-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ Any value MXC cannot read or cannot parse — a wrong type, a corrupt value, a
registry error — is treated as `Blocked`. Telemetry collection is never the
outcome of a failure.

MXC reports each distinct unreadable or unrecognized policy failure once per
process so operators can distinguish a broken deployment from an intentional
block without weakening the fail-closed result.

## Deploying it

### Registry deployment
Expand All @@ -63,8 +67,9 @@ HKLM\SOFTWARE\Policies\Mxc\AllowTelemetry directly. For example:

### Verifying

Whatever consent-management/status surface ships must report the user's
recorded choice separately from the administrative ceiling.
The consent status surface reports the user's recorded choice separately from
the administrative ceiling. The consent APIs expose the stored and effective
states, the policy state, and whether a prompt is needed.

For example, if a user previously granted consent and an administrator later
sets `AllowTelemetry=0`, the status must still make both facts visible:
Expand Down Expand Up @@ -127,8 +132,9 @@ administrative policy is an availability control, not an expression of a
user's informed choice, and no Microsoft guidance treats an admin "allow" as
consent on the user's behalf.

The implementation should enforce that rule in one place rather than
re-deriving it in each host surface.
The implementation stack enforces this rule in one place rather than
re-deriving it in each host surface. The telemetry gate combines the per-run
request with the consent and administrative-policy states before each event.

## Relationship to the Windows `AllowTelemetry` policy

Expand Down
45 changes: 26 additions & 19 deletions docs/telemetry/telemetry-consent-design.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Telemetry consent design

This document defines the Windows telemetry-consent contract.
This document defines the implemented Windows telemetry-consent contract.
MXC ships the canonical consent resource, persistent consent storage, and
consent-management APIs described below. Telemetry remains off by default and
is enabled only when every gate in this contract permits collection.

MXC telemetry is Windows-only and fails closed. Collection requires all four
conditions at the time each event is written:
Expand All @@ -19,9 +22,11 @@ collection but can never opt a user in.

## Canonical consent resource

MXC owns one immutable, versioned consent resource. Every EXE and SDK presenter
must show every supplied field verbatim. Hosts control layout, accessibility,
and native UI, but may not substitute wording.
The immutable, versioned authoring resource is
`src/core/wxc_common/resources/telemetry/consent/en-US.json`. The build embeds
it in `wxc_common::telemetry::consent_prompt`. Every EXE and SDK presenter must
show every supplied field verbatim. Hosts control layout, accessibility, and
native UI, but may not substitute wording.

Current resource:

Expand All @@ -32,15 +37,17 @@ Current resource:

### Title

> Help improve Microsoft eXecution Container (MXC)
```text
Help improve Microsoft eXecution Container (MXC)
```

### Body

> Help improve MXC by sharing optional diagnostic data with Microsoft.
> If enabled, MXC sends diagnostic information about product usage,
> performance, and reliability. MXC does not send your commands, file paths,
> credentials, or other customer content.
> You can change your choice at any time.
```text
Help improve MXC by sharing optional diagnostic data with Microsoft.
If enabled, MXC sends diagnostic information about product usage, performance, and reliability. MXC does not send your commands, file paths, credentials, or other customer content.
You can change your choice at any time.
```

### Actions

Expand Down Expand Up @@ -105,11 +112,11 @@ version is 2 and records:
- `promptLocale`
- MXC version, source, and update timestamp for local audit/support provenance

Stored and effective state are reported separately. A legacy grant without the
current prompt version remains visible as `storedState: "granted"` but is
`effectiveState: "undetermined"` with reason `prompt-version-missing` or
`prompt-version-unsupported`. It never authorizes collection. Legacy denial
remains denied.
Stored and effective state are reported separately. A grant without the
current prompt version and canonical prompt locale remains visible as
`storedState: "granted"` but is `effectiveState: "undetermined"` with reason
`prompt-version-missing` or `prompt-version-unsupported`. It never authorizes
collection. Legacy denial remains denied.

Only two operations mutate the store:

Expand Down Expand Up @@ -160,7 +167,7 @@ in control; neither creates consent.
A consent request made while policy is blocked does not invoke the presenter or
persist dormant consent. Withdrawal remains available while blocked.

## Per-run enablement
## Per-run enablement and integration boundary

Consent is not an execution switch. Each run must also request telemetry with
the stable top-level `telemetry.enabled: true` setting. The switch never
Expand Down Expand Up @@ -194,10 +201,10 @@ rename the provider or events.

## Validation and release gate

Implementations should include deterministic coverage for policy evaluation,
Implementations must include deterministic coverage for policy evaluation,
prompt gating, withdrawal, corruption recovery, and concurrent consent-store
mutation. The checked-in ETW smoke test only validates event emission; it is
not a substitute for consent-flow coverage.

The versioned wording and data/control inventory still require Microsoft
privacy/legal approval before release.
Release requires Microsoft privacy/legal approval of the versioned wording and
data/control inventory.
27 changes: 25 additions & 2 deletions docs/telemetry/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,36 @@ Rust constants and `write_event!` struct fields.
| **Sampling keywords** | `MICROSOFT_KEYWORD_MEASURES` named constant | Raw `u64` in `keyword(...)` | `const MICROSOFT_KEYWORD_MEASURES: u64 = 0x0000_4000_0000_0000` |
| **Common event fields** | `_GENERIC_PARTB_FIELDS_ENABLED` pattern | `struct("Name", { ... })` in `write_event!` | `struct("COMMON_MXC_PARAMS", { Version, Channel, IsDebugging, UTCReplace_AppSessionGuid })` |
| **Provider lifecycle** | `IMPLEMENT_TRACELOGGING_CLASS` singleton | `define_provider!` static + `register()`/`unregister()` | `OnceLock<ProviderState>` for version/channel, manual lifecycle |
| **Privacy Product** | Common Schema Part A product | `u16(...)` field | `PartA_PrivacyProduct = 11` (`MXC (Microsoft Execution Containers)`) on all events |
| **Privacy Data Category** | Common Schema Part A data category | `u16(...)` field | `PartA_PrivacyDataCategory = 1` (`Client Diagnostic Data`) on all events |
| **Privacy Data Tags** | `TelemetryPrivacyDataTag(PDT_*)` | `u64("PartA_PrivTags", &val)` field | `PDT_PRODUCT_AND_SERVICE_USAGE` on all events |
| **Activity tracking** | `DEFINE_TELEMETRY_ACTIVITY` | Manual `Opcode` | Not needed for current events |

The remaining gap (activity tracking) is not needed for current events.
If needed later, it can be added incrementally.

## Common Event Fields (Part A)

Every MXC telemetry event carries the WinExt-required Common Schema privacy
metadata:

| Field | Type | Value |
|-------|------|-------|
| `PartA_PrivacyProduct` | uint16 | `11` — `MXC (Microsoft Execution Containers)` |
| `PartA_PrivacyDataCategory` | uint16 | `1` — `Client Diagnostic Data` |
| `PartA_PrivTags` | uint64 | `Product and Service Usage Data` |

Product `11` is privacy-approved and registered in the WinExt product list.
The OS/Common Schema catalog update is tracked by UTC bug `63666074`. MXC's
events use the sampled `MICROSOFT_KEYWORD_MEASURES` keyword and explicit
product consent, so they are optional diagnostic data rather than required
(CORE) or critical-optional events. If the sampling level, event purpose, or
collected fields change, the privacy tag and approval classification must be
re-evaluated.

The WinExt provider-group identifier remains a secure build input. It must not
be copied into source, tests, documentation, or commit messages.

## Common Event Fields (Part C)

Every MXC telemetry event includes a `COMMON_MXC_PARAMS` struct grouping
Expand Down Expand Up @@ -97,7 +121,7 @@ Emitted on execution errors.
| Field | Type | Description |
|-------|------|-------------|
| `mxc.sandbox_kind` | string | Containment kind requested by the caller (`process`, `vm`, or a concrete backend name) |
| `mxc.backend` | string | Concrete containment backend selected on the host |
| `mxc.backend` | string | Containment backend name |
| `mxc.error_type` | string | Error category (`config_error`, `policy_error`, `process_error`, `timeout`, `init_error`, `internal_error`, `cancelled`, `unknown`) |
| `mxc.exit_code` | int32 | Process exit code |
| `mxc.phase` | string | State-aware lifecycle phase; empty for one-shot executions |
Expand All @@ -107,7 +131,6 @@ Emitted on execution errors.
> bounded `error_type` category and the numeric `exit_code` — never the
> message string itself.


### Crash telemetry (panic hook)

When telemetry is active, the executors install a global
Expand Down
6 changes: 3 additions & 3 deletions sdk/dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,9 @@ and its
An IT administrator can still block MXC telemetry device-wide via MXC's own
registry policy setting. See
[`docs/telemetry/telemetry-administrative-policy.md`](../../docs/telemetry/telemetry-administrative-policy.md)
for the stable registry contract and interaction rules. Any eventual .NET
policy/consent query must fail closed rather than upgrading an unreadable
device state into collection.
for the stable registry contract and interaction rules. Policy and consent
queries are not yet exposed by the .NET SDK; any eventual query must fail
closed rather than upgrading an unreadable device state into collection.

## Projects

Expand Down
1 change: 1 addition & 0 deletions src/core/wxc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ fn run_state_aware_main(
telemetry_active,
telemetry::TelemetryContext {
backend,
sandbox_kind: backend,
phase,
correlation_vector: &correlation,
},
Expand Down
14 changes: 10 additions & 4 deletions src/core/wxc_common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ license.workspace = true
microvm = ["dep:nanvix_common", "dep:uuid"]
# Enables JSON Schema generation from the dedicated wire model. Off by default
# so normal builds don't carry schemars.
schema-gen = ["dep:mxc_schema_support", "dep:schemars"]
schema-gen = ["dep:mxc_schema_support", "dep:schemars"]
# Exposes telemetry test harnesses to downstream integration tests.
test-support = []

[dependencies]
serde = { workspace = true }
Expand All @@ -21,11 +23,11 @@ cidr = { workspace = true }
url = { workspace = true }
getrandom = { workspace = true }
semver = "1"
schemars = { workspace = true, optional = true }
schemars = { workspace = true, optional = true }
nanvix_common = { path = "../../backends/nanvix/common", optional = true }
uuid = { workspace = true, optional = true }
mxc_config_contract = { workspace = true }
mxc_schema_support = { workspace = true, optional = true }
mxc_config_contract = { workspace = true }
mxc_schema_support = { workspace = true, optional = true }
mxc_telemetry = { workspace = true }

[target.'cfg(target_os = "windows")'.dependencies]
Expand All @@ -39,3 +41,7 @@ libc = { workspace = true }

[dev-dependencies]
tempfile.workspace = true

[build-dependencies]
serde.workspace = true
serde_json.workspace = true
133 changes: 133 additions & 0 deletions src/core/wxc_common/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use serde::Deserialize;
use std::collections::HashSet;
use std::env;
use std::fs;
use std::io;
use std::path::PathBuf;

#[derive(Debug, Deserialize)]
struct ConsentResource {
#[serde(rename = "resourceVersion")]
resource_version: u32,
locale: String,
messages: ConsentMessages,
#[serde(rename = "learnMoreUrl")]
learn_more_url: String,
}

#[derive(Debug, Deserialize)]
struct ConsentMessages {
title: ConsentMessage,
body: ConsentMessage,
#[serde(rename = "affirmativeLabel")]
affirmative_label: ConsentMessage,
#[serde(rename = "negativeLabel")]
negative_label: ConsentMessage,
#[serde(rename = "learnMoreLabel")]
learn_more_label: ConsentMessage,
}

#[derive(Debug, Deserialize)]
struct ConsentMessage {
id: String,
text: String,
}

fn invalid_resource(message: impl Into<String>) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, message.into())
}

fn rust_string(value: &str) -> String {
format!("{value:?}")
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
let resource_path = manifest_dir.join("resources/telemetry/consent/en-US.json");
println!("cargo:rerun-if-changed={}", resource_path.display());

let resource: ConsentResource = serde_json::from_str(&fs::read_to_string(&resource_path)?)?;
if resource.locale != "en-US" {
return Err(invalid_resource(format!(
"the fallback consent resource must use locale en-US, got {}",
resource.locale
))
.into());
}
if resource.resource_version == 0 {
return Err(invalid_resource("the consent resource version must be nonzero").into());
}
if resource.learn_more_url.is_empty() {
return Err(invalid_resource("the consent resource must include a learn-more URL").into());
}

let messages = [
&resource.messages.title,
&resource.messages.body,
&resource.messages.affirmative_label,
&resource.messages.negative_label,
&resource.messages.learn_more_label,
];
if messages
.iter()
.any(|message| message.id.is_empty() || message.text.is_empty())
{
return Err(invalid_resource("consent message IDs and text must be nonempty").into());
}
let ids: HashSet<&str> = messages.iter().map(|message| message.id.as_str()).collect();
if ids.len() != messages.len() {
return Err(invalid_resource("consent message IDs must be unique").into());
}

let generated = format!(
r#" pub const CONSENT_RESOURCE_VERSION: u32 = {};
pub const FALLBACK_LOCALE: &str = {};

pub static EN_US_CONSENT_PROMPT: ConsentPrompt = ConsentPrompt {{
resource_version: CONSENT_RESOURCE_VERSION,
locale: FALLBACK_LOCALE,
title: ConsentMessage {{
id: {},
text: {},
}},
body: ConsentMessage {{
id: {},
text: {},
}},
affirmative_label: ConsentMessage {{
id: {},
text: {},
}},
negative_label: ConsentMessage {{
id: {},
text: {},
}},
learn_more_label: ConsentMessage {{
id: {},
text: {},
}},
learn_more_url: {},
}};
"#,
resource.resource_version,
rust_string(&resource.locale),
rust_string(&resource.messages.title.id),
rust_string(&resource.messages.title.text),
rust_string(&resource.messages.body.id),
rust_string(&resource.messages.body.text),
rust_string(&resource.messages.affirmative_label.id),
rust_string(&resource.messages.affirmative_label.text),
rust_string(&resource.messages.negative_label.id),
rust_string(&resource.messages.negative_label.text),
rust_string(&resource.messages.learn_more_label.id),
rust_string(&resource.messages.learn_more_label.text),
rust_string(&resource.learn_more_url),
);

let out_dir = PathBuf::from(env::var("OUT_DIR")?);
fs::write(out_dir.join("telemetry_consent_resource.rs"), generated)?;
Ok(())
}
27 changes: 27 additions & 0 deletions src/core/wxc_common/resources/telemetry/consent/en-US.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"resourceVersion": 1,
"locale": "en-US",
"messages": {
"title": {
"id": "telemetry-consent-title",
"text": "Help improve Microsoft eXecution Container (MXC)"
},
"body": {
"id": "telemetry-consent-body",
"text": "Help improve MXC by sharing optional diagnostic data with Microsoft.\nIf enabled, MXC sends diagnostic information about product usage, performance, and reliability. MXC does not send your commands, file paths, credentials, or other customer content.\nYou can change your choice at any time."
},
"affirmativeLabel": {
"id": "telemetry-consent-affirmative-label",
"text": "Yes"
},
"negativeLabel": {
"id": "telemetry-consent-negative-label",
"text": "No"
},
"learnMoreLabel": {
"id": "telemetry-consent-learn-more-label",
"text": "Privacy Statement"
}
},
"learnMoreUrl": "https://go.microsoft.com/fwlink/?linkid=521839"
}
Loading
Loading