Skip to content

test(process-container): realign stale UI policy, captureDenials and clipboard probe tests - #1393

Merged
Elliot (theelliotm) merged 9 commits into
mainfrom
user/emichlin/update-stale-ci-tests
Oct 5, 2026
Merged

Elliot (theelliotm) merged 9 commits into
mainfrom
user/emichlin/update-stale-ci-tests

Conversation

@theelliotm

@theelliotm Elliot (theelliotm) commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

📖 Description

Nightly process-container jobs fail ~114 assertions that encode behavior two merged PRs changed, not product regressions.

  • [docs/test] Clarify desktopSystemControl policy behavior #1368 established that a permissive UI knob only omits the matching JOB_OBJECT_UILIMIT bit — it never grants the operation. Only the desktopSystemControl docs were updated. The UI policy tests still asserted an undocumented "grant" floor, so it failed wherever the host denied a capability independently. Replaced with a measurement: a permissive baseline learns what is reachable, and only the allow direction is relaxed for what is not. The block direction stays strict everywhere.
  • Fail unavailable guarded denial capture before sandbox creation #1290 removed the captureDenials fallback — with no capture provider the request now fails before the sandbox exists. P15a asserted the removed behavior; it now detects both providers via wxc-exec --probe and asserts the documented outcome.
  • The clipboard read probe seeded from the host, but the clipboard is per-window-station, so the seed never reached the contained process. It now seeds the clipboard under test in-process and keeps the seed's owner window alive across the read. Under ui.clipboard=read, only ERROR_ACCESS_DENIED FAILs the test — nothing can legally seed that clipboard, since the only writer is the one the policy blocks. Under ui.clipboard=all, the seeded clipboard must be able to be read and match the seed to PASS.

Docs: propagated the #1368 caveat to clipboard/systemSettings, and noted that the os-version matrix tracks flag availability, not granted behavior.

Hosts that deny a UI capability outright regardless of policy (e.g. clipboard on 23H2/24H2/25H2) are now recorded as skips rather than containment failures, since a host-level denial says nothing about the UILIMIT mapping.

🔗 References

🔍 Validation

Command Result
cargo fmt --all -- --check passed
cargo clippy -p wxc_ui_probe --all-targets -- -D warnings passed
cargo build -p wxc_ui_probe passed
PowerShell AST parse over tests/scripts/**/*.ps1 passed

Probe behavior verified standalone (host-seeded, empty, and self-seeded clipboards) and on a prerelease-process-container arm64 host, where all eight P4e clipboard cases now pass — including the blocked control cases that confirm enforcement still surfaces as ERROR_ACCESS_DENIED.

✅ Checklist

📋 Issue Type

  • Bug fix
  • Feature
  • Task

Elliot (theelliotm) and others added 2 commits October 5, 2026 11:59
The nightly process-container jobs fail ~114 assertions that encode
behavior two merged PRs changed, not product regressions.

PR #1368 established that a permissive UI knob only omits the
corresponding JOB_OBJECT_UILIMIT bit; it never grants the operation.
That is mechanism-general, but only the desktopSystemControl section was
updated, so the UI policy matrix still asserted an undocumented "grant"
floor and failed wherever the host denies a capability independently.

Replace that assertion with a measurement. Measure-UiGrantable runs a
fully permissive baseline to learn which capabilities are reachable on
this host, and the per-knob cases skip only the allow direction for
capabilities that are not. The block direction stays strict everywhere:
an operation that succeeds under a deny expectation is still a failure.
A baseline that does not report on every tag records a failure and gates
nothing, so coverage is never dropped silently.

Phase 4b now parses INCONCLUSIVE instead of collapsing it to <missing>.

PR #1290 removed the captureDenials fallback: where no capture provider
is available the request fails before the sandbox is created. Detect
both providers via wxc-exec --probe and assert that documented outcome
instead of the removed one. The guarded provider resolves plm.exe next
to the loaded module, so probe it from the debug binary the suites
actually run. Capture assertions that need a live session now skip when
no provider exists.

Propagate the #1368 caveat to the clipboard and systemSettings sections,
and note that the os-version matrix tracks flag availability rather than
granted behavior.

Two failure clusters are deliberately left out of scope and are now
reported honestly rather than as containment defects: the hostLoopback
rejection-detection gap, and the clipboard read probe seeding, which
cannot work from the host because the clipboard is per-window-station.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nd trip

Requiring a readable round trip to call the read path allowed is a
stronger precondition than some hosts can satisfy. On a prerelease
process-container host both permissive cases failed it for different
reasons: with the write limit set the self-seed is refused and the
container's clipboard is genuinely empty (formats=0, gle=1168), and
without it the seed lands but the data handle comes back unreadable
(formats=4, gle=6).

Neither call was refused. JOB_OBJECT_UILIMIT_READCLIPBOARD denies with
ERROR_ACCESS_DENIED, so treat any other failure as the limit being
absent and report it as such. The clipboard=none and clipboard=write
cases are the standing control for that denial, and both still pass on
the affected host.

INCONCLUSIVE now means only that OpenClipboard itself was denied, which
is an environment denial the harness already skips, so drop the branches
that classified a missing seed.

Also trim the comments added by the two preceding commits.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 5, 2026 19:27
@azure-pipelines

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

Elliot (theelliotm) and others added 4 commits October 5, 2026 12:31
READCLIPBOARD reports allowed when GetClipboardData was not refused,
which establishes the UILIMIT mapping but not the read path. Recording
that as a plain PASS named "READCLIPBOARD=allowed" reads as a verified
round trip and drops the probe diag that says otherwise.

Record it as a warn carrying the diag. The run still does not fail: the
mapping under test is correct, and the blocked direction is untouched.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The read probe destroyed the seed's owner window before reading the data
back. This process pumps no messages, so WM_RENDERALLFORMATS and
WM_DESTROYCLIPBOARD go unhandled and the data can become unreadable
while the format list still advertises it -- which is what a
prerelease process-container host reported: formats=4 from a seed that
succeeded, and GetClipboardData failing with ERROR_INVALID_HANDLE.

Return the owner from the seed and destroy it only after the read, so a
single run writes and reads back the same clipboard with the owner
alive throughout. Failure paths still tear it down immediately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The UI policy matrix dropped each tag's DIAG line unless the result was
a warn or a skip, so a passing case said only "got=blocked" and gave no
record of how the probe reached that verdict. Append the diagnostic to
every recorded detail when the probe emitted one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
JOB_OBJECT_UILIMIT_READCLIPBOARD denies with ERROR_ACCESS_DENIED, so any
other outcome means the limit did not refuse the call -- which is what a
permissive policy asserts. Warning when the probe additionally could not
read text back held the allow direction to a round trip the policy does
not promise, and under ui.clipboard=read nothing can legally seed that
clipboard: the only writer is the one the policy blocks.

The recorded detail carries the probe diagnostic either way, so the
distinction stays visible without failing to report a green result.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

Note

Copilot was unable to run its full agentic suite in this review.

Copilot review overview

Review effort: Lite
Findings: 1 High severity · 2 Medium severity

Open (3)
What changed in this PR

Updates the process-container UI policy and capture-denials tests/probes to distinguish “limit not set” from “capability actually exercisable on this host”, reducing false failures and improving diagnostics.

Changes:

  • UI policy matrix: measure which UI operations are grantable on the host under a permissive policy and skip allow-direction assertions that are undecidable.
  • UI mitigations: treat INCONCLUSIVE probe results as “not exercised” and skip those assertions with diagnostics.
  • Capture-denials: detect native/guarded capture provider availability and skip or assert the correct branch based on host capability; improve clipboard probing robustness and update docs accordingly.
File Description
tests/​scripts/​run_processcontainer_ui_policy_matrix_test.ps1 Adds host grantability measurement and uses it to skip undecidable allow-direction checks; improves diag output.
tests/​scripts/​run_processcontainer_ui_mitigations_test.ps1 Allows INCONCLUSIVE verdicts and skips undecidable checks with probe diagnostics.
tests/​scripts/​run_processcontainer_privilege_test.ps1 Splits assertions based on guarded-capture availability; verifies expected failure mode when unavailable.
tests/​scripts/​run_processcontainer_capture_denials_test.ps1 Skips capture-denials tests when capture providers are unusable on the host.
tests/​scripts/​lib/​WinProcessContainer.Native.ps1 Clarifies clipboard seeding rationale for UI probe scenarios.
tests/​scripts/​lib/​WinProcessContainer.Common.ps1 Extends host capability detection to include native/guarded capture providers and prints them.
src/​testing/​wxc_ui_probe/​src/​main.rs Adds in-process clipboard seeding and richer diagnostics for read-clipboard probing.
docs/​process-container/​os-version-support.md Clarifies that “supported” means “flag can be set”, not “operation is enabled.”
docs/​process-container/​UIPolicy_Schema.md Clarifies semantics: permissive values omit limits but do not explicitly grant capabilities; updates examples/tables.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/testing/wxc_ui_probe/src/main.rs
Comment thread tests/scripts/run_processcontainer_ui_mitigations_test.ps1 Outdated
Comment thread tests/scripts/run_processcontainer_ui_policy_matrix_test.ps1
Copilot AI balanced review requested due to automatic review settings October 5, 2026 19:49
CloseClipboard was resolved optionally while OpenClipboard was called
unconditionally, so an unresolvable CloseClipboard would leave the
clipboard open for the rest of the process. The probe runs several tags
per invocation, so that would break every later clipboard tag in the
same run. Resolve it up front with the other required entry points and
close unconditionally.

Escape the tag before interpolating it into the DIAG match so the
pattern stays an exact tag match.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

🔵 Needs a closer look

Inconclusive probes can incorrectly relax or skip strict UI enforcement assertions.

Review effort: Balanced
Findings: None

Resolved since last review (3)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Do not skip inconclusive blocked-direction control checks

tests/​scripts/​run_processcontainer_ui_mitigations_test.ps1:103

Scenario A is exclusively the blocked-direction control, but this turns every INCONCLUSIVE verdict into a non-failing skip. That weakens the existing enforcement check and conflicts with the PR's claim that the block direction stays strict; an unexercised limit should keep this assertion red rather than allowing the nightly suite to pass without confinement evidence.

Medium severity Treat inconclusive baselines as failed measurements

tests/​scripts/​run_processcontainer_ui_policy_matrix_test.ps1:85

An INCONCLUSIVE baseline is currently recorded as “host denies” because every token other than FAIL maps to $false. For example, a transient OpenClipboard failure emits READCLIPBOARD=INCONCLUSIVE; this then causes later allow-direction assertions to be skipped even though the baseline never established a host denial. Treat inconclusive baseline results as a failed measurement and leave the map empty so subsequent cases remain strict.

Medium severity Keep block-direction checks strict for inconclusive results

tests/​scripts/​run_processcontainer_ui_policy_matrix_test.ps1:184

This branch also skips INCONCLUSIVE results when $want is blocked, contradicting the stated invariant that only allow-direction checks are relaxed and block-direction checks remain strict. Restrict this exception to allowed expectations; otherwise an all-bits enforcement regression can become a skip whenever the probe reports an inconclusive precondition.

@theelliotm
Elliot (theelliotm) marked this pull request as ready for review October 5, 2026 20:03
@theelliotm
Elliot (theelliotm) requested a review from a team as a code owner October 5, 2026 20:03
@theelliotm Elliot (theelliotm) self-assigned this Oct 5, 2026
@theelliotm
Elliot (theelliotm) marked this pull request as draft October 5, 2026 20:08
Copilot AI balanced review requested due to automatic review settings October 5, 2026 20:09
Elliot (theelliotm) and others added 2 commits October 5, 2026 13:09
… is allowed

ui.clipboard=all permits the probe to seed the clipboard it reads, so
accepting "the limit did not refuse the call" left the read path itself
unasserted: a seeded-but-unreadable handle still passed. That is exactly
the ERROR_INVALID_HANDLE failure this branch fixes, which the suite
would not have caught.

Opt the clip-all case into requiring the seeded text to come back. A
refused seed stays a skip, since the WRITECLIPBOARD assertion already
owns that and there is nothing to read. ui.clipboard=read cannot take
this requirement -- the only writer is the one that policy blocks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The read probe only checked that GetClipboardData returned a non-null
handle, but reported "returned clipboard text" -- claiming more than it
verified. Lock the handle while the clipboard is still open, decode the
UTF-16, and compare it to the seed, so the round trip ui.clipboard=all
requires is an actual content match rather than handle liveness.

Contents are never echoed: on a shared window station they can belong to
the host user, so a mismatch reports only its length.

The verdict is unchanged in every case. A handle that does not match the
seed still means the limit did not refuse the call.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@theelliotm
Elliot (theelliotm) marked this pull request as ready for review October 5, 2026 20:15

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

:shipit:

@theelliotm
Elliot (theelliotm) merged commit 058b5c5 into main Oct 5, 2026
14 of 20 checks passed
@theelliotm
Elliot (theelliotm) deleted the user/emichlin/update-stale-ci-tests branch October 5, 2026 20:43
@theelliotm
Elliot (theelliotm) removed the request for review from Copilot October 5, 2026 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants