Skip to content

Align package broker requests with the broker's request validation - #5467

Merged
Gabriel Dufresne (GabrielDuf) merged 18 commits into
mainfrom
cbenoit-package-broker-request-alignment
Oct 8, 2026
Merged

Gabriel Dufresne (GabrielDuf) merged 18 commits into
mainfrom
cbenoit-package-broker-request-alignment

Conversation

@CBenoit

@CBenoit Benoît Cortier (CBenoit) commented Oct 2, 2026 •

Copy link
Copy Markdown
Member
  • I have read the contributing guidelines, and I agree with the Code of Conduct.
  • Have you checked that there aren't other open pull requests for the same changes?
  • Have you tested that the committed code can be executed without errors?
  • Have you confirmed that this issue is caused by UniGetUI itself, and not by the package manager or the package involved?
    If the same issue can be reproduced outside UniGetUI with the relevant package manager or with the package itself, please report it there first. UniGetUI should only be used to track issues that are specific to UniGetUI's behavior or integration.

Aligns the Devolutions Agent package broker integration with the broker's request validation, and makes broker outcomes understandable to the user.

Send resolved package versions to the package broker (BrokerRequestBuilder)

  • The broker evaluates version conditions against the version in the request, so UniGetUI now sends the version it already knows:
    • the version the user selected;
    • otherwise the listed version, for an install;
    • otherwise the target version, for an update.
  • No version is sent:
    • for uninstalls;
    • for Scoop and vcpkg;
    • for pre-release installs;
    • for managers without broker version rules;
    • when the known version would not be accepted for that manager.
  • A version saved for installs never blocks an update or an uninstall.
  • Fields the broker refuses for a role or a manager are left out:
    • pre-release, hash-check skip and architecture for uninstalls;
    • architecture for Scoop updates;
    • the source URL for managers that identify their source by name;
    • blank custom parameters.
  • Scope and architecture are sent when chosen.
  • The PowerShell -AllowClobber retry is not sent, because the broker accepts no PowerShell parameter. The command conflict is reported instead.
  • "Update all" already creates one operation per package.

Validate package broker request fields (BrokerRequestValidator, new)

  • Mirrors the broker's rules, with localized explanations:
    • API-wide identifier, version and custom-parameter bounds;
    • command-line safety guards;
    • per-manager identifier rules: Chocolatey NuGet ids, Scoop single names, PowerShell names without wildcards, npm/Bun registry names, crates, pip distributions;
    • per-manager version allowlists, including ranges and canonical SemVer for Bun;
    • source names;
    • characters refused by batch-script managers;
    • install location as a plain local drive path;
    • Scoop custom parameters, and managers that accept no custom parameters;
    • per-manager option support: scope, architecture, pre-release, interactive, hash check, install location, uninstall-previous;
    • elevation-dependent rules: per-user managers, and pre/post commands on elevated or machine-scope operations.
  • The broker stays the authority. A request it would reject is not sent, and the failure lists every problem.
  • The installation options dialog previews the same problems live: it dry-runs the request builder after applying the manager's elevation requirements. Changes are announced through the shared accessibility live region.

WinGet custom installer arguments

  • The dialog warns when --override/--custom, or any custom argument on an elevated operation, goes through the Devolutions Agent. These arguments reach an installer running with administrator rights, and the organization's policy may block them.

Explain package broker policy decisions (BrokerFailureDescriber, new)

  • Specific messages for:
    • policy denial, with rule id and reason;
    • validation failures, with the broker's message and details;
    • paused (no valid policy) versus busy (after the client's retries);
    • Forbidden, keeping the broker's reason (for example policy validity);
    • administrator required;
    • request too large, unsupported option, timeout, invalid response.
  • The capabilities request is made up front. If the broker refuses this client (401/403, for example an unsigned build), the operation explains why and raises BrokerUnavailable, which now carries the specific title.
  • In the policy editor, a save refused because an elevated administrator is required, or because the client is not authorized, gets a specific message.

The pipe transport, server verification and retry handling are unchanged. New strings were added to lang_en.json only.

Validation (from src/):

  • dotnet test UniGetUI.PackageEngine.Tests/UniGetUI.PackageEngine.Tests.csproj /p:Platform=x64 --filter "FullyQualifiedName~Broker|FullyQualifiedName~PackageOperationsTests": passed (448 for the Windows target, 436 for net10.0).
  • dotnet test UniGetUI.PackageEngine.Tests/UniGetUI.PackageEngine.Tests.csproj /p:Platform=x64 (full): passed for net10.0. On the Windows target, only PowerShellOperationLauncherTests.TheScopeRetryMarkersSurviveTheLauncher failed; it also fails on main in my environment.
  • dotnet test UniGetUI.Tests/UniGetUI.Tests.csproj /p:Platform=x64: passed (553).
  • dotnet format whitespace and dotnet format style UniGetUI.Windows.slnx --no-restore --verify-no-changes on the changed files: passed.
  • pwsh ./scripts/translation/Verify-Translations.ps1: passed.

Not tested: end to end against a running Devolutions Agent with the updated broker, and the dialog notices in the running app.

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

🟡 Changes recommended

Version handling and live option validation still diverge from the requests ultimately sent to the broker.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
What changed in this PR

Aligns broker requests with stricter validation and improves user-facing broker failure reporting.

Changes:

  • Adds broker request validation and resolved-version handling.
  • Provides detailed policy, authorization, and transport errors.
  • Adds live broker warnings to installation options.
File Description
PolicyEditorStructuredInputGuardTests.cs Tests authorization messages.
PackageOperationsTests.cs Tests broker operation failures and versions.
BrokerRequestBuilderTests.cs Tests request validation rules.
BrokerFailureDescriberTests.cs Tests failure descriptions.
PackageOperations.cs Integrates capability probing and detailed failures.
BrokerRequestValidator.cs Implements broker-compatible validation.
BrokerRequestBuilder.cs Resolves versions and validates requests.
BrokerFailureDescriber.cs Produces localized failure explanations.
InstallOptionsControl.axaml Displays broker notices.
PolicyEditorDialogViewModel.cs Clarifies policy authorization failures.
InstallOptionsViewModel.cs Computes live broker warnings.
lang_en.json Adds localized English strings.

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

Comment thread src/UniGetUI.Avalonia/ViewModels/DialogPages/InstallOptionsViewModel.cs Outdated
Comment thread src/UniGetUI.PackageEngine.AgentBroker/BrokerRequestBuilder.cs
Comment thread src/UniGetUI.PackageEngine.AgentBroker/BrokerRequestValidator.cs

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

Pre-release updates can omit known versions, and several broker warnings or authorization messages can be stale or misleading.

Review effort: Balanced
Findings: None

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

In code that hasn't changed since last review

Medium severity Prevent stale asynchronous refresh results from overwriting current state

src/​UniGetUI.Avalonia/​ViewModels/​DialogPages/​InstallOptionsViewModel.cs:552

These live results can be overwritten by an older refresh. Every edit starts a fire-and-forget RefreshCommandPreviewAsync, and each call performs multiple asynchronous loads; if a previous call completes after a later one, it assigns validation text for stale options and may leave the dialog showing or hiding the wrong broker warning. Add a monotonically increasing refresh generation or cancellation token and only publish results from the latest invocation.

Medium severity Include metadata-driven elevation in custom argument warnings

src/​UniGetUI.Avalonia/​ViewModels/​DialogPages/​InstallOptionsViewModel.cs:581

This warning only treats the checkbox value as elevation, but the broker path also elevates when WinGet metadata sets package.OverridenOptions.RunAsAdministrator in ApplyElevationRequirements (PackageOperations.cs:392-400, WinGetPkgOperationHelper.cs:217-275). Because command preview applies that metadata before this method runs, an installer that automatically requires elevation can still receive arbitrary custom arguments without the promised warning. Match the same elevation predicate used by the operation.

Medium severity Restrict pre-release suppression to install operations

src/​UniGetUI.PackageEngine.AgentBroker/​BrokerRequestBuilder.cs:189

Pre-release suppression currently applies to updates as well as installs. InstallOptions.PreRelease can remain enabled on an update, so this returns null instead of sending the known NewVersionString, contrary to the intended exception for pre-release installs only. Restrict this branch to OperationType.Install.

Medium severity Preserve authorization failure details in broker error dialogs

src/​UniGetUI.PackageEngine.Operations/​PackageOperations.cs:565

The new authorization failure is raised through BrokerUnavailable, whose only production subscriber still opens SimpleErrorDialog with the hard-coded title “Agent broker unavailable” (AvaloniaBootstrapper.cs:140-150). For a 401/403 the agent is available but rejecting this client, so the immediate user-facing dialog loses the specific authorization title computed above and remains misleading. Carry the full failure description (or otherwise pass its title) through this notification path.

@CBenoit

Copy link
Copy Markdown
Member Author

Addressed the four 'previously missed' review notes in 1e2b9af: (1) the options dialog publishes only the latest broker check (refresh generation); (2) the WinGet custom-arguments warning uses the operation's elevation predicate (package requirement or checkbox, respecting ProhibitElevation); (3) the pre-release exception now only applies to installs, so updates keep their target version; (4) BrokerUnavailable now carries the full title and message, so a refused client shows the authorization title instead of 'Agent broker unavailable'.

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

Source mapping, PowerShell parameters, and paused-broker responses remain inconsistent with the broker contract.

Review effort: Balanced
Findings: None

Previously missed (3)

In code that hasn't changed since last review

Medium severity Map structured BrokerPaused responses to 409 instead of busy 503

src/​UniGetUI.PackageEngine.AgentBroker/​BrokerFailureDescriber.cs:82

BrokerPaused is mapped to HTTP 503 by the broker server, including the “active policy is unavailable” path. Because this branch also accepts ErrorCode.BrokerPaused, a real paused response is always described as “busy” and the BrokerPaused switch case below is unreachable; the 409 test does not match the wire contract. Reserve this branch for an unstructured 503 so structured paused responses get the paused explanation.

Medium severity Validate or strip source URLs before broker submission

src/​UniGetUI.PackageEngine.AgentBroker/​BrokerRequestValidator.cs:56

This validates only the source name, but BrokerRequestBuilder still forwards package.Source.Url. The broker rejects non-null source URLs for Chocolatey, npm, pip, PowerShell, and PowerShell 7, while UniGetUI's default sources for all five managers have URLs, so normal requests for these managers still pass this preflight and are then rejected by the broker. Build a name-only RequestSource for those managers or mirror the broker's URL rules before submission.

Medium severity Validate PowerShell custom and broker-added parameters

src/​UniGetUI.PackageEngine.AgentBroker/​BrokerRequestValidator.cs:80

PowerShell custom parameters still bypass this preflight even though the broker's PowerShell command builder rejects every non-empty custom parameter. This also affects the automatic -AllowClobber retry because BrokerRequestBuilder appends it after Validate returns, so that retry is sent and rejected. Align the PowerShell parameter mapping with the broker and ensure broker-added parameters are validated too.

@CBenoit

Copy link
Copy Markdown
Member Author

Addressed the three 'previously missed' notes in 75688c3: (1) a structured BrokerPaused response is described as paused unless its message reports the busy state, and only an unstructured 503 is treated as busy; tests now use the 503 wire status. (2) The source URL is omitted for managers that identify their source by name (Chocolatey, PowerShell, PowerShell 7, npm, Bun, Cargo, .NET Tool, pip, vcpkg). This also covers the pip change from #5466, merged from main. (3) The custom parameters that are actually sent, including the AllowClobber retry, are validated, and they are refused with an explanation for managers whose broker command builder accepts none.

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

🟡 Changes recommended

The asynchronous broker notices do not use the established accessibility announcement service, and validator documentation conflicts with its behavior.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)

Comment thread src/UniGetUI.PackageEngine.AgentBroker/BrokerRequestValidator.cs

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

Request validation still permits broker-rejected identifiers, parameters, and Bun versions, while irrelevant saved versions can block updates or uninstalls.

Review effort: Balanced
Findings: None

Resolved since last review (2)
Previously missed (4)

In code that hasn't changed since last review

Medium severity Version validation incorrectly blocks update and uninstall

src/​UniGetUI.PackageEngine.AgentBroker/​BrokerRequestBuilder.cs:190

ResolveVersion omits versions for updates and uninstalls, but Build still validates options.Version before calling it. A saved install-only value such as /latest therefore prevents an uninstall/update even though that value would not be sent. Gate both earlier version guards on role == Install, or validate only the resolved version.

Medium severity Missing universal PackageIdentifier validation

src/​UniGetUI.PackageEngine.AgentBroker/​BrokerRequestValidator.cs:246

This manager-specific switch omits the broker API's universal PackageIdentifier constraints. For example, a WinGet identifier longer than 256 characters passes IsOptionSafeIdentifier and this method, but the broker rejects it while deserializing the request; non-ASCII identifiers have the same gap for several managers. Apply the API-wide length/ASCII allowlist before the manager-specific rules so these requests fail locally with the promised explanation.

Medium severity Missing custom-parameter length validation

src/​UniGetUI.PackageEngine.AgentBroker/​BrokerRequestValidator.cs:321

The broker's custom-parameter type requires each value to contain 1–512 characters, but this validation only checks manager-specific content. A WinGet or Scoop argument longer than 512 characters therefore passes locally and is sent, only to be rejected during broker deserialization. Enforce the shared bounds before the manager-specific checks (and either omit or reject empty entries).

Medium severity SemVer validation accepts broker-invalid versions

src/​UniGetUI.PackageEngine.AgentBroker/​BrokerRequestValidator.cs:514

This regex is looser than the broker's SemVer parser: it accepts numeric prerelease identifiers with leading zeroes (for example 1.2.3-01) and arbitrarily large numeric components, both of which the broker rejects. As a result, Bun requests advertised as valid locally still fail remotely. Use SemVer validation with the same canonical numeric rules and bounds as the broker.

@CBenoit

Copy link
Copy Markdown
Member Author

Addressed the four 'previously missed' notes in b26f7c3: (1) the saved version is only checked, and only sent, for installs, so a stale value can no longer block an update or an uninstall; (2) package identifiers are checked against the broker API's universal 256-byte length and ASCII character set before the manager-specific rules; (3) custom parameters are bounded to 512 bytes, and empty entries are dropped from the request; (4) Bun versions follow canonical SemVer: no leading zeros in numeric pre-release identifiers, and release components must fit 64 bits.

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

🟡 Changes recommended

Whitespace-only custom parameters can bypass local rejection and still be sent to the broker.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread src/UniGetUI.PackageEngine.AgentBroker/BrokerRequestBuilder.cs Outdated

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

🟡 Changes recommended

Inferred versions are incorrectly omitted for several supported managers, preventing version-based broker policies from evaluating them.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)

Comment thread src/UniGetUI.PackageEngine.AgentBroker/BrokerRequestBuilder.cs

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

🟡 Changes recommended

Validation failures can instruct users to edit installation options even when the rejected package identifier or source cannot be changed there.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)

Comment thread src/UniGetUI.PackageEngine.AgentBroker/BrokerFailureDescriber.cs Outdated

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

The live options validation omits request-builder safety checks, allowing some operations to appear valid before failing.

Review effort: Balanced
Findings: None

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

In code that hasn't changed since last review

Medium severity Preview bypasses request builder option-safety validation

src/​UniGetUI.Avalonia/​ViewModels/​DialogPages/​InstallOptionsViewModel.cs:557

The live check bypasses the request builder's option-safety guards. For example, a WinGet package ID such as -foo or /foo produces no issue here (CheckPackageId allows those characters), but BrokerRequestBuilder.Build rejects it through CoreTools.IsOptionSafeIdentifier; the dialog therefore stays clear and the operation fails only after it starts. Move those guards into the shared validator or include the builder's validation outcome in this preview so it reports every request-blocking problem.

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

🟡 Changes recommended

Live validation omits Cargo close-app entries, allowing an operation to fail without the promised advance warning.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

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

It duplicates security-sensitive external broker rules and lacks end-to-end validation against the updated Agent.

Review effort: Balanced
Findings: None

Resolved since last review (1)

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

🟡 Changes recommended

PowerShell architecture and install-location constraints remain inconsistent with the broker’s advertised capabilities.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread src/UniGetUI.PackageEngine.AgentBroker/BrokerRequestValidator.cs
Send the resolved package version to the package broker: the version the
user selected, else the listed version for installs and the target version
for updates, omitting versions the broker does not accept for the manager
and never sending one for uninstalls or for managers that pin their own.

Validate package broker request fields before sending them (install
location, versions, package identifiers, source names, custom parameters,
architecture) and explain each problem in the operation failure and in the
installation options dialog.

Warn in the installation options dialog when WinGet custom installer
arguments go through the Devolutions Agent.

Explain package broker policy decisions and errors: show the denying rule
and reason, validation messages, paused and busy states, and authorization
failures, including when the broker refuses the capabilities request and
when a policy change requires an elevated administrator.

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

Share the brokered install location resolver with the installation options dialog, validate versions of managers with broker version rules only against those rules, and bound Bun version length.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Publish only the latest options-dialog broker check, use the operation's elevation predicate for the WinGet custom arguments warning, keep the target version for pre-release updates, and pass the specific failure title to the broker notification dialog.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Omit the source URL for managers that identify sources by name, validate the custom parameters that are actually sent (including the PowerShell AllowClobber retry) and refuse them for managers that accept none, and tell a busy broker apart from a paused one.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Route changed broker validation problems (assertive) and custom-argument warnings (polite) through AccessibilityAnnouncementService, and describe the managers that refuse custom parameters as an explicit list.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Check package identifiers, custom parameters and versions against the API-wide length and character rules, use canonical SemVer rules for Bun, drop empty custom parameters, and ignore a saved install version for updates and uninstalls.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The dialog now dry-runs the request builder, so the command-line safety guards are reported alongside the field rules.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Move the option-safety and shell-identifier guards into the request validator so they are reported as localized issues, in the operation failure and in the options dialog.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Report elevated operations for managers that run per user and pre/post commands on elevated or machine-scope operations before sending them, sharing the elevation predicate with the operation. Describe a generic Forbidden response with the broker's reason instead of asking for administrator rights.

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

A 401 or 403 on the capabilities request now reports that the broker does not accept this client, without reclassifying execution-time Forbidden responses. The options dialog re-checks broker rules when a pre/post command changes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The broker accepts no PowerShell custom parameter, so the AllowClobber retry is no longer sent: the operation explains the command conflict instead. The options dialog orders whole refreshes (command preview included), and length limits are reported in bytes, as the broker counts them.

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

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Report options the broker's command builder refuses for the manager (machine or user scope, architecture, pre-release, interactive, hash-check skip, install location, uninstall-previous, Cargo actions), stop sending pre-release, hash-check skip and architecture where the role ignores them, and check source names as sent.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@CBenoit
Benoît Cortier (CBenoit) force-pushed the cbenoit-package-broker-request-alignment branch from 2cd4ff1 to 634eb7c Compare October 8, 2026 14:39
@GabrielDuf
Gabriel Dufresne (GabrielDuf) merged commit 92d5e4f into main Oct 8, 2026
6 checks passed
@GabrielDuf
Gabriel Dufresne (GabrielDuf) deleted the cbenoit-package-broker-request-alignment branch October 8, 2026 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants