Skip to content

Commit c48e0fc

Browse files
richiemsftCopilot
andauthored
Add processContainer.captureDenials config for Windows denial capture (#663)
* Add processContainer.captureDenials config for Windows denial capture Introduce a captureDenials object under processContainer that enables the Windows deny-and-record learning-mode trace. Presence enables capture; an optional outputPath names where the ETL trace is sealed (validated absolute with an existing parent), otherwise the runner falls back to a managed per-run temp file. Config plumbing only: wire model, ContainerPolicy, parser validation, regenerated dev schema + SDK wire types, docs, and an example. Capability auto-injection and the runner trace wiring land in the follow-up integration PR (on top of #622 + #661). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Allow-list processContainer.captureDenials as a wire-only field in SDK conformance oracle Mirror the existing learningMode treatment: captureDenials is a wire field the SDK does not surface through the policy API yet, so the compile-time conformance test must include it in the ProcessContainer OnlyInWire allow-list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review: reject root outputPath, fix doc/example, rename test - validate_capture_denials_output_path now rejects filesystem-root paths (Path::parent()==None), with a new unit test. - wire.rs doc drops the unverified 'and be writable' claim (validation only checks parent existence); schema + SDK types regenerated. - Example 29 and docs/schema.md no longer show a C:\\logs path whose parent may not exist (example omits outputPath; docs note the parent must already exist). - Renamed capture_denials_threads_... test to capture_denials_accepts_... for clarity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add captureDenials.mode selector (block-and-log / allow-and-log) Introduce a CaptureDenialsMode enum on the captureDenials config object so a single learning-mode surface chooses between block-and-log (default; deny-by-default preserved) and allow-and-log (audit; relaxes deny-by-default, emits a security warning). Both modes record every ungranted access check; mode only decides block vs allow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Shorten capture denial mode names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec * Reject directory paths for denial traces Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
1 parent 657c2c8 commit c48e0fc

8 files changed

Lines changed: 439 additions & 8 deletions

File tree

docs/schema.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,15 @@ production configs and the dev schema when working on experimental features:
5959

6060
"processContainer": { // Process-based container-specific
6161
"leastPrivilege": false,
62-
"capabilities": ["internetClient"]
62+
"capabilities": ["internetClient"],
63+
"captureDenials": { // Windows-only: record the process's access
64+
"mode": "block", // "block" (default): access stays denied and
65+
// is logged (deny-by-default preserved). "allow":
66+
// access is allowed and logged (audit; relaxes
67+
// deny-by-default, emits a security warning).
68+
"outputPath": "C:\\logs\\denials.etl" // attempts to a learning-mode ETL trace. The
69+
} // parent dir must already exist; omit outputPath
70+
// for a managed per-run temp file.
6371
},
6472

6573
"lxc": { // LXC-specific

schemas/dev/mxc-config.schema.0.8.0-dev.json

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,50 @@
4444
},
4545
"type": "object"
4646
},
47+
"CaptureDenials": {
48+
"additionalProperties": false,
49+
"description": "Windows denial-capture settings. The presence of the `captureDenials` object enables capture; all fields are optional.",
50+
"properties": {
51+
"mode": {
52+
"anyOf": [
53+
{
54+
"$ref": "#/definitions/CaptureDenialsMode"
55+
},
56+
{
57+
"type": "null"
58+
}
59+
],
60+
"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."
61+
},
62+
"outputPath": {
63+
"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.",
64+
"type": [
65+
"string",
66+
"null"
67+
]
68+
}
69+
},
70+
"type": "object"
71+
},
72+
"CaptureDenialsMode": {
73+
"description": "How `captureDenials` handles each ungranted access check while recording it.",
74+
"oneOf": [
75+
{
76+
"description": "`block` — the access stays **denied** and the denial is recorded. Deny-by-default containment is preserved; this is the safe default.",
77+
"enum": [
78+
"block"
79+
],
80+
"type": "string"
81+
},
82+
{
83+
"description": "`allow` — the access is **allowed** and recorded (audit mode). This relaxes deny-by-default for the run, so it is a security-sensitive choice and the runner emits a security warning.",
84+
"enum": [
85+
"allow"
86+
],
87+
"type": "string"
88+
}
89+
]
90+
},
4791
"ClipboardPolicy": {
4892
"description": "Clipboard access level.",
4993
"enum": [
@@ -646,6 +690,17 @@
646690
"null"
647691
]
648692
},
693+
"captureDenials": {
694+
"anyOf": [
695+
{
696+
"$ref": "#/definitions/CaptureDenials"
697+
},
698+
{
699+
"type": "null"
700+
}
701+
],
702+
"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."
703+
},
649704
"learningMode": {
650705
"description": "AppContainer permissive learning mode.",
651706
"type": [

sdk/node/src/generated/wire.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,25 @@ export interface BaseProcessUi {
3737
systemSettings?: string | null;
3838
}
3939

40+
/**
41+
* Windows denial-capture settings. The presence of the `captureDenials` object enables capture; all fields are optional.
42+
*/
43+
export interface CaptureDenials {
44+
/**
45+
* 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.
46+
*/
47+
mode?: CaptureDenialsMode | null;
48+
/**
49+
* 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.
50+
*/
51+
outputPath?: string | null;
52+
}
53+
54+
/**
55+
* How `captureDenials` handles each ungranted access check while recording it.
56+
*/
57+
export type CaptureDenialsMode = "block" | "allow";
58+
4059
/**
4160
* Clipboard access level.
4261
*/
@@ -299,6 +318,10 @@ export interface ProcessContainer {
299318
* AppContainer capabilities (e.g. `internetClient`, `registryRead`).
300319
*/
301320
capabilities?: string[] | null;
321+
/**
322+
* 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.
323+
*/
324+
captureDenials?: CaptureDenials | null;
302325
/**
303326
* AppContainer permissive learning mode.
304327
*/

sdk/node/tests/unit/wire-conformance.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,14 @@ type _WslcWireKeys = AssertTrue<Equivalent<OnlyInWire<WslcConfig, WireWslc>, nev
199199
type _PortMappingWireKeys = AssertTrue<Equivalent<OnlyInWire<PublicPortMapping, WirePortMapping>, never>>;
200200
type _LxcWireKeys = AssertTrue<Equivalent<OnlyInWire<LxcConfig, WireLxc>, never>>;
201201

202-
// `processContainer.learningMode` is a wire field the SDK does not expose (the
203-
// AppContainer permissive learning mode is not surfaced through the policy API).
202+
// `processContainer.learningMode` and `processContainer.captureDenials` are wire
203+
// fields the SDK does not yet expose (the AppContainer learning-mode features are
204+
// not surfaced through the policy API).
204205
type _ProcessContainerWireKeys = AssertTrue<
205-
Equivalent<OnlyInWire<ProcessContainerConfig, WireProcessContainer>, 'learningMode'>
206+
Equivalent<
207+
OnlyInWire<ProcessContainerConfig, WireProcessContainer>,
208+
'learningMode' | 'captureDenials'
209+
>
206210
>;
207211

208212
// `seatbelt.guiAccess` and `seatbelt.launchMethod` are wire fields the one-shot

0 commit comments

Comments
 (0)