Skip to content

v2026.09.26.002: a value that is not set no longer stops a check - #7

Open
cdburgess75 wants to merge 1 commit into
fix/programdata-aclfrom
fix/missing-registry-values
Open

cdburgess75 wants to merge 1 commit into
fix/programdata-aclfrom
fix/missing-registry-values

Conversation

@cdburgess75

@cdburgess75 cdburgess75 commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

What the real run showed

A real SYSTEM run on RLG-JANE-PC (Windows 11 Pro 22621, branch fix/os-eol-by-edition = #4, 2026-09-26) logged eight Invoke-SafeBlock skips. Each one dropped the rest of its block:

Block Logged Cause
LLMNR check 'EnableMulticast' cannot be found registry value not set
LAN Manager auth check 'LmCompatibilityLevel' cannot be found registry value not set
CIS Benchmark (stopped after 1.1.1) 'LmCompatibilityLevel' cannot be found registry value not set
PS script block audit 'EnableScriptBlockLogging' cannot be found registry value not set
Credential exposure 'UseLogonCredential' cannot be found registry value not set
Windows Update Cache The property 'Sum' cannot be found Measure-Object over no files
Local admin check error code = 1789 Get-LocalGroupMember could not resolve a member
Defender exclusions Operation failed with the following error: 0x%1!x! Defender off (Datto AV)

Plus antivirus: "Datto AV, Datto AV".

The registry cases all come from one pattern: (Get-ItemProperty $key -Name X -ErrorAction SilentlyContinue).X. Where X is not set, which is Windows' default for most policies, Get-ItemProperty returns nothing, and .X on nothing throws under Set-StrictMode -Version 2. StrictMode has been on since v1.002. So on any box where these values are not set, these checks have never run to the end.

What changes

  • Get-RegistryValue returns the value, or $null when the key or value is absent or unreadable, and never throws. Every read of the old form now uses it. That includes the RDP/NLA check, which did not fail on this box but reads its values the same way; its conversion is not from the logged list. A static AST test fails on any new instance.
  • What "not set" means (ADR 0009): Windows' documented default where there is one, and unknown otherwise. Neither is raised as a finding or scored.
    • LmCompatibilityLevel not set is level 3, Windows' default on 7 / 2008 R2 and later (Microsoft Policy CSP). The Hardening Engine logs it as OK, CIS 2.3 passes, and the -15 rule does not fire. $SK_SetLMAuthLevel still raises only a level that is set below 3.
    • EnableMulticast not set: LLMNR is on, so it logs a warning. It writes only with $SK_DisableLLMNR.
    • UseLogonCredential not set: WDigest is off, so there is no IOC. RunAsPPL and EnableVirtualizationBasedSecurity not set: off, logged as before.
    • NoDriveTypeAutoRun not set: a CIS 2.8 issue in the log (not scored).
    • fDenyTSConnections or UserAuthentication not found: RDP is logged as not checked. It does not raise "NLA not enforced" from a value it never read.
  • Windows Update: Remove-FolderContents returns early on an empty folder. The WU block had stopped wuauserv, bits and UsoSvc before the throw and restarted them after it, so the throw left them stopped. The restart is now in a finally.
  • Local admins: Get-LocalGroupMember -SID S-1-5-32-544 runs first, so a localized group name also works. If it fails, an ADSI WinNT fallback lists the members without resolving them, so an orphaned SID comes back as the SID. If neither answers, the result is unknown and raises no finding.
  • Defender exclusions: when Get-MpPreference fails, the block logs not checked and raises no finding.
  • Antivirus: each product is named once. SecurityCenter2 and the Datto service check could both add Datto AV.

⚠️ Decisions for the reviewer

  1. Script block logging is now opt-in ($SK_EnableScriptBlockLogging = $false). The audit wrote the ScriptBlockLogging policy (EnableScriptBlockLogging = 1) whenever the value was not 1, with no config switch, even over an explicit GPO 0. The read in front of it threw wherever the policy was not set, so the write has never run on those boxes. Fixing the read alone would have turned 4104 logging on across the fleet on the first run. 4104 logs the text of every script block, which can include secrets. I followed the "all auto-remediation defaults to false" rule and the PR v2026.09.25.004: the Intel Engine loads threat intel; every intel match is report-only #5 precedent of not reviving dormant actions. If you want it on by default, flip one default.
  2. Expect new local-admin findings. Boxes where the check was being skipped will now report Local admin: ... (Medium, orphaned SIDs included) and Domain Users as an admin (High). These are the findings any box that could list its admins already raised.

Scoring: no rule changes, and no device's score changes. The LAN Manager rule is rewritten as "set and below 3". Its old $null -eq $lmSc branch could never fire, because the read used -ErrorAction Stop and a catch.

Verification status

  • Parse clean. PSScriptAnalyzer: 0 errors. Warnings are 95 against 93 on the base (+2 AvoidUsingEmptyCatchBlock, in the new fallbacks).
  • All seven tests/Test-*.ps1 pass under pwsh 7.5.
  • New tests/Test-MissingRegistryValues.ps1 has 51 cases. The registry mock returns a key without the value asked for, no key, or an unreadable key. It runs the RDP, LLMNR, LAN Manager, local admin, Defender exclusion, WU cache (real temp folder), script block, credential and CIS blocks and the LAN Manager scoring rule verbatim. It asserts each block runs to its end, and checks every log line, registry write, finding, IOC count, score and service stop/start. Plus the static AST check.
  • tests/Test-EngineScope.ps1: adds lm-not-set (no deduction), lm-2 (-15) and av-duplicates, and fails if CIS stops anywhere except 2.9 with Defender removed.
  • Mutation check: with the old read pattern put back, the new test fails 38 assertions and prints the RLG-JANE-PC messages word for word. Each of 12 other fixes, reverted on its own, fails at least one assertion.
  • NOT yet run on real Windows. Per the repo rule, please do not merge until one real SYSTEM run.

Suggested real-Windows checks (RLG-JANE-PC is ideal)

  1. The log has none of the eight ... skipped lines above. The CIS block reaches CIS Benchmark Lite - N check(s) failed (or all passed).
  2. LAN Manager auth level: not set, Windows default 3 ... (OK) and [CIS 2.3] ... not set, Windows default 3 (OK) appear. security_score does not drop from the LAN Manager rule.
  3. Get-Service wuauserv,bits,UsoSvc after the run: each is back in its pre-run state or running, not left stopped.
  4. Local admin check lists members. With error 1789, it uses the ADSI fallback; check that the names and SIDs look right.
  5. Defender exclusions - not checked with Datto AV. antivirus reads Datto AV once.
  6. HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging is not created (opt-in off).

Not touched: Intel Engine skipped - IntelEngine_PrimarySource, as decided.

Stacked on #6 (base fix/programdata-acl), the top of the open stack. #5 and #6 were opened after #4, so this sits on top of them rather than on #4 directly.

🤖 Generated with Claude Code

A real run on RLG-JANE-PC (Windows 11 Pro 22621, 2026-09-26) logged eight
Invoke-SafeBlock skips, each dropping the rest of its block. Most checks read
(Get-ItemProperty $key -Name X -ErrorAction SilentlyContinue).X; where X is
not set (Windows' default for most policies) that is .X on nothing, which
throws under Set-StrictMode -Version 2. Since v1.002 the LLMNR, LAN Manager
auth, PS script block audit and credential exposure checks never ran to the
end on such a box, and the CIS block stopped after 1.1.1.

- New Get-RegistryValue returns the value or $null and never throws; every
  read of that form uses it, the RDP/NLA check included. Not set means
  Windows' documented default (LmCompatibilityLevel 3, LLMNR on, WDigest off,
  script block logging off), otherwise unknown; neither is a finding or a
  deduction (ADR 0009).
- Script block logging is opt-in: the audit's unconditional HKLM policy write
  never ran where the policy was unset, and fixing the read would have turned
  4104 logging on fleet-wide. New $SK_EnableScriptBlockLogging, default $false.
- Remove-FolderContents returns early on an empty folder ('Sum' of nothing
  threw); the Windows Update block restarts wuauserv/bits/UsoSvc in a finally,
  so an empty cache no longer leaves them stopped.
- Local admins: Get-LocalGroupMember by SID S-1-5-32-544, with an ADSI WinNT
  fallback for error 1789 (an unresolvable member); neither is unknown.
- Defender exclusions: Get-MpPreference failing with Defender off (0x%1!x!)
  is logged as not checked, no finding.
- Antivirus names each product once ('Datto AV, Datto AV').
- No scoring rule changes.

New tests/Test-MissingRegistryValues.ps1; tests/Test-EngineScope.ps1 adds
lm-not-set, lm-2 and av-duplicates. NOT yet run on real Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

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