Skip to content

docs: add guide for debugging workloads in the process container - #1069

Draft
Alexander Sklar (asklar) wants to merge 4 commits into
mainfrom
user/asklar/process-container-debugging-doc
Draft

docs: add guide for debugging workloads in the process container#1069
Alexander Sklar (asklar) wants to merge 4 commits into
mainfrom
user/asklar/process-container-debugging-doc

Conversation

@asklar

@asklar Alexander Sklar (asklar) commented Aug 29, 2026

Copy link
Copy Markdown
Member

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 was docs/diagnostics.md (live tracing) and docs/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:

  • Which process you actually want to debug - wxc-exec.exe (ordinary, attach normally) vs. the sandboxed workload vs. "I just want to know what was blocked". Steers readers to --audit first, since most "I need a debugger" cases are policy gaps.
  • Debug on launch. Documents the OS-side hook's contract: 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. 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.
  • Caveats that will otherwise cost someone an afternoon: a stale debugOnLaunch value 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, and process.timeout keeps counting while you sit at the first instruction.
  • Inject-learning-mode hook - forcing learningModeLogging / permissiveLearningMode onto 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.
  • Common launch failures - the four cases launch_diagnostics.rs already 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.

  • Verified every relative link resolves to an existing file.
  • Verified every cross-document anchor matches a real heading in the target file.
  • Verified the one external link (Microsoft Learn, UWP/WinDbg on-launch debugging) resolves.
  • Checked field names against the repo rather than assuming: process.timeout (docs/schema.md), ui.allowWindows, and the four diagnostic kind strings + the Feature Store registry path (src/backends/appcontainer/common/src/launch_diagnostics.rs).

Checklist

Issue Type

  • Bug fix
  • Feature
  • Task
Microsoft Reviewers: Open in CodeFlow

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
Copilot AI balanced review requested due to automatic review settings August 29, 2026 19:52
@asklar
Alexander Sklar (asklar) requested a review from a team as a code owner August 29, 2026 19:52
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@microsoft-github-policy-service microsoft-github-policy-service Bot added the Copilot-Instructions PR modifies Copilot instruction files (.github/copilot-instructions.md or .github/instructions/) label Aug 29, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

Review tier: Balanced
Findings: 2 Low severity

New issues introduced by this change (2)
Severity Finding
Low severity docs/​process-container/​debugging.md — This promises an adjusted config for every audit, but audit intentionally skips Adjusted_*.json
Low severity docs/​process-container/​debugging.mdui.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
Copilot AI review requested due to automatic review settings August 29, 2026 20:00
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

Review tier: Balanced
Findings: 2 Low severity

Pre-existing issues (2)
Severity Finding
Low severity docs/​process-container/​debugging.mdui.allowWindows is an SDK policy property, not a field accepted in the JSON config used by… View comment
Low severity docs/​process-container/​debugging.md — This promises an adjusted config for every audit, but audit intentionally skips Adjusted_*.jsonView 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-ItemProperty does not support a -Type parameter. Since the value may not exist yet, create or replace it with New-ItemProperty and its -PropertyType parameter 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 returns BackendUnavailable and mxc_engine::run does 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

  • --audit does not always emit an adjusted config or fill every missing grant. The postprocessor skips Adjusted_*.json when 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. |

Copilot AI review requested due to automatic review settings August 29, 2026 20:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

Review tier: Balanced
Findings: 2 Low severity

Pre-existing issues (2)
Severity Finding
Low severity docs/​process-container/​debugging.mdui.allowWindows is an SDK policy property, not a field accepted in the JSON config used by… View comment
Low severity docs/​process-container/​debugging.md — This promises an adjusted config for every audit, but audit intentionally skips Adjusted_*.jsonView 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_*.json when 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 SandboxPolicy property, not the JSON config field used by wxc-exec. The wire schema's top-level ui section accepts disable, so copying ui.allowWindows into a config is rejected as an unknown field; tell config authors to set ui.disable: false instead.
| `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`. |

@asklar
Alexander Sklar (asklar) marked this pull request as draft August 29, 2026 20:11
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
Copilot AI review requested due to automatic review settings August 29, 2026 20:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

Review tier: Balanced
Findings: 3 Medium severity · 2 Low severity

New issues introduced by this change (3)
Severity Finding
Medium severity docs/​process-container/​debugging.md — This command fails because Set-ItemProperty has no -Type parameter. Use New-ItemProperty with…
Medium severity docs/​process-container/​debugging.md — The PR description states that, with no debugger command configured, the process remains suspended…
Medium severity docs/​process-container/​debugging.md — The PR description documents HKLM\SOFTWARE\wxc / debugOnLaunch, while the guide now instructs…
Pre-existing issues (2)
Severity Finding
Low severity docs/​process-container/​debugging.mdui.allowWindows is an SDK policy property, not a field accepted in the JSON config used by… View comment
Low severity docs/​process-container/​debugging.md — This promises an adjusted config for every audit, but audit intentionally skips Adjusted_*.jsonView 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 as BackendUnavailable (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-exec rejects ui.allowWindows; its closed ui object accepts disable, clipboard, and injection (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
Comment on lines +93 to +96
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.
Comment on lines +115 to +119
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Copilot-Instructions PR modifies Copilot instruction files (.github/copilot-instructions.md or .github/instructions/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants