Skip to content

Quarantine registry-value findings (HKLM, HKCU, ...)#28

Merged
OpenSource-For-Freedom merged 1 commit into
mainfrom
patch
Jun 1, 2026
Merged

Quarantine registry-value findings (HKLM, HKCU, ...)#28
OpenSource-For-Freedom merged 1 commit into
mainfrom
patch

Conversation

@OpenSource-For-Freedom
Copy link
Copy Markdown
Owner

Summary

Follow-up to #27 (directory-shaped quarantine). scanner/browser_scanner.py emits a fair number of findings whose path is a registry value — browser-hijack run-keys, native-messaging hosts, side-loaded extension registrations. The vault couldn't contain them: File.Exists is false for HKLM\…, so the value stayed in the live registry and the scan report ended with Quarantine skipped (path not on disk).

What changed

QuarantineService.cs

  • IsRegistryPath(string?) matches HKLM, HKCU, HKCR, HKU (and the long-form HKEY_* variants) on the first segment.
  • QuarantineFile checks IsRegistryPath before NormalizeFullPath — otherwise Path.GetFullPath would mangle the string into a CWD-relative file path.
  • QuarantineRegistryEntry parses the path, opens the parent key writable, and looks for a value with the matching last-segment name. If found, GetValueKind + GetValue(DoNotExpandEnvironmentNames) capture the value verbatim — important for REG_EXPAND_SZ entries that legitimately use %SystemRoot% etc. (without DoNotExpandEnvironmentNames, the restore would write a fully-expanded path that breaks on a different machine state).
  • The export — hive, subkey, value name, kind, payload — is written as <leaf>.reg.json into the vault. DeleteValue runs only after the export is durable, so a crash mid-quarantine leaves the live registry intact.
  • Restore for an IsRegistry record reads the JSON, opens (or creates) the subkey, and writes the captured kind + payload back via SetValue. The export file is deleted on success.
  • DeleteFromVault is unchanged — the export is just another file in the vault directory.
  • QuarantineRecord gains an IsRegistry flag, mutually exclusive with IsDirectory. Old records on disk default both to false and continue to behave as files.

AutomatedResponseService.cs — the existence gate now accepts registry paths alongside files and directories:

if (!File.Exists(containmentPath)
    && !Directory.Exists(containmentPath)
    && !QuarantineService.IsRegistryPath(containmentPath))
{
    report.Messages.Add($"Quarantine skipped (path not on disk): {containmentPath}");
    continue;
}

Scope choice

Value-level containment covers every shape browser_scanner.py actually emits today. Whole-key (subtree) quarantine would need recursive export + ACL preservation — a separate feature. If a path resolves to a subkey rather than a value, QuarantineRegistryEntry throws NotSupportedException with "Registry KEY quarantine is not yet implemented" instead of silently failing, so the operator sees an actionable error.

Test plan

  • Plant a synthetic run-key value (HKCU\Software\Microsoft\Windows\CurrentVersion\Run, e.g. WraithTestC:\does-not-exist.exe) and rescan. Expect the value to disappear from regedit and a WraithTest.reg.json entry to appear in the vault.
  • Restore the entry from the Quarantine window. Expect the value to reappear in regedit with the original kind (REG_SZ) and data.
  • Repeat with a REG_EXPAND_SZ value using %SystemRoot%\notepad.exe. Restore must preserve %SystemRoot% literally rather than expanding to the absolute path.
  • Try a path that resolves to a subkey (no matching value name). Expect the scan log to surface NotSupportedException: Registry KEY quarantine is not yet implemented instead of a silent skip.
  • Try an HKLM path under an unelevated build (manually disable the manifest). Expect a clear UnauthorizedAccessException rather than a corrupted half-quarantine.

https://claude.ai/code/session_01LJscMnf6U5HycjwB89m1zU


Generated by Claude Code

scanner/browser_scanner.py emits a fair number of findings whose
'path' is a registry value (browser hijack run-keys, native-messaging
hosts, side-loaded extension registrations). The vault couldn't
contain them — File.Exists is false for HKLM\... — so the value
stayed in the live registry and the scan report ended with
'Quarantine skipped (path not on disk)'.

QuarantineService now handles registry-value paths alongside files
and directories:

- IsRegistryPath detects the HKLM/HKCU/HKCR/HKU (and HKEY_* long
  form) prefixes before NormalizeFullPath gets a chance to mangle
  the string into a CWD-relative path.
- QuarantineRegistryEntry parses the path, opens the parent key
  writable, and checks whether the last segment is a value name on
  that key. If so, GetValueKind + GetValue(DoNotExpandEnvironmentNames)
  capture the value verbatim. The export — hive, subkey, value name,
  kind, payload — is written as <leaf>.reg.json into the vault, then
  DeleteValue removes it from the live registry.
- Restore reads the JSON export, opens (or creates) the subkey,
  and SetValue restores the captured kind + payload. The JSON
  export file is deleted after a successful restore.
- DeleteFromVault works unchanged — the export is just another file.
- QuarantineRecord gains an IsRegistry flag (mutually exclusive with
  IsDirectory). Old records on disk default to false and continue
  to behave as files.
- AutomatedResponseService gate now also accepts
  QuarantineService.IsRegistryPath() inputs.

Value-level containment is enough for every shape browser_scanner
actually emits today. Whole-key (subtree) quarantine surfaces a
NotSupportedException with a clear message if a path resolves to a
subkey rather than a value — left as a follow-up because it requires
recursive export and ACL handling.
Copilot AI review requested due to automatic review settings June 1, 2026 03:16
@OpenSource-For-Freedom OpenSource-For-Freedom merged commit b436ebf into main Jun 1, 2026
8 of 9 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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.

2 participants