docs: add guide for debugging workloads in the process container - #1069
docs: add guide for debugging workloads in the process container#1069Alexander Sklar (asklar) wants to merge 4 commits into
Conversation
There was no doc covering how to attach a debugger to a process running inside a Windows process container. Adds docs/process-container/debugging.md, covering: * Which process you actually want to debug (wxc-exec vs. the sandboxed workload vs. "just tell me what was blocked"). * The OS-side debug-on-launch hook: the sandboxed process is created suspended, and a debugger command line configured in HKLM\SOFTWARE\wxc (debugOnLaunch) is launched under the caller's token with the PID/TID appended, following the packaged-app on-launch convention. Documents that the hook is feature-gated off by default on every build, that WinDbg is the verified configuration and Visual Studio is not, and the caveats (stale value hangs CI, the debugger is uncontained, process.timeout keeps counting while suspended). * The inject-learning-mode hook as a way to force a learning-mode capability onto a run whose config you do not control, with a warning that the permissive variant weakens containment machine-wide. * Pointers to --audit / captureDenials / the diagnostics console, which answer most "I need a debugger" questions more cheaply. * The launch failures MXC already self-diagnoses in launch_diagnostics.rs. Cross-links the new doc from README (Debugging + Documentation table), docs/diagnostics.md, and docs/learning-mode/capabilities.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 32262a24-46de-45a0-9fc9-f18612f4491c
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Copilot review overview
Review tier: Balanced
Findings: 2
New issues introduced by this change (2)
| Severity | Finding |
|---|---|
docs/process-container/debugging.md — This promises an adjusted config for every audit, but audit intentionally skips Adjusted_*.json… |
|
docs/process-container/debugging.md — ui.allowWindows is an SDK policy property, not a field accepted in the JSON config used by… |
What changed in this PR
Adds a Windows ProcessContainer workload-debugging guide and integrates it into related documentation.
Changes:
- Documents debug-on-launch and learning-mode hooks.
- Covers debugger setup, caveats, denial capture, and launch failures.
- Adds cross-references across existing documentation.
| File | Description |
|---|---|
README.md |
Links the new debugging guide. |
docs/process-container/debugging.md |
Adds the debugging guide. |
docs/learning-mode/capabilities.md |
References learning-mode injection. |
docs/diagnostics.md |
Adds related debugging links. |
.github/copilot-instructions.md |
Indexes the new guide. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
|
||
| | Tool | Use it for | | ||
| |---|---| | ||
| | [`wxc-exec --audit`](../learning-mode/capabilities.md#three-learning-mode-flows) | Bringing a new workload up. Runs permissively, records every access check, and emits `denials.json` plus an `Adjusted_*.json` config with the missing grants already filled in. | |
| | Symptom | Cause | Fix | | ||
| |---|---|---| | ||
| | `packaged_app` | The target is a packaged (MSIX) app. Packaged apps cannot be launched inside a container. | Install an unpackaged build. | | ||
| | `dll_init_failed_ui_required` — exit code `0xC0000142` (`STATUS_DLL_INIT_FAILED`) from PowerShell | The sandbox is blocking Win32k syscalls, which PowerShell needs to initialize. | Set `ui.allowWindows: true`. | |
The opening rationale claimed a debugger running as you "cannot simply open" a sandboxed process. That is wrong. Verified empirically on a non-elevated, medium-integrity shell: all 29 AppContainer processes running on the test machine opened successfully with PROCESS_ALL_ACCESS. Mandatory integrity control's no-write-up rule blocks low-to-high access, not the high-to-low direction a debugger needs, and the process DACL grants the creating user. Replaces it with the actual obstacle, which is launch control rather than access: * A debugger cannot create the target itself, because a sandboxed process must be created through the OS sandbox-creation API with a fully-formed spec. A classic IFEO Debugger value does not help either, since it substitutes the debugger for the target image and would run the debugger inside the container. * Attaching after the fact is too late for startup failures. Adds an explicit callout that access is NOT the problem, so readers do not inherit the same misconception, and reframes the hook as launching the debugger alongside the target rather than substituting for it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 32262a24-46de-45a0-9fc9-f18612f4491c
The "Enabling the hook" section described how the OS gates the debug hooks and how to inspect that gating. That is an internal implementation detail of an unshipped capability and does not belong in a public repo. Removed: * The description of the hooks living in a dedicated OS feature group. * The characterization of which build types have it on, and the reference to internal test runs and flighting builds. * The Windows Feature Store registry path, priority values, and EnabledState semantics, plus the pointer to the velocity-key checking code as a way to inspect the debug hooks specifically. Replaced with an Availability section that states only what a reader needs: the hook is off by default, is not available on generally available builds, is not turned on by anything in MXC, and can be recognized behaviorally (the launch stops before the workload runs). Enablement is explicitly out of scope and directed to internal channels. The BaseContainer feature_not_enabled row in the launch-failures table is unchanged; that diagnostic is a shipped, already-public behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 32262a24-46de-45a0-9fc9-f18612f4491c
There was a problem hiding this comment.
Copilot review overview
Review tier: Balanced
Findings: 2
Pre-existing issues (2)
| Severity | Finding |
|---|---|
docs/process-container/debugging.md — ui.allowWindows is an SDK policy property, not a field accepted in the JSON config used by… View comment |
|
docs/process-container/debugging.md — This promises an adjusted config for every audit, but audit intentionally skips Adjusted_*.json… View comment |
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
docs/process-container/debugging.md:146
- This example fails because
Set-ItemPropertydoes not support a-Typeparameter. Since the value may not exist yet, create or replace it withNew-ItemPropertyand its-PropertyTypeparameter instead.
Set-ItemProperty -Path 'HKLM:\SOFTWARE\wxc' -Name 'debugOnLaunch' `
-Value $windbg -Type String
docs/process-container/debugging.md:240
- The suggested automatic fallback is not available after this diagnostic occurs. Tier fallback happens before execution; if the selected BaseContainer launch itself returns
E_NOTIMPL, the runner returnsBackendUnavailableandmxc_engine::rundoes not retry with AppContainer. Recommend a supported host or enabling the required flags instead.
| `feature_not_enabled` — `E_NOTIMPL` from the sandbox API | The BaseContainer feature is not enabled on this build. | Enable the required feature flags, or accept the automatic AppContainer fallback. |
docs/process-container/debugging.md:220
--auditdoes not always emit an adjusted config or fill every missing grant. The postprocessor skipsAdjusted_*.jsonwhen analysis is truncated or has no mergeable file/capability denials, and UI/network/other denials are deliberately not merged. Describing the adjusted file as guaranteed and complete can lead users to treat an incomplete policy as ready to use.
| [`wxc-exec --audit`](../learning-mode/capabilities.md#three-learning-mode-flows) | Bringing a new workload up. Runs permissively, records every access check, and emits `denials.json` plus an `Adjusted_*.json` config with the missing grants already filled in. |
There was a problem hiding this comment.
Copilot review overview
Review tier: Balanced
Findings: 2
Pre-existing issues (2)
| Severity | Finding |
|---|---|
docs/process-container/debugging.md — ui.allowWindows is an SDK policy property, not a field accepted in the JSON config used by… View comment |
|
docs/process-container/debugging.md — This promises an adjusted config for every audit, but audit intentionally skips Adjusted_*.json… View comment |
Suppressed comments (2)
docs/process-container/debugging.md:211
- This currently promises an adjusted config for every audit, but post-processing deliberately skips
Adjusted_*.jsonwhen analysis is truncated or finds no mergeable file/capability grants. Qualify the output so users do not treat a successful audit without this optional artifact as a failure.
| [`wxc-exec --audit`](../learning-mode/capabilities.md#three-learning-mode-flows) | Bringing a new workload up. Runs permissively, records every access check, and emits `denials.json` plus an `Adjusted_*.json` config with the missing grants already filled in. |
docs/process-container/debugging.md:229
- This remediation names the TypeScript
SandboxPolicyproperty, not the JSON config field used bywxc-exec. The wire schema's top-leveluisection acceptsdisable, so copyingui.allowWindowsinto a config is rejected as an unknown field; tell config authors to setui.disable: falseinstead.
| `dll_init_failed_ui_required` — exit code `0xC0000142` (`STATUS_DLL_INIT_FAILED`) from PowerShell | The sandbox is blocking Win32k syscalls, which PowerShell needs to initialize. | Set `ui.allowWindows: true`. |
Verified the current implementation on the OS integration branch rather than relying on the original change description, which is now stale. Three factual errors fixed: 1. Registry location. The debugger command line is not read from a product-specific key. It is an Image File Execution Options value named SecurityEnvironmentDebugger (REG_SZ) on the *target executable*, queried via LdrQueryImageFileExecutionOptions. Rewrote the configuration section and the PowerShell example accordingly, and noted the two consequences that follow: it is scoped per-image, and it is distinct from the classic Debugger value so it does not affect unsandboxed launches. 2. Behavior with no debugger configured. The doc claimed the process stays suspended as an escape hatch for manual attach. It does not: when no debugger is launched the suspend is dropped and the process runs normally. Removed the claim and the Visual Studio workaround that depended on it. 3. Learning-mode injection modes. Described as permissive / non-permissive rather than by internal variant numbering. Also sharpened the stale-value caveat: because the value is keyed by image name, setting it on a shared interpreter such as python.exe or pwsh.exe affects every sandboxed run of that interpreter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 32262a24-46de-45a0-9fc9-f18612f4491c
There was a problem hiding this comment.
Copilot review overview
Review tier: Balanced
Findings: 3
New issues introduced by this change (3)
| Severity | Finding |
|---|---|
docs/process-container/debugging.md — This command fails because Set-ItemProperty has no -Type parameter. Use New-ItemProperty with… |
|
docs/process-container/debugging.md — The PR description states that, with no debugger command configured, the process remains suspended… |
|
docs/process-container/debugging.md — The PR description documents HKLM\SOFTWARE\wxc / debugOnLaunch, while the guide now instructs… |
Pre-existing issues (2)
| Severity | Finding |
|---|---|
docs/process-container/debugging.md — ui.allowWindows is an SDK policy property, not a field accepted in the JSON config used by… View comment |
|
docs/process-container/debugging.md — This promises an adjusted config for every audit, but audit intentionally skips Adjusted_*.json… View comment |
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
docs/process-container/debugging.md:241
- Once a selected BaseContainer launch returns
E_NOTIMPL, there is no automatic retry with AppContainer: fallback occurs during tier selection before the runner executes, while this late error is returned asBackendUnavailable(dispatcher.rs:354-413,base_container_runner.rs:1769-1779). Remove the fallback advice for this diagnostic.
| `feature_not_enabled` — `E_NOTIMPL` from the sandbox API | The BaseContainer feature is not enabled on this build. | Enable the required feature flags, or accept the automatic AppContainer fallback. |
docs/process-container/debugging.md:239
- This remediation uses the TypeScript SDK property name, but the JSON consumed by
wxc-execrejectsui.allowWindows; its closeduiobject acceptsdisable,clipboard, andinjection(wire.rs:516-527,docs/schema.md:291-304). Give both forms explicitly so readers do not copy an invalid field into their config.
| `dll_init_failed_ui_required` — exit code `0xC0000142` (`STATUS_DLL_INIT_FAILED`) from PowerShell | The sandbox is blocking Win32k syscalls, which PowerShell needs to initialize. | Set `ui.allowWindows: true`. |
docs/process-container/debugging.md:221
- An adjusted config is conditional, not an output of every audit: base64 input cannot be adjusted, truncated analysis skips generation, and runs without mergeable file/capability denials produce no
Adjusted_*.json(README.md:230,src/host/plm/src/stop.rs:314-333). Qualify this so a successful audit without that artifact is not mistaken for a failure.
| [`wxc-exec --audit`](../learning-mode/capabilities.md#three-learning-mode-flows) | Bringing a new workload up. Runs permissively, records every access check, and emits `denials.json` plus an `Adjusted_*.json` config with the missing grants already filled in. |
| $key = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\$exe" | ||
|
|
||
| New-Item -Path $key -Force | Out-Null | ||
| Set-ItemProperty -Path $key -Name 'SecurityEnvironmentDebugger' -Value $windbg -Type String |
| If **no** debugger command line is configured for that executable, the hook | ||
| drops the suspend and the process runs normally. There is no "leave it suspended | ||
| and attach by hand" mode: without a configured debugger you get an ordinary | ||
| launch, so configuring the value is the only way to use this hook. |
| Execution Options key, under a dedicated `SecurityEnvironmentDebugger` value: | ||
|
|
||
| ```text | ||
| HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<yourapp.exe> | ||
| SecurityEnvironmentDebugger = "<debugger command line>" (REG_SZ) |


Documentation
There was no doc anywhere in
docs/covering how to attach a debugger to a process running inside a Windows process container. The closest existing coverage wasdocs/diagnostics.md(live tracing) anddocs/learning-mode/capabilities.md(what got denied) - neither answers "how do I break on the workload's first instruction?"Adds
docs/process-container/debugging.md:wxc-exec.exe(ordinary, attach normally) vs. the sandboxed workload vs. "I just want to know what was blocked". Steers readers to--auditfirst, since most "I need a debugger" cases are policy gaps.HKLM\SOFTWARE\wxc(debugOnLaunch) is launched under the caller's token with the PID/TID appended, following the packaged-app on-launch convention. Notes that the hook is feature-gated off by default on every build including release, that WinDbg is the verified configuration and Visual Studio is not, and that with no command line configured the process just stays suspended.debugOnLaunchvalue makes every sandboxed launch on the box look like a hang (including CI), the debugger is deliberately uncontained so its own view is not evidence of what the workload may do, andprocess.timeoutkeeps counting while you sit at the first instruction.learningModeLogging/permissiveLearningModeonto a run whose config you do not control, with a warning that the permissive variant weakens containment machine-wide, and a pointer to the supported entry points for when you do control the config.launch_diagnostics.rsalready self-diagnoses, tabulated with fixes.This documents behavior only. No OS-side source, feature flag IDs, or internal branch/test terminology.
References
Documents the expected behavior of the recently added OS-side debug hooks (debug-on-launch and inject-learning-mode) from the MXC consumer's point of view.
No linked issue - this is a docs gap noticed while looking for existing debugging guidance.
Validation
Docs-only change; the repo has no markdown lint or link-check gate.
process.timeout(docs/schema.md),ui.allowWindows, and the four diagnostickindstrings + the Feature Store registry path (src/backends/appcontainer/common/src/launch_diagnostics.rs).Checklist
Cargo.lock, thedependency-feed-checkcheck passes (see docs/pull-requests.md)Issue Type
Microsoft Reviewers: Open in CodeFlow