Skip to content

On Windows the log-hash key is held to an allowlist, so a key anyone beyond SYSTEM, Administrators and the service can read is refused (#4028) - #4044

Merged
erikdarlingdata merged 4 commits into
devfrom
fix/4028-key-file-allowlist
Sep 23, 2026
Merged

erikdarlingdata merged 4 commits into
devfrom
fix/4028-key-file-allowlist

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Closes #4028.

Why

#4020's review (Low 3): on Windows the log-hash key was trusted through the credential check it shares with the role credential files. That check refuses only a file Users, Authenticated Users or Everyone can read, so INTERACTIVE, Domain Users or one named user holding ReadData passed it. Whoever can read the key can test guesses at every literal the keyed hashes hide.

What changes

  • New DarlingFileSecurity.AccessBeyondTrusted(path): an allowlist over the trusted-owner set (SYSTEM, Administrators, the service account). It names every other account holding ANY Allow entry, or reports a DACL it can't read.
  • DarlingLogHashKeyFile.Load refuses a key with any such reader, after the shared check. Creating a key verifies against the same allowlist, so a key is never written that the next start would refuse.
  • The credential files keep the denylist their existing DACLs were built against. darling.json is read by INTERACTIVE by design, so this can't newly refuse an install's credentials.

Round-1 review fixes (cd02713)

  • Medium: the check asked only who could READ. An account with WriteData could plant a key of its own. Any local user can make a valid one, because the DPAPI is LocalMachine and the entropy is in the source. An account with ChangePermissions or TakeOwnership could grant itself the read. Now any Allow entry for anyone beyond the three trusted accounts refuses the key, whatever it grants. The service writes only those three entries.

  • Low: the key was checked by path, then read by path. Whoever controls a pre-existing darling-keys could swap the file in between. DarlingFileSecurity.OpenForServiceOnlyRead opens the key once, without following a link at its name and sharing read alone, so nobody can rename, delete, replace or write it while it is held. Through that handle it checks:

    • reparse point or directory;
    • hard links (more than one name is refused);
    • the owner;
    • the allowlist.

    The read then uses the same handle, bounded to 1 KB. A key this service writes is about 350 bytes. This closes the swap without the directory allowlist, which stays out of scope below.

  • Low: the Unix half of Log-hash key trust: check who owns the key directory and file on Unix, and allowlist the directory's DACL on Windows (#4020 review, Low) #4028. This stays out of scope by the ruling; see below. The reviewer's FIFO point is the same case.

Not done, and closed with this

Test plan

  • New AKeyAnyoneBeyondTheServiceCanRead_IsRefused_OnWindows. A real key, with an INTERACTIVE read grant added, is refused with INTERACTIVE named; the file is untouched; and the shared denylist alone would have loaded it.
  • Red-watch: without the load check, the test fails.
  • Review fixes, each red-watched by reverting it:
    • AKeyAnyoneBeyondTheServiceHoldsAnyRightTo_IsRefused_OnWindows: WriteData, AppendData, ChangePermissions, TakeOwnership and Delete. All 5 fail with the ReadData-only filter restored.
    • AHeldKey_CannotBeRenamedDeletedOrWritten_UntilItIsReleased_OnWindows: fails when the handle shares write and delete.
    • AHardLinkedKey_IsRefused_OnWindows: fails without the link-count check.
    • AnOversizedKey_IsRefused_OnWindows: fails without the 1 KB bound.
  • After merging dev, all green, 0 warnings: PgLogHashKey* 26, DarlingFileSecurity* 17, *HardenFiles* 19, DocCommentHygiene 77, DarlingManagedRoles* 27.
  • PgLogHashKey*, DarlingFileSecurityTests, DarlingHardenFilesVerbTests, DarlingStoreLoginsTests, ComposeStoreRolesLiveTests: 114 green, plus the new test. Build: 0 warnings.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv

…ond SYSTEM, Administrators and the service account can read is refused (#4028)

The credential check the key shares refuses only a file Users, Authenticated
Users or Everyone can read. INTERACTIVE, Domain Users or one named user holding
ReadData passed it, and whoever reads the key can test guesses at every literal
the keyed hashes hide. DarlingFileSecurity.ReadersBeyondTrusted is an allowlist
over the trusted-owner set; the key's load refuses on it and its creation
verifies against it. The credential files keep the denylist their existing
DACLs were built against, so this cannot newly refuse an install's credentials.

Red-watched: without the load check, a key with an INTERACTIVE read grant
loads.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv
erikdarlingdata and others added 3 commits September 23, 2026 11:06
…s, and is judged and read through one held handle (#4028 review)

Round-1 security review of #4044:
- Medium: the allowlist asked only who could READ the key. WriteData let an account plant a key of its own
  (LocalMachine DPAPI, entropy in the source), and ChangePermissions or TakeOwnership let it grant itself the read.
  Any Allow entry beyond SYSTEM, Administrators and the service account now refuses the key.
- Low: the key was checked by path and read by path, so whoever could write the directory could swap the file in
  between. It is now opened once, sharing read alone, and its links, hard links, owner and DACL are judged through
  that handle, which the read then uses, bounded to 1 KB.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv
… review)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 23, 2026 15:15
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 23, 2026 15:15
@erikdarlingdata
erikdarlingdata merged commit 58a31c7 into dev Sep 23, 2026
17 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/4028-key-file-allowlist branch September 23, 2026 15:23
erikdarlingdata added a commit that referenced this pull request Sep 23, 2026
…ntries in their sections (#4080)

Adds 42 entries and 42 link refs (#3992, #3995, #3996, #3998, #4001, #4002, #4003, #4007, #4010, #4011, #4013, #4015, #4020, #4022, #4025, #4029, #4030, #4031, #4036, #4038, #4039, #4040, #4044, #4047, #4048, #4049, #4050, #4051, #4055, #4061, #4063, #4064, #4065, #4066, #4067, #4068, #4069, #4070, #4071, #4073, #4074, #4078). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran.

#3989 left 26 entries under bare 'Changed' and 'Fixed' lines above '### Added'. They move into '### Changed' and '### Fixed', below the new entries, and one blank line stays under [Unreleased].


Claude-Session: https://claude.ai/code/session_01Ua31ugERL5DmhFVRtf6keQ

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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