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
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ The workspace is organized into six top-level directories under `src/`:

| Directory | Purpose | Examples |
|-----------|---------|----------|
| `core/` | Cross-platform foundation + per-platform aggregator binaries | `wxc_common/`, `wxc/`, `lxc/`, `mxc_darwin/`, `mxc_engine/`, `mxc-sdk/`, `mxc_pty/`, `mxc_build_common/`, `generated/` |
| `core/` | Cross-platform foundation + per-platform aggregator binaries | `wxc_common/`, `wxc/`, `lxc/`, `mxc_darwin/`, `mxc_engine/`, `mxc-sdk/`, `mxc_pty/`, `mxc_build_common/`, `learning_mode_core/`, `generated/` |
| `backends/` | Backend-specific code (one subfolder per containment backend or backend support component) | `appcontainer/common`, `windows_sandbox/{daemon,guest,common,lifecycle}`, `isolation_session/{bindings,common}`, `learning_mode/windows`, `hyperlight/common`, `nanvix/{common,build_common,binaries,runner}`, `lxc/common`, `bubblewrap/common`, `wslc/common`, `seatbelt/common` |
| `ffi/` | Foreign-function-interface crates (C ABI for language bindings) | `mxc_ffi/` |
| `host/` | Host-side utilities | `wxc_host_prep/`, `wxc_winhttp_proxy_shim/` |
Expand All @@ -200,7 +200,8 @@ 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_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, and depends only on `wxc_common`; runner integration consumes it from the AppContainer backend layer.
- `learning_mode_core` is the cross-platform learning-mode denial model and output layer. It owns denial types, summaries, analyzer abstractions, and RFC 7464 emission, 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.
- `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`) 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`, 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 All @@ -214,7 +215,7 @@ The workspace is organized into six top-level directories under `src/`:

### Config parser pattern

The parser deserializes JSON directly into the typed wire model (`wxc_common::wire`), the single source of truth for the config shape (it also generates the JSON schema). All typed config deserialization goes through `config_deserialize.rs`, which distinguishes syntax errors from typed policy errors and adds the complete JSON path plus source line/column when available; state-aware backend errors are prefixed with their full `experimental.<backend>.<phase>` location. `config_parser.rs` then maps the wire types to the validated domain structs in `models.rs`. The stable surface uses `deny_unknown_fields` (closed); the `experimental` block is permissive.
The parser deserializes JSON directly into the typed wire model (`wxc_common::wire`), the single source of truth for the config shape (it also generates the JSON schema). All typed config deserialization goes through `config_deserialize.rs`, which distinguishes syntax errors from typed policy errors and adds the complete JSON path plus source line/column when available; state-aware backend errors are prefixed with their full `experimental.<backend>.<phase>` location. `config_parser.rs` then maps the wire types to the validated domain structs in `models.rs`. The stable surface uses `deny_unknown_fields` (closed); the `experimental` block is permissive.

### TypeScript conventions

Expand Down
10 changes: 10 additions & 0 deletions src/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
members = [
"core/wxc",
"core/wxc_common",
"core/learning_mode_core",
"core/lxc",
"core/mxc_darwin",
"core/mxc-sdk",
Expand Down Expand Up @@ -113,6 +114,7 @@ windows_sandbox_lifecycle = { path = "backends/windows_sandbox/lifecycle" }
isolation_session_common = { path = "backends/isolation_session/common" }
hyperlight_common = { path = "backends/hyperlight/common" }
learning_mode_windows = { path = "backends/learning_mode/windows" }
learning_mode_core = { path = "core/learning_mode_core" }
nanvix_runner = { path = "backends/nanvix/runner" }
tokio = { version = "1", features = ["full"] }
uuid = { version = "1", features = ["v4"] }
Expand Down
1 change: 1 addition & 0 deletions src/backends/learning_mode/windows/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ thiserror = { workspace = true }

[target.'cfg(target_os = "windows")'.dependencies]
wxc_common = { workspace = true }
learning_mode_core = { workspace = true }
Comment thread
richiemsft marked this conversation as resolved.
windows = { workspace = true }
windows-core = { workspace = true }

Expand Down
142 changes: 142 additions & 0 deletions src/backends/learning_mode/windows/examples/lm_analyze.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Decode a sealed learning-mode `.etl` into the captureDenials RFC 7464
//! JSON text sequence, or dump its raw ETW events for schema discovery.
//!
//! This is a developer diagnostic for inspecting captured traces manually.
//! End users and SDK agents do not invoke it: the BaseContainer runner seals
//! the trace and reports its path, while this example performs the manual
//! analysis until runner integration consumes the trace automatically.
//!
//! Usage:
//!
//! ```text
//! # Emit the DeniedResource JSON text sequence (0x1E-framed) to stdout:
//! cargo run -p learning_mode_windows --example lm_analyze -- <path-to.etl> --exit-code <code>
//!
//! # Dump every decoded event (id + property name/value pairs):
//! cargo run -p learning_mode_windows --example lm_analyze -- <path-to.etl> --raw
//! ```
//!
//! Exit codes: `0` = decoded; `2` = wrong platform / bad args; `1` = decode
//! failed.

#[cfg(not(target_os = "windows"))]
fn main() {
eprintln!("lm_analyze is Windows-only");
std::process::exit(2);
}

#[cfg(target_os = "windows")]
fn main() {
std::process::exit(windows_impl::run());
}

#[cfg(target_os = "windows")]
mod windows_impl {
use std::collections::BTreeMap;
use std::io::Write;
use std::path::Path;

use learning_mode_core::{emit, DenialAnalyzer, DenialSummary};
use learning_mode_windows::{visit_raw_events, EtlDenialAnalyzer};

pub fn run() -> i32 {
let args: Vec<String> = std::env::args().skip(1).collect();
let Some(etl_path) = args.first() else {
eprintln!("usage: lm_analyze <path-to.etl> [--raw | --exit-code <code>]");
return 2;
};
let raw = args.iter().any(|a| a == "--raw");
let path = Path::new(etl_path);

if raw {
dump_raw(path)
} else {
let Some(exit_code) = parse_exit_code(&args) else {
eprintln!("--exit-code <code> is required unless --raw is used");
return 2;
};
emit_json_sequence(path, exit_code)
}
}

fn parse_exit_code(args: &[String]) -> Option<i32> {
let index = args.iter().position(|arg| arg == "--exit-code")?;
args.get(index + 1)?.parse().ok()
}

/// Decodes denials and writes the RFC 7464 JSON text sequence to stdout.
fn emit_json_sequence(path: &Path, exit_code: i32) -> i32 {
let analysis = match EtlDenialAnalyzer.analyze(path) {
Ok(d) => d,
Err(e) => {
eprintln!("analyze failed: {e}");
return 1;
}
};
let summary = DenialSummary::new(
exit_code,
analysis.denials.len(),
analysis.denied_resources_truncated,
);
let stdout = std::io::stdout();
let mut handle = stdout.lock();
if let Err(e) = emit::write_stream(&mut handle, &analysis.denials, &summary) {
eprintln!("write failed: {e}");
return 1;
}
eprintln!(
"lm_analyze: {} unique denial(s){}",
analysis.denials.len(),
if analysis.denied_resources_truncated {
" (truncated)"
} else {
""
}
);
0
}

/// Dumps every decoded event, plus a per-event-id histogram, so the
/// real provider/ID/field schema can be confirmed against hardware.
fn dump_raw(path: &Path) -> i32 {
let mut histogram: BTreeMap<(String, u16), usize> = BTreeMap::new();
let stdout = std::io::stdout();
let mut out = stdout.lock();
let event_count = {
let mut visitor = |ev: &learning_mode_windows::DecodedEventParts| {
let provider = format!("{:?}", ev.provider);
*histogram
.entry((provider.clone(), ev.event_id))
.or_default() += 1;
let props: Vec<String> = ev.props.iter().map(|(k, v)| format!("{k}={v}")).collect();
writeln!(
out,
"provider {} event {} | {}",
provider,
ev.event_id,
props.join(" | ")
)
};
match visit_raw_events(path, &mut visitor) {
Ok(count) => count,
Err(e) => {
eprintln!("decode failed: {e}");
return 1;
}
}
};

if writeln!(out, "--- {event_count} event(s) total ---").is_err() {
return 1;
}
for ((provider, id), count) in &histogram {
if writeln!(out, " provider {provider:?} id {id}: {count}").is_err() {
return 1;
}
}
0
}
}
Loading
Loading