Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ See [docs/diagnostics.md](docs/diagnostics.md) for full diagnostics reference.
wxc-exec.exe --audit policy.json
```

> **Warning:** `--audit` injects `permissiveLearningMode` β€” AppContainer restrictions are **not** enforced for the duration of the run. Use only for policy authoring. `learningModeLogging` and `permissiveLearningMode` are reserved internal capability names and are rejected in `processContainer.capabilities`; use `processContainer.learningMode: true` for deny-and-record mode or `--audit` for permissive mode. See [docs/learning-mode/capabilities.md](docs/learning-mode/capabilities.md) for the three learning-mode flows.
> **Warning:** `--audit` injects `permissiveLearningMode` β€” AppContainer restrictions are **not** enforced for the duration of the run. Use only for policy authoring. It cannot be combined with `processContainer.captureDenials`; use `captureDenials.mode: "allow"` for permissive application-driven capture. `learningModeLogging` and `permissiveLearningMode` are reserved internal capability names and are rejected in `processContainer.capabilities`. See [docs/learning-mode/capabilities.md](docs/learning-mode/capabilities.md) for the three learning-mode flows.

## Telemetry (Experimental)

Expand Down
7 changes: 7 additions & 0 deletions docs/learning-mode/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ stays enforced:
1. **Developer inner-loop (`--audit`).** A developer runs `wxc-exec --audit`
with ProcessContainer containment to discover the capabilities and paths
their process needs. `--audit` is rejected for every other Windows backend.
It is also mutually exclusive with `captureDenials`; use
`captureDenials.mode: "allow"` for permissive application-driven capture.
It triggers UAC, injects `permissiveLearningMode`, and drives a WPR/ETW
permissive-learning-mode trace for the run. This is typically a static
config the developer iterates on locally.
Expand Down Expand Up @@ -115,6 +117,11 @@ Windows-only `captureDenials` config switch drives collecting those events and
surfacing the resulting denials to the caller. Its `mode` selects how each
ungranted access is handled while it is recorded:

> **Host requirement.** `captureDenials` requires a feature-enabled Windows
> build exposing the BaseContainer security-environment and Learning Mode APIs.
> It is not supported by the AppContainer fallback tiers; unsupported hosts
> return `backend_unavailable`.

- `mode: "block"` (default) maps onto `learningModeLogging`
(deny-and-record) β€” the app / user-configurable flow.
- `mode: "allow"` maps onto `permissiveLearningMode` (allow-and-record)
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 @@ -26,6 +26,7 @@ flatbuffers = { workspace = true }
sandbox_spec = { workspace = true }
widestring = { workspace = true }
winreg = { workspace = true }
learning_mode_windows = { workspace = true }

[dev-dependencies]
tempfile.workspace = true
35 changes: 32 additions & 3 deletions src/backends/appcontainer/common/src/appcontainer_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ use crate::job_object::UiJobObject;
use crate::process_mitigation;
use wxc_common::error::WxcError;
use wxc_common::logger::Logger;
use wxc_common::models::{ExecutionRequest, NetworkEnforcementMode, NetworkPolicy, ScriptResponse};
use wxc_common::models::{
ExecutionRequest, FailurePhase, NetworkEnforcementMode, NetworkPolicy, ScriptResponse,
};
use wxc_common::process_util::{
create_std_pipes, InterruptiblePipeReader, OwnedHandle, PipeReadCanceller, PipeWriter,
SendOwnedHandle, SidAndAttributes,
Expand All @@ -46,6 +48,9 @@ use wxc_common::sandbox_process::{
use wxc_common::script_runner::get_timeout_milliseconds;
use wxc_common::{string_util, ui_policy};

pub(crate) const CAPTURE_DENIALS_FALLBACK_UNSUPPORTED_MSG: &str =
"captureDenials requires the BaseContainer learning-mode APIs and is not supported by the AppContainer fallback tier";

/// `UpdateProcThreadAttribute` value for
/// `PROC_THREAD_ATTRIBUTE_ALL_APPLICATION_PACKAGES_POLICY` that opts the
/// process out of inheriting `ALL APPLICATION PACKAGES` grants. This
Expand Down Expand Up @@ -1303,6 +1308,12 @@ impl AppContainerScriptRunner {

impl SandboxBackend for AppContainerScriptRunner {
fn validate(&self, request: &ExecutionRequest) -> Result<(), ScriptResponse> {
if request.policy.capture_denials.is_some() {
return Err(ScriptResponse {
failure_phase: FailurePhase::BackendUnavailable,
..ScriptResponse::error(CAPTURE_DENIALS_FALLBACK_UNSUPPORTED_MSG)
});
}
if !request.policy.denied_paths.is_empty() && self.filesystem_mode != FilesystemMode::Dacl {
return Err(ScriptResponse::error(
wxc_common::error::DENIED_PATHS_NOT_SUPPORTED_MSG,
Expand Down Expand Up @@ -1823,8 +1834,10 @@ mod tests {

// ---- validate_runner: unsupported policy fields surface as errors. ----

use super::{AppContainerScriptRunner, FilesystemMode};
use wxc_common::models::ExecutionRequest;
use super::{
AppContainerScriptRunner, FilesystemMode, CAPTURE_DENIALS_FALLBACK_UNSUPPORTED_MSG,
};
use wxc_common::models::{ExecutionRequest, FailurePhase};
use wxc_common::sandbox_process::SandboxBackend;

#[test]
Expand Down Expand Up @@ -1885,4 +1898,20 @@ mod tests {
let request = ExecutionRequest::default();
assert!(runner.validate(&request).is_ok());
}

#[test]
fn validate_runner_rejects_capture_denials_on_appcontainer_fallback() {
let runner = AppContainerScriptRunner::new();
let mut request = ExecutionRequest::default();
request.policy.capture_denials = Some(Default::default());

let error = runner
.validate(&request)
.expect_err("AppContainer fallback must not silently ignore captureDenials");
assert_eq!(error.failure_phase, FailurePhase::BackendUnavailable);
assert_eq!(
error.error_message,
CAPTURE_DENIALS_FALLBACK_UNSUPPORTED_MSG
);
}
}
Loading
Loading