Inject and validate the two learning-mode capabilities - #622
Conversation
Recognize learningMode (deny-and-record) and permissiveLearningMode (audit / allow-all) as distinct learning-mode capabilities in the AppContainer runner (shared by classic AppContainer and BaseContainer). Add a LearningModeCapability enum plus a learning_mode_capability_diagnostics helper that emits an informational note for learningMode and a security warning for permissiveLearningMode. Permissive mode is now allowed in every build, including release, since it is a legitimate auditing tool; it only warns rather than hard-erroring. This replaces the previous release-build rejection of permissiveLearningMode. Capability injection itself rides the existing generic capability -> SID plumbing, so no new injection path is needed. Add unit tests for classification and diagnostics, and document the two capabilities in docs/learning-mode/capabilities.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces explicit recognition of two distinct Windows learning-mode capabilities for the AppContainer-based runners (classic AppContainer and BaseContainer), adding diagnostics and documentation so operators can understand whether they are in deny-and-record vs audit/allow-all mode.
Changes:
- Add
LearningModeCapabilityclassification pluslearning_mode_capability_diagnostics()to emit an informational note forlearningModeand a security warning forpermissiveLearningMode. - Replace the prior release-build rejection behavior in the runner with unconditional diagnostics logging.
- Add unit tests for capability classification/diagnostics and add documentation for the two capabilities.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/backends/appcontainer/common/src/appcontainer_runner.rs | Adds learning-mode capability classification + diagnostics and updates runner logging; includes unit tests. |
| docs/learning-mode/capabilities.md | Documents learningMode vs permissiveLearningMode, their semantics, and how to enable them. |
The config parser previously stripped permissiveLearningMode in release builds and mapped the processContainer.learningMode boolean to permissive (debug only). This contradicted the runner, which now permits permissiveLearningMode in every build and only warns. Stop stripping permissiveLearningMode in release, and map the learningMode boolean to the deny-and-record learningMode capability in every build (restrictions stay enforced; denials are recorded). Update the parser unit tests to cover both capabilities across build configs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ode-capabilities # Conflicts: # src/backends/appcontainer/common/src/appcontainer_runner.rs # src/core/wxc_common/src/config_parser.rs
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
MGudgin
left a comment
There was a problem hiding this comment.
This PR has several issues, but my main concerns are the weakening of security guarantees, the default behaviour change for appcontainer and the fact that basecontainer now also has differnet behaviour with no warning to the caller. I'm blocking merge.
…iveLearningMode Reverts the config-path weakening flagged by review and hardens the permissiveLearningMode gate across both AppContainer-family backends. - config_parser: restore the release-build strip of permissiveLearningMode requested via the config `capabilities` array (case-insensitive). The learningMode boolean still maps to the safe deny-and-record capability in all builds. permissiveLearningMode is reachable only via --audit. - appcontainer_runner / base_container_runner: match the capability case-insensitively (Windows derives the SID case-insensitively) and share a single permissive_learning_mode_requires_audit() gate so BaseContainer now emits the same diagnostics and enforces the same --audit requirement as AppContainer (previously it passed permissive through silently). - main: make the --audit injection dedup check case-insensitive. - docs + dev schema: document that permissiveLearningMode is --audit-only and rejected from config in release; correct the learningMode description. - tests: release-only strip tests (incl. mis-cased), case-insensitive classification, and --audit gate tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The wire.rs learning_mode doc comment feeds the generated SDK TypeScript wire types; regenerate sdk/src/generated/wire.ts to satisfy the SDK wire-types codegen gate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
@MGudgin addressed your comments, left the permissive learning mode available in retail builds based on the teams chat. |
The deny-and-record learning-mode capability was named learningMode, but the OS token that actually produces block-and-log behavior is learningModeLogging (a bare learningMode token yields no logged denials, VM-verified). Rename the recognized capability string, the config learningMode-boolean mapping, diagnostics, tests, and docs accordingly. The processContainer.learningMode config field and permissiveLearningMode are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Permissive learning mode is now on parity with learningModeLogging: it is accepted from the config capabilities array in every build and the runners enforce it via the capability string, emitting a security warning. Removes the runner-side --audit gate at both AppContainer-family runners, the release-build config strip, and the now-unused request.audit model field. --audit is unchanged as the developer inner-loop flow (injects permissiveLearningMode + drives the WPR/ETW PLM trace). Documents the three learning-mode flows in capabilities.md and updates the README --audit warning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
src/core/wxc_common/src/wire.rs:196
- This rustdoc says
permissiveLearningModemay be supplied explicitly, but the parser added in this PR rejects that name case-insensitively (and the next comment says it is reserved). Because this text is propagated into the generated schema and TypeScript wire docs, consumers are told to create a configuration that will always fail parsing. Describe it as internally injected by--audit/denial capture instead, then regenerate the derived files.
/// AppContainer learning mode (deny-and-record): failed access checks are
/// logged for diagnostics while the accesses stay denied; containment is
/// unchanged. Distinct from the allow-all `permissiveLearningMode`
/// capability, which may be supplied explicitly in `capabilities` or
/// injected by the `--audit` CLI flag.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 21 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/core/wxc_common/src/config_parser.rs:727
- This new rejection breaks checked-in supported configs:
tests/configs/hello_world_v060.json:10andtests/examples/01_hello_world.json:9both still supplypermissiveLearningModedirectly, so they now fail parsing instead of running. Please migrate those fixtures (and any compatibility expectations for released 0.6 configs) in this PR so the repository does not continue publishing/running invalid examples.
if let Some(reserved) = caps.iter().find(|capability| {
capability.eq_ignore_ascii_case("learningModeLogging")
|| capability.eq_ignore_ascii_case("permissiveLearningMode")
}) {
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
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
src/core/wxc/src/main.rs:1060
- This validation is never reached for state-aware requests: the
MxcRequest::StateAwarearm calls the divergingrun_state_aware_mainbefore execution reaches this block. As a result,wxc-exec --auditcan proceed with a Windows Sandbox or IsolationSession lifecycle request while silently skipping both capability injection and PLM tracing, contrary to the ProcessContainer-only contract. Reject--auditin the state-aware arm (or validate it there) before dispatch.
if cli.audit {
if let Err(message) = validate_audit_containment(&request.containment) {
docs/learning-mode/capabilities.md:17
- This platform note contradicts the parser behavior added by this PR: direct use of either capability string is rejected case-insensitively on every platform, not ignored outside Windows. Clarify that only the runtime effect of internally injected capabilities is Windows/AppContainer-specific, while direct capability-array use is rejected everywhere.
> **Platform support.** Learning-mode capabilities are **Windows-only** and
> apply to the AppContainer-based backends (classic AppContainer and
> BaseContainer, which share `backends/appcontainer/common`). On other platforms
> the capability strings are ignored.
src/core/wxc_common/src/wire.rs:197
- The canonical schema reference still omits
processContainer.learningMode(docs/schema.md:60-63lists onlyleastPrivilegeandcapabilities). Since this changes the field's semantics and exposes it through the public TypeScript config interface, document its deny-and-record behavior there as required for schema-facing changes.
/// AppContainer learning mode (deny-and-record): failed access checks are
/// logged for diagnostics while the accesses stay denied; containment is
/// unchanged. Distinct from the allow-all `permissiveLearningMode`
/// capability, which is injected internally by the `--audit` CLI flag or
/// dedicated denial-capture configuration.
pub learning_mode: Option<bool>,
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
…#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
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
docs/learning-mode/capabilities.md:98
block-and-logis not a validcaptureDenials.mode; the wire enum acceptsblock(the default) orallow. Using this heading's value in a config produces a parse error.
2. **App / user-configurable (`captureDenials` block-and-log / `learningModeLogging`).**
docs/learning-mode/capabilities.md:105
allow-and-logis not a validcaptureDenials.mode; the permissive wire value isallow. Please use the actual accepted value here.
3. **Fleet auditing (`captureDenials` allow-and-log / `permissiveLearningMode`).**
docs/learning-mode/capabilities.md:121
- These examples advertise mode strings that the parser rejects.
CaptureDenialsModeserializes to"block"and"allow", so the mapping reference should use those exact values.
- `mode: "block-and-log"` (default) maps onto `learningModeLogging`
(deny-and-record) — the app / user-configurable flow.
- `mode: "allow-and-log"` maps onto `permissiveLearningMode` (allow-and-record)
— the fleet-auditing flow.
docs/learning-mode/capabilities.md:17
- The capability strings are not ignored on other platforms:
config_parser.rs:778-788rejects either reserved name case-insensitively before backend execution, regardless of target OS. Distinguish unsupported backend behavior from parser behavior so users do not expect such configs to be accepted.
> **Platform support.** Learning-mode capabilities are **Windows-only** and
> apply to the AppContainer-based backends (classic AppContainer and
> BaseContainer, which share `backends/appcontainer/common`). On other platforms
> the capability strings are ignored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/core/wxc/src/main.rs:1060
- This check is never reached for state-aware requests: the
MxcRequest::StateAwarebranch dispatches through the divergingrun_state_aware_mainat lines 950–988. As a result,--auditis still silently accepted for Windows Sandbox or IsolationSession lifecycle requests even though neither honors the capability. Reject--auditbefore state-aware dispatch (or make it conflict with state-aware envelopes) so the ProcessContainer-only contract applies to every request path.
if cli.audit {
if let Err(message) = validate_audit_containment(&request.containment) {
docs/learning-mode/capabilities.md:17
- The last sentence is inconsistent with the new parser behavior: reserved capability names are rejected case-insensitively before backend dispatch on every platform, rather than ignored on non-Windows hosts. Distinguish the no-effect behavior of
learningModefrom the platform-independent rejection of direct capability names.
> **Platform support.** Learning-mode capabilities are **Windows-only** and
> apply to the AppContainer-based backends (classic AppContainer and
> BaseContainer, which share `backends/appcontainer/common`). On other platforms
> the capability strings are ignored.
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Recognize
learningModeLogging(deny-and-record) andpermissiveLearningMode(audit / allow-all) as distinct, reserved internal capabilities for the AppContainer-family runners. Direct use of either name inprocessContainer.capabilitiesis rejected case-insensitively.📖 Description
LearningModeCapabilityclassification and effective-mode diagnostics shared by classic AppContainer and BaseContainer.learningModeLoggingemits an informational deny-and-record note;permissiveLearningModeemits an always-visible security warning because it allows access.processContainer.learningMode: trueis the supported deny-and-record entry point, while--auditis the supported permissive entry point for Windows ProcessContainer only.--auditrejects every resolved backend except Windows ProcessContainer before mutating policy or starting a trace. For ProcessContainer it injectspermissiveLearningMode, removeslearningModeLoggingif present, and drives the WPR/ETW permissive-learning-mode trace.docs/learning-mode/capabilities.mdand updates the README warning to describe the reserved capability names and supported entry points.🔗 References
captureDenials+ itsmodeselector that maps onto these capabilities)Recommended merge order: #663 (config) → #622 (runtime) → integration.
🔍 Validation
cargo test -p wxc_common -p appcontainer_common— 454 + 141 pass (learning-mode classification / diagnostics tests updated)cargo fmt --all -- --check,cargo clippy -p wxc_common -p appcontainer_common -p wxc --all-targets -- -D warningscargo check --workspace --all-targets(confirms therequest.auditfield removal has no other construction sites)npm run test:unit(SDK) — 187 passnode scripts/versioning/{check-schema-codegen,check-sdk-types-codegen,validate-configs}.js✅ Checklist
Cargo.lock, thedependency-feed-checkcheck passes (see docs/pull-requests.md)📋 Issue Type
GitHub Actions runs the PR validation build automatically. The ADO pipeline
(
MXC-PR-Build) is the Azure version of the PR pipeline, kept in parity with the GitHubActions build; it runs on merge to
main, and Microsoft reviewers with write access can trigger iton a PR with
/azp run. See docs/pull-requests.md.If the
dependency-feed-checkcheck fails on a new dependency, the crate must be added tothe feed before the PR can pass. See docs/pull-requests.md
for the steps.
Microsoft Reviewers: Open in CodeFlow