You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(agent)!: restrict package broker pipe access and report busy state - #2029
Standard users can no longer open the package broker pipe with generic write access. Package broker clients must open the pipe with GENERIC_READ | FILE_WRITE_DATA, as Devolutions.Now.Policy.Client does starting with the release that includes Devolutions/now-libraries#114, and UniGetUI starting with the release that adopts it.
The broker keeps a pipe instance listening at all times. When all connection slots are in use, or a user already holds 4 concurrent connections, the broker replies 503 Service Unavailable with a Retry-After header and a BrokerPaused error instead of leaving the pipe busy. Clients should retry these replies after the indicated delay. At most 16 busy replies are pending at once; beyond that, further clients are disconnected without a reply and should retry the connection later.
Do not merge before the Devolutions.Now.Policy.Client and UniGetUI releases above are available.
BREAKING CHANGE: package broker clients running as standard users must open the pipe without GENERIC_WRITE.
Two tests that the deferral commit had added are gone again: the WaitNamedPipeW waiter test, which assumes no instance listens at capacity, and client_access_allows_generic_read_write_clients. client_access_rejects_generic_write_clients and the narrowed access-mask, instance-creation, and busy-reply tests are back.
instance-creation retry with backoff and first_pipe_instance(true) for the first instance;
connect-error backoff with rate-limited logging;
the 5 s request-header timeout.
After startup, an instance always listens. The next instance is created before a connected one is handed off. Rejected clients are disconnected so their instance listens again. If creating the next instance fails, the current instance keeps listening during the backoff. Clients arriving in that window are disconnected without a reply, because sending one would mean handing off the only listening instance (accept_loop_keeps_listening_while_next_instance_creation_backs_off).
agent-policy-tester already opens the pipe with GENERIC_READ | FILE_WRITE_DATA; only its doc comment changes.
Over-limit clients are disconnected without promised 503 Retry-After
crates/now-package-broker/src/pipe.rs:366
This fallback contradicts the PR's busy-response contract: once 16 busy replies are waiting for request headers, the next over-limit client is disconnected here without the promised 503 and Retry-After. The dependent policy client retries only 503 responses carrying Retry-After, so an incomplete response will not follow the new retry path precisely during a sustained overload. Either preserve a retryable response for this case or document and coordinate the disconnect behavior with clients before release.
Replacement retry blocks accept loop and starves the healthy spare
crates/now-package-broker/src/pipe.rs:290
If both disconnecting this failed instance and creating its replacement fail, awaiting the exponential retry pauses the entire accept loop even when spare.instance is still healthy. A client can connect to that spare during the 100 ms–30 s sleep, consume the only listener, and receive no response until the sleep ends; subsequent clients then see ERROR_PIPE_BUSY. Promote/continue polling the spare and schedule replacement asynchronously instead of sleeping in the accept loop.
This issue also appears on line 325 of the same file.
Changes made after review, beyond the plain revert:
Per-user admission happens before the expensive identity capture. A cheap pid → token-user lookup takes the per-user slot first, and the full capture must report the same user (UserAdmission::serve, covered by an integration test).
The accept loop keeps two listening instances and awaits both, so a client arriving during a handoff connects instead of getting ERROR_PIPE_BUSY. Bursts larger than the listener count still see ERROR_PIPE_BUSY briefly; NamedPipeClientStream.ConnectAsync waits that out.
If a failed instance can't be disconnected or replaced, the spare takes over instead of the loop sleeping.
Busy replies and rejections are logged at most once per minute, with a suppressed count.
At most 16 busy replies are pending at once. Clients beyond that are disconnected without a reply; the PR body documents this.
Note
LLM-assisted content (no human feedback).
Base automatically changed from
cbenoit-broker-input-and-pipe-hardening to
masterOctober 6, 2026 05:05
Standard users can no longer open the package broker pipe with generic write access. Package broker clients must open the pipe with `GENERIC_READ | FILE_WRITE_DATA`, as `Devolutions.Now.Policy.Client` does starting with the release that includes Devolutions/now-libraries#114, and UniGetUI starting with the release that adopts it.
The broker keeps a pipe instance listening at all times. When all connection slots are in use, or a user already holds 4 concurrent connections, the broker replies `503 Service Unavailable` with a `Retry-After` header and a `BrokerPaused` error instead of leaving the pipe busy. Clients should retry these replies after the indicated delay. At most 16 busy replies are pending at once; beyond that, further clients are disconnected without a reply and should retry the connection later.
Do not merge before the `Devolutions.Now.Policy.Client` and UniGetUI releases above are available.
BREAKING CHANGE: package broker clients running as standard users must open the pipe without `GENERIC_WRITE`.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🔵 Needs a closer look
It changes a Windows security boundary and connection admission state machine while depending on unreleased client support.
Review effort: Balanced Findings: None
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Standard users can no longer open the package broker pipe with generic write access. Package broker clients must open the pipe with
GENERIC_READ | FILE_WRITE_DATA, asDevolutions.Now.Policy.Clientdoes starting with the release that includes Devolutions/now-libraries#114, and UniGetUI starting with the release that adopts it.The broker keeps a pipe instance listening at all times. When all connection slots are in use, or a user already holds 4 concurrent connections, the broker replies
503 Service Unavailablewith aRetry-Afterheader and aBrokerPausederror instead of leaving the pipe busy. Clients should retry these replies after the indicated delay. At most 16 busy replies are pending at once; beyond that, further clients are disconnected without a reply and should retry the connection later.Do not merge before the
Devolutions.Now.Policy.Clientand UniGetUI releases above are available.BREAKING CHANGE: package broker clients running as standard users must open the pipe without
GENERIC_WRITE.