Skip to content

fix: say so when App Blocker cannot lift a block, instead of leaving it in the list - #2358

Merged
laurentiu021 merged 1 commit into
mainfrom
fix/ifeo-unrecoverable-block-detection
Sep 18, 2026
Merged

laurentiu021 merged 1 commit into
mainfrom
fix/ifeo-unrecoverable-block-detection

Conversation

@laurentiu021

Copy link
Copy Markdown
Owner

Closes #2357

The gap

#2030 stopped the app creating an IFEO block on consent.exe. It did nothing about a machine where an
older build already wrote one — and that is the case that strands someone, because the undo is circular:
UnblockApp writes to HKLM, which needs elevation, which for consent.exe needs the process the block
disabled.

So an affected machine listed consent.exe in App Blocker as an ordinary row beside the deliberate blocks,
with an Unblock button that could never succeed, and nothing anywhere saying why or what to do instead. The
machine looks healthy right up to the first time something asks for administrator rights.

Verified against the tags rather than from memory: v1.65.13 (2026-08-17) did not carry consent.exe in
BootCriticalExecutables; the guard arrived in 7dc0c607 two weeks later.

What this does

Detection only. Nothing is removed automatically, elevated or not. The repair stays the existing Unblock
button behind the confirmation dialog it already uses — an unattended registry write is the user's decision,
not the app's.

  • One rule, not two. The refusal predicate is extracted, so refusing a new block and recognising an old
    one read the same definition. Two copies is precisely how they would come to disagree about which entries
    are dangerous, and getting that wrong fails open: an unmatched name reads as "no problem".
  • The verdict travels with the data. GetBlockedApps stamps it onto each row, so nothing downstream can
    derive it a second, differing way.
  • The row is marked and the tab explains. The banner names what was lost, and the recovery step branches
    on elevation — the button when the app can actually use it, the exact registry value and location when no
    permission prompt can appear to grant it. A single static sentence would have been wrong in one of those
    two states, and the state it would be wrong in is the stranded one.
  • The Dashboard reports it. Red alert routed to the tab, through the existing alert seam. The damage is
    invisible until the first elevation request, which makes the App Blocker tab the last place anyone would
    think to look.
  • The confirmation says what a block looks like. It previously promised only that the program would not
    launch. What you see is a Windows error saying it cannot find SysManager_Blocked.exe, which reads as
    SysManager having broken the computer.

What is deliberately NOT here

A stub in System32. The absent file is the mechanism — a Debugger path that does not resolve is why
the launch fails. Requiring it to exist before writing would refuse every block and kill the feature.
Shipping a real one is a worse trade for an unsigned app, and a stub on a user-writable path invoked by an
elevated target would be a genuine privilege problem.

regedit.exe / cmd.exe / powershell.exe added to the refusal list. The list's own criterion is
"blocking it removes the means of unblocking it", and these do not meet it — the app is the undo path and it
keeps working. Blocking a shell for a household member is also a legitimate use of this tab, and refusing it
would remove a real capability to guard against a recoverable state.

Automatic removal when elevated. It was in the first draft and was dropped: it is a silent registry write
nobody asked for.

Proof

Regression test red before the fix, green after. With the classification forced to false, 4 of the 19
new service tests fail — each reporting IsUnrecoverable = False — and the other 15 stay green, because they
do not depend on that line. Full output was read, not just the summary.

The damage is reproduced the way it actually happened: the value written straight into a redirected hive,
bypassing TryBlockApp, because today's code refuses to write it. A test going through the service could not
set this state up at all — which is exactly why the gap survived, since the only supported path already
refuses and nothing exercised the unsupported one.

The UI test was proved the same way. Mistyping the bound property name in the XAML fails
TheBanner_ShowsTheViewModelsWords_WhenStranded; restoring it passes. It reads the rendered TextBlock
rather than the view model, so a correctly computed property bound to nothing cannot pass.

Negative cases, which matter as much here. An empty IFEO key and a third-party Debugger are both
excluded, so a partially repaired machine raises no permanent unfixable warning about nothing — that key
cannot be removed without administrator rights, so such a warning would never clear.

Dashboard alert tested through a pure classifier, matching every other alert on that page: the scan's only
other statement is a Dispatcher.BeginInvoke, and with no Application.Current that call short-circuits, so
a test driving the scan would assert against an untouched alert and pass while proving nothing.

Check Result
Build, all four projects, Release 0 warnings, 0 errors
dotnet format --verify-no-changes exit 0, empty log
Unit suite, full, local 5845 passed, 0 failed (+30)
New integration tests 3 passed
Pre-existing AppBlocker + Dashboard tests 133 passed
ArchitectureTests (blocking guards) 166 passed

Docs updated in this PR: README App Blocker section, ARCHITECTURE service entry, CHANGELOG. SECURITY.md
already reads 1.111.x, which covers this patch.

…it in the list (#2357)

#2030 stopped the app CREATING an IFEO block on consent.exe. It did nothing for a
machine an older build had already written one on, and that is the case that strands
somebody: unblocking writes to HKLM, which needs elevation, which needs the consent UI
the block disabled. Such an entry sat in the blocked list as an ordinary row beside the
deliberate ones, next to an Unblock button that could never succeed, with nothing saying
why.

Detection only. Nothing is removed automatically, elevated or not — the repair stays the
existing Unblock button behind its confirmation dialog.

- Extract the refusal predicate so one definition serves both refusing a new block and
  recognising an old one, instead of two lists that could drift about which entries matter.
- GetBlockedApps stamps the verdict onto each row, so the flag travels with the data and
  cannot be derived a second, differing way by whoever displays it.
- Mark the row, and show a banner naming what was lost. The recovery step branches on
  elevation: the button when the app can use it, the exact registry value and location
  when no permission prompt can appear to grant it.
- Raise it on the Dashboard as a red alert routed to the tab, because the damage is
  invisible until the first elevation request.
- Say what a block looks like in the confirmation: Windows reports it cannot find
  SysManager_Blocked.exe. That absent file IS the mechanism, and it stays that way —
  a real stub in System32 is a worse trade for an unsigned app, and a stub on a
  writable path invoked by an elevated target would be a privilege problem.

An empty IFEO key and a third-party Debugger are both excluded, so a partially repaired
machine raises no permanent unfixable warning about nothing.

Regression test red before the fix, green after: with the classification forced to false,
4 of the 19 new service tests fail reporting IsUnrecoverable = False, and the other 15
stay green because they do not depend on it. The UI binding test was proved the same way —
mistyping the bound property name fails it.

Tests: 5845 unit (+30), 3 new integration. Whole unit suite green locally, 0 failed.
@laurentiu021
laurentiu021 merged commit 92e4126 into main Sep 18, 2026
6 checks passed
@laurentiu021
laurentiu021 deleted the fix/ifeo-unrecoverable-block-detection branch September 18, 2026 09:07
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.

[Bug]: App Blocker — a block an older build wrote on consent.exe cannot be undone, and nothing says so

1 participant