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
3 changes: 2 additions & 1 deletion .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 @@ -207,6 +207,7 @@ The workspace is organized into six top-level directories under `src/`:
- The lower-level execution surface lives in `wxc_common::sandbox_process`: the `SandboxBackend` trait (`validate` + `spawn(request, logger, StdioMode) -> Box<dyn SandboxProcess>` + a `diagnose_exit` hook) and the generic `Runner<B>` adapter that bridges any `SandboxBackend` to the run-to-completion `ScriptRunner` (via `spawn(StdioMode::Inherit)` then `wait()`). `StdioMode::Pipes` hands the caller live stdin/stdout/stderr (what the `mxc-sdk` streaming path uses); `StdioMode::Inherit` lets the child inherit the host's stdio (what the executor binaries use, preserving the TTY under a pty). `SandboxBackend` is implemented for Seatbelt, Bubblewrap, and Windows ProcessContainer.
- `mxc_ffi` (`ffi/mxc_ffi`, `crate-type = ["cdylib", "staticlib", "lib"]`) is a flat, panic-safe **C ABI over `mxc-sdk`** for language bindings. `mxc_run(policyJson, command, out)` runs a sandbox to completion, filling a `#[repr(C)] MxcRunResult` (status + exit_code + timed_out + owned stdout/stderr/error C strings); every entry point is `catch_unwind`-wrapped so a panic becomes a status code, never an unwind. Its `build.rs` runs **csbindgen** to generate the C# P/Invoke (`sdk/dotnet/Microsoft.Mxc.Sdk/Native/NativeMethods.g.cs`), gated behind the crate's **`dotnetsdk`** feature (off by default, so the whole-workspace backend build matrix doesn't compile csbindgen). The generated file is **not committed** (gitignored); the C# csproj regenerates it at build time and `scripts/check-dotnet-bindings-codegen.js` runs the codegen in CI and asserts the expected entry points are produced. The C ABI is **not a stable external contract** (native + binding are co-versioned and generated together; see the crate docs). It exposes three surfaces: **run-to-completion** (`mxc_run`), **streaming** (`mxc_spawn` → opaque `MxcSandbox` handle; `mxc_stream_read`/`write`/`flush`, `mxc_sandbox_take_stdin`/`stdout`/`stderr`, `mxc_sandbox_id`/`try_wait`/`wait`/`kill`/`free`, in `src/streaming.rs`), and the **state-aware lifecycle** (`mxc_state_aware` for the envelope phases + `mxc_state_aware_exec` returning a live streaming handle, in `src/state_aware.rs`). All three `.rs` files are csbindgen inputs in `build.rs`; the `MXC_STATUS_*` space already reserves the state-aware phase codes.
- `mxc_pty` is the shared pty bridge used by the LXC backend (`lxc_common::lxc_bindings::attach_run`) so the inner shell sees a real TTY and host stdio is streamed live. (Seatbelt and Bubblewrap no longer use it: they spawn directly and let the child inherit the host's stdio — a TTY when the executor binary runs under a pty — via `SandboxBackend::spawn(StdioMode::Inherit)`.)
- `learning_mode_core` is the **cross-platform learning-mode / captureDenials model + output emitter**: `DeniedResource` (+ `ResourceType`/`AccessType`), `DenialSummary`, the `DenialAnalyzer` decode trait, and `emit` — which writes the on-disk denials deliverable as a **single JSON document** `{ "denials": [...], "summary": {...} }` (`write_document` / `DenialsDocument`) plus the one-line stderr `DenialsOutputPointer`. It carries no OS-specific code (must not depend on any `backends/*` crate); the Windows ETL decoder implementing `DenialAnalyzer` lives in `backends/learning_mode/windows`. When `processContainer.captureDenials` is set, the BaseContainer runner seals a unique internal ETL temp, decodes it via that backend, writes the JSON file (caller's `outputPath` with a unique per-run identifier stamped into the stem, e.g. `denials.<run-id>.json`, or a managed temp), deletes the ETL, and prints the pointer line (carrying the actual path) on stderr. Each denial's `resource` field holds the file path or the AppContainer capability name; capability denials resolve their capability SID to a friendly name via `backends/learning_mode/windows`'s `capability_names` (well-known `S-1-15-3-…` SID → policy name; custom hashed SIDs fall back to the SID string).
- `mxc_build_common` is a build-time helper crate — all Windows binary crates use it in their `build.rs` to embed VersionInfo (ProductName, FileDescription, copyright, version+commit). When adding a new Windows binary crate, add `mxc_build_common` as a build-dependency and call `mxc_build_common::embed_version_info()` from `build.rs`
- `nanvix_build_common` is a **build-only** helper crate (never linked into the runtime): it stages NanVix binaries next to the executable and resolves the `NANVIX_BIN` prefetch directory. The `nanvix_binaries`, `wxc`, and `lxc` build scripts consume it as a `[build-dependencies]` entry. Runtime constants it needs (binary/snapshot filenames) stay in `nanvix_common`. Keep build-only file-staging logic here, not in `nanvix_common` (which is a runtime dependency of `nanvix_runner`).
- Platform-specific modules use `#[cfg(target_os = "windows")]` / `#[cfg(target_os = "linux")]`
Expand Down
66 changes: 64 additions & 2 deletions docs/learning-mode/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,67 @@ ungranted access is handled while it is recorded:
- `mode: "allow"` maps onto `permissiveLearningMode` (allow-and-record)
— the fleet-auditing flow.

The capture pipeline is delivered incrementally and is documented separately as
it lands.
### Output file the caller consumes

After the sandboxed workload exits, MXC decodes the captured denials and writes
them to a **single JSON file** — the deliverable a host application reads to
regenerate its sandbox policy:

```json
{
"denials": [
{
"resource": "C:\\Users\\test\\secret.txt",
"resourceType": "file",
"accessType": "read",
"pid": 1234,
"filetime": "132847890123456789"
},
{
"resource": "internetClient",
"resourceType": "capability",
"accessType": "unknown",
"pid": 1234,
"filetime": "132847890123512345"
}
],
"summary": {
"exitCode": 0,
"totalDenials": 2,
"deniedResourcesTruncated": false
}
}
```

- `denials` is already de-duplicated per `(resource, accessType)`, so
`summary.totalDenials` equals `denials.length`.
- `resource` is the user-visible identifier for the denied resource,
interpreted by `resourceType`: a canonical `C:\…` path for `file`, the
AppContainer **capability name** (e.g. `internetClient`) for `capability`,
and the raw resource identifier otherwise. Well-known capability SIDs are
resolved to their policy name; custom (hashed) capability SIDs that can't be
reversed fall back to the `S-1-15-3-…` SID string.
- `resourceType` is one of `file`, `ui`, `network`, `capability`, `other`;
`accessType` is one of `read`, `write`, `execute`, `unknown`. Capability
denials are recorded under `block`; current `allow` traces expose capability
checks as empty-`ObjectType` access events that are omitted because they do
not carry a stable capability identifier.
- `filetime` is a decimal string containing the Windows `FILETIME` value, so
JavaScript consumers retain all 64 bits without numeric precision loss.

**Locating the file.** Set `captureDenials.outputPath` to name the file
explicitly (its parent directory must already exist). MXC inserts a unique
per-run identifier (process id plus random suffix) into the file stem
(`denials.json` → `denials.<run-id>.json`) so concurrent and sequential
captures using the same configured path do not collide. If `outputPath` is
omitted, MXC writes a managed per-run temp file. Either way, `wxc-exec` prints
**one structured pointer line** to its own **stderr** — carrying the *actual*
path — so the caller can locate the deliverable without scanning the filesystem:

```json
{"type":"captureDenials","outputPath":"C:\\logs\\denials.4321_0123456789abcdef0123456789abcdef.json","exitCode":0,"totalDenials":2,"deniedResourcesTruncated":false}
```

The pointer echoes the file's `summary`; the authoritative record is the file
itself. The intermediate ETW `.etl` trace is an internal, runner-managed temp
file that MXC decodes and then deletes — callers never see it.
7 changes: 4 additions & 3 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ production configs and the dev schema when working on experimental features:
// is logged (deny-by-default preserved). "allow":
// access is allowed and logged (audit; relaxes
// deny-by-default, emits a security warning).
"outputPath": "C:\\logs\\denials.etl" // attempts to a learning-mode ETL trace. The
} // parent dir must already exist; omit outputPath
// for a managed per-run temp file.
"outputPath": "C:\\logs\\denials.json" // JSON denials file the app reads. The parent
} // 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.
},

"lxc": { // LXC-specific
Expand Down
2 changes: 1 addition & 1 deletion schemas/dev/mxc-config.schema.0.8.0-dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"description": "How each ungranted access check is handled while it is recorded. Both modes log every access the policy does not grant to the ETL trace; the mode only decides whether that access is blocked or allowed. Defaults to `block` when omitted."
},
"outputPath": {
"description": "Absolute path where the denial ETL trace is written. The caller names the path; the OS opens it under the caller's own identity when the trace is sealed. When omitted, MXC writes the trace to a managed per-run temporary file. The parent directory must already exist.",
"description": "Absolute path where the JSON denials output file is written — the deliverable a consuming application reads to learn what the workload was denied. It is a single JSON document `{ \"denials\": [...], \"summary\": {...} }`. A per-run identifier (process id plus random suffix) is inserted into the file stem (e.g. `denials.json` -> `denials.<run-id>.json`) so concurrent and sequential captures do not collide; the actual path is reported on stderr. When omitted, MXC writes it to a managed per-run temporary file and prints its path on stderr. The parent directory must already exist. (The intermediate ETL trace is an internal, runner-managed temp file that is decoded then deleted.)",
"type": [
"string",
"null"
Expand Down
2 changes: 1 addition & 1 deletion sdk/node/src/generated/wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface CaptureDenials {
*/
mode?: CaptureDenialsMode | null;
/**
* Absolute path where the denial ETL trace is written. The caller names the path; the OS opens it under the caller's own identity when the trace is sealed. When omitted, MXC writes the trace to a managed per-run temporary file. The parent directory must already exist.
* Absolute path where the JSON denials output file is written — the deliverable a consuming application reads to learn what the workload was denied. It is a single JSON document `{ "denials": [...], "summary": {...} }`. A per-run identifier (process id plus random suffix) is inserted into the file stem (e.g. `denials.json` -> `denials.<run-id>.json`) so concurrent and sequential captures do not collide; the actual path is reported on stderr. When omitted, MXC writes it to a managed per-run temporary file and prints its path on stderr. The parent directory must already exist. (The intermediate ETL trace is an internal, runner-managed temp file that is decoded then deleted.)
*/
outputPath?: string | null;
}
Expand Down
1 change: 1 addition & 0 deletions src/Cargo.lock

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

1 change: 1 addition & 0 deletions src/backends/appcontainer/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ sandbox_spec = { workspace = true }
widestring = { workspace = true }
winreg = { workspace = true }
learning_mode_windows = { workspace = true }
learning_mode_core = { workspace = true }

[dev-dependencies]
tempfile.workspace = true
Loading
Loading