fix: Bulk Installer winget binary-planting LPE + search argument injection#1420
Merged
Conversation
…ction
MarkInstalledAppsAsync (auto-runs on tab open) and the package search each built
their own ProcessStartInfo { FileName = "winget", UseShellExecute = false } with NO
WorkingDirectory. With UseShellExecute=false, Win32 CreateProcess searches the
calling process's own directory before the real winget App Execution Alias, so an
attacker-planted winget.exe beside SysManager's portable .exe (often run from a
user-writable folder, sometimes elevated) would run with the app's privileges.
Separately, the search argument was $"search \"{query}\" …" built from the
unvalidated search box — a double-quote could break out and inject winget args.
Both calls now route through BulkInstallerService (IPowerShellRunner seam), which
launches winget with WorkingDirectory pinned to System32 (matching every other
winget call and removing the CWD from the search order), and the search query is
sanitized (quotes + control chars stripped) before interpolation. Also brings the
two calls into Gate-ARCH conformance (external process launches route through the
single runner seam, not a bespoke ProcessStartInfo). The obsolete
SearchWingetPackages VM method is removed.
Source: fresh multi-agent ultra-audit (security-deep), P1 (LPE) + P3 (injection),
confirmed by 3 adversarial verifier lenses (isReal=true, high confidence).
Regression tests: SanitizeQuery_StripsQuotesAndControlChars,
SanitizeQuery_EmptyOrUnusable_ReturnsEmpty, SearchAsync_RoutesThroughRunner_
WithSanitizedQuery, SearchAsync_EmptyAfterSanitize_NeverRunsProcess,
ListInstalledAsync_RoutesThroughRunner.
…e version/changelog conflicts)
The two failing assertions expected 'foo calc' but SanitizeQuery only strips double-quotes and control chars — not '&' — so 'foo" & calc "' correctly yields 'foo & calc' (the & is harmless inside a quoted, shell-less winget argument; the security property is that the embedded quotes can't break out of the token). Corrected the [InlineData] expectation and the SearchAsync arg assertion (now checks the query is wrapped in exactly one quote pair). Hand-verified all five Theory rows + the empty cases against the real logic before pushing.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Two fresh ultra-audit findings (security-deep) in
BulkInstallerViewModel, both confirmed by 3 adversarial verifier lenses (isReal=true, high confidence):The defects
MarkInstalledAppsAsync(runs automatically on tab open) and the package search each built their ownProcessStartInfo { FileName = "winget", UseShellExecute = false }with noWorkingDirectory. WithUseShellExecute=false, Win32CreateProcesssearches the calling process's own directory before resolving the real winget App Execution Alias — so an attacker-plantedwinget.exebeside SysManager's single portable .exe (often run from a user-writable folder like Downloads, sometimes elevated) would execute with the app's privileges. Separately, the search argument was$"search \"{query}\" …"built from the unvalidated search box — the one process-launch trust boundary in the app without input validation — so a query containing a double-quote could break out and inject extra winget arguments.The fix
Both winget calls now route through
BulkInstallerService(theIPowerShellRunnerseam):WorkingDirectorypinned to System32, removing the CWD from theCreateProcesssearch order — matching every other winget call in the app and closing the binary-planting vector.ProcessStartInfo. The obsoleteSearchWingetPackagesVM method is removed.Regression tests
SanitizeQuery_StripsQuotesAndControlChars/SanitizeQuery_EmptyOrUnusable_ReturnsEmpty— the injection guard.SearchAsync_RoutesThroughRunner_WithSanitizedQuery— dangerous quotes are gone from the argument string that reaches winget; the call goes through the seam.SearchAsync_EmptyAfterSanitize_NeverRunsProcess— a quotes-only query launches nothing.ListInstalledAsync_RoutesThroughRunner— the list path goes through the seam (WorkingDirectory pinned).Checks