Skip to content

Keep an elevated window's native libraries in a folder only administrators can change - #52

Merged
donislawdev merged 2 commits into
mainfrom
fix/security-s1
Oct 6, 2026
Merged

donislawdev merged 2 commits into
mainfrom
fix/security-s1

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

What changes

An elevated window no longer loads WPF's native libraries from a folder other accounts can write to (security report S-1).

  • Before: the single-file window unpacked five native libraries under the temporary folder and an elevated window loaded them from there. The .NET host sets no permissions on that folder and reuses it without comparing anything.
  • Now: an elevated window that is a single-file bundle checks in its own Main, before anything of WPF exists, where the runtime loads native libraries from. Anywhere but <drive of Windows>\ProgramData\BetterWindowsServices it prepares that folder (protected list: SYSTEM and Administrators full control, OWNER RIGHTS read, owner Administrators), starts itself once more with DOTNET_BUNDLE_EXTRACT_BASE_DIR pointing there, and ends. Every elevated start examines the folder level by level down to the files. A folder that cannot be made, cannot be read, can be changed by others or is a link refuses the start with a user32 box naming the folder and what to do.
  • Without elevation, or outside a single-file bundle (bin, tests), nothing changes and nothing on disk is touched.

How it is built

  • Bws.Core: Unpacking (the pure rule) and AdministratorsFolder (the descriptor rule, making and examining the folder).
  • Bws.Gui: Program.Main of its own, App.xaml as a Page, the restart and the box in Elevation.cs (already the one file allowed to start a process and already outside coverage).
  • One [UnconditionalSuppressMessage] for IL3000, counted in AnalyzerRuleGuards: the bundle is recognised by its empty assembly location, the one documented fact for it.
  • MessageBox added to the window's Win32 inventory and to OutboundRegisters.
  • The dead-code scan learns that the runtime calls a static Main.

Checked

  • Narrow test run: core 67/67, window 36/36, architecture 186/186, prose 25/25, site 26/26.
  • Mutation run of the 23 new registry entries: 23 caught.
  • Published single-file window, elevated: the first process ends with code 0 and the window loads wpfgfx_cor3.dll from the ProgramData folder, whose list is the protected one above. Without elevation it loads from the temporary folder in one process. The build from before this change loads from the temporary folder both ways.
  • The status line of the elevated window does not name the variable the window set for itself, while the previous build started with that variable does.

Not checked

  • The refusal box on a live window - only the sentences are tested, and no broken folder was prepared on a real machine.
  • A token without a linked one (built-in Administrator, UAC off) and Windows Server.
  • The time to Main of the real window (the 64-67 ms second start was measured on a smaller probe).

🤖 Generated with Claude Code

donislawdev and others added 2 commits October 6, 2026 19:06
…s can change when it runs elevated

An elevated window whose native libraries were unpacked under the temporary folder now starts
once more with them in <drive of Windows>\ProgramData\BetterWindowsServices, a folder made with a
protected list (SYSTEM and Administrators full control, OWNER RIGHTS read) and examined at every
elevated start, level by level and file by file. A folder that cannot be made, cannot be read,
can be changed by others or is a link refuses the start with a user32 box that says which folder
and why. Without elevation, or outside a single-file bundle, nothing changes.

The window has a Main of its own and App.xaml is a Page, because constructing the WPF application
already loads the first library from the folder. The rule lives in Bws.Core (Unpacking,
AdministratorsFolder), the restart and the box in Elevation.cs. One IL3000 suppression, counted
in AnalyzerRuleGuards, recognises the bundle by its empty assembly location. The dead-code scan
learns that the runtime calls a static Main.

Security report S-1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README, the FAQ in both languages and SECURITY.md said the window loads the libraries it
unpacks from the account's temporary folder and suggested setting DOTNET_BUNDLE_EXTRACT_BASE_DIR by
hand. An elevated window now keeps them in ProgramData\BetterWindowsServices, checks that folder at
every start and refuses to start when it cannot be trusted, so the advice is replaced by what the
program does and what it costs: a second start and about 8 MB per version left in that folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 6, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository UI (base), Organization UI (inherited)
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 5e1a7c20-925f-435e-bd28-0b72972c1549
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@donislawdev
donislawdev merged commit 7e7579d into main Oct 6, 2026
8 checks passed
@donislawdev
donislawdev deleted the fix/security-s1 branch October 6, 2026 17:37
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.

1 participant