Repository navigation
Stop the config backups granting INTERACTIVE read, and pin the exact identity set (#1769) - #1786
Merged
Merged
Conversation
…identity set (#1769) This is the half of #1769 that is safe regardless of how the headline question is decided. It does NOT close the issue. VERIFIED, AND IT CONTRADICTS THE ISSUE'S FIX SHAPE. Removing INTERACTIVE:(R) from darling.json ITSELF would break the shipped Viewer. ViewerSettings.TryLoad reads the file directly (ViewerSettings.cs:122), the Viewer manifest is asInvoker (app.manifest:20), and under UAC an interactive admin's default token carries BUILTIN\Administrators as SE_GROUP_USE_FOR_DENY_ONLY - so a DACL of exactly SYSTEM + Administrators + service identity denies it, and MainWindow shows "darling.json could not be read". That hits the default shipped layout, where the Viewer sits in viewer\ under the service root and probes the parent for the service's config. So darling.json keeps its INTERACTIVE grant here, pending a decision on how to give the Viewer a non-secret path to what it needs. The BACKUPS have no such constraint. A .bak-* is a byte-for-byte copy of the same secrets, and nothing reads one: the only references to the name in non-test code are the two lines that construct it, and restoring one is a hand operation that already requires elevation, because writing darling.json does - INTERACTIVE only ever had Read, never Write. The grant bought nothing and left a second readable copy of every encrypted password and access token. install-darling.ps1 gets the same split so the installer and the service cannot disagree, and its summary line now says which file gets which posture instead of claiming one rule for both. Two pins, because the two failure modes are different. The exactly-three- identities assertion the issue asked for catches an unintended principal in the emitted DACL and names it (presence/absence assertions pass happily while a fourth rides along, which is the shape of the reported defect). The end-to-end backup assertion runs through the real WriteWithBackupAsync path, because the thing being fixed is a CALL SITE passing the wrong argument, which a test of the shape alone cannot see. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ckup-acl # Conflicts: # CHANGELOG.md
…ckup-acl # Conflicts: # CHANGELOG.md
erikdarlingdata
added a commit
that referenced
this pull request
Jul 28, 2026
Resolved to get CI to run at all, not as an arm-time step: a CONFLICTING PR has no merge ref, and pull_request workflows build the merge ref -- so Build and Claude Auto Review could not start on this PR while the conflict stood, while other branches' runs kept firing normally. That is what made this look like an Actions outage for ~25 minutes. CHANGELOG.md was the only conflict, and only in the link-ref block. Resolved KEEP-BOTH with no re-sorting: this branch's #1665/#1788 refs and dev's #1781/#1783/#1786/#1792 refs all retained, in the order they appeared. No entry text on either side was touched. Everything else auto-merged. Verified rather than assumed, because the dangerous case here is textual cleanliness hiding a semantic break: full solution rebuild 0 warnings / 0 errors, Darling suite 3549 passed / 0 failed. The one real collision this class of merge produced -- #1776's new live-store hygiene guard versus this branch's new own-store live test -- was found the same way and fixed in 59f877a before this merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull Bot
pushed a commit
to ehtick/PerformanceMonitor
that referenced
this pull request
Jul 29, 2026
…gdata#1816) Field-verified exposure: backups created before erikdarlingdata#1786 fixed the creation path kept inherited BUILTIN\Users read - full DPAPI recoverability for every stored credential and token by any local account. The installer's check flags them but only prints the fix. The config-hardening sweep now also hardens every darling.json.bak-* sibling (allowInteractiveRead: false, matching the creation path), with the live file's best-effort + runnable-remediation contract and the still-readable CRITICAL backstop. One service start closes the exposure fleet-wide with no human in the loop. ACL tests against real files pin: both backups stripped, live file left to its own hardening, lookalike backups untouched, no-backup no-op; watched red by disabling the hardening call. Full fast suite 3572 green; zero warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1769, which has been retitled to what actually ships: "Config BACKUPS were interactive-readable; darling.json's INTERACTIVE grant is load-bearing by design (Viewer)".
The original ask — drop the INTERACTIVE ACE from
darling.jsonitself — is not being made, because it would break the shipped Viewer. That design question, with all three options recorded verbatim, now lives in #1792 and does not close with this PR.The issue's fix shape does not survive contact, and this is why
#1769 asks for the INTERACTIVE ACE to be dropped from
darling.json's hardening so the emitted DACL is exactly SYSTEM + Administrators + the service identity. That would break the shipped Viewer, and the chain is not subtle:ViewerSettings.TryLoaddoesFile.ReadAllText(path)atDarling/PerformanceMonitor.Darling.Viewer/ViewerSettings.cs:122.<requestedExecutionLevel level="asInvoker" />atDarling/PerformanceMonitor.Darling.Viewer/app.manifest:20, wired via<ApplicationManifest>in the Viewer csproj.BUILTIN\AdministratorsasSE_GROUP_USE_FOR_DENY_ONLY, so an Allow ACE for Administrators grants that token nothing. A three-identity DACL therefore denies the Viewer.MainWindow.OnLoadedcatches and shows"darling.json could not be read: …"(MainWindow.xaml.cs:174-178). No connection, no data.viewer\under the service root, and it probes the parent for the service'sdarling.json(ViewerSettings.cs:92-104).The existing code says as much at
DarlingWorker.cs:540-542, andinstall-darling.ps1mirrors the grant deliberately. So the current behavior is intentional; the issue and the design disagree, and the issue did not account for the Viewer.The security rationale is still sound, to be clear. I checked the obvious rebuttal: the Viewer already gets INTERACTIVE read on
pg-admin-credential.dpapiby design, but theadminrole's SELECT carves out the secret columns, sodarling.jsonreally is the only interactive-readable path to the monitored servers' SQL passwords and the MCP/web tokens. The tightening is worth doing — it just cannot be done by deleting the ACE alone, and choosing how to give the Viewer a non-secret path to what it needs (it only wants thepostgressection) is a design decision, not a small fix.What this PR does instead
The config BACKUPS have no such constraint. A
darling.json.bak-*is a byte-for-byte copy of the same secrets, and nothing reads one: the only references to the.bak-name in non-test code are the two lines that CONSTRUCT it (DarlingCliCommands.cs:1333,1336), and restoring one is a hand operation that already requires elevation, because writingdarling.jsondoes — INTERACTIVE only ever hadReadhere, neverWrite.So the grant bought nothing and left a second readable copy of every encrypted password and access token, exposed to any interactively-logged-on user.
HardenConfigBackupnow passesallowInteractiveRead: false, andinstall-darling.ps1gets the same split so the installer and the service cannot disagree — its summary line now states which file gets which posture rather than claiming one rule for both.This is correct under all three ways the headline question could be decided, which is why it is worth landing on its own.
Pins
Two, because the two failure modes are different.
HardenFile_NonInteractive_EmitsExactlyTheThreeIntendedIdentities— the exactly-three-identities assertion the issue asked for, valid on the non-interactive posture (superuser credential, transient init pwfile, and now backups). Presence/absence assertions pass happily while a fourth principal rides along, which is exactly the reported defect's shape, so this one names the intruder.WriteWithBackup_HardensTheBackup_…runs through the realWriteWithBackupAsyncpath, because the thing being fixed is a call site passing the wrong argument — a test of the ACL shape alone cannot see that.Mutation table
HardenFileadd INTERACTIVE unconditionallygranted principals beyond SYSTEM, Administrators and the service identity: NT AUTHORITY\INTERACTIVE, plus the existing superuser testHardenConfigBackuptoallowInteractiveRead: trueWriteWithBackup_HardensTheBackup_BecauseFileCopyDoesNotCarryTheDaclM6 is the one that matters for this change: it proves the call site is pinned, not merely the shape.
Verification
Darling.Testsfull suite: 3485 passed, 0 failed, 171 skipped (the live-Postgres tests skip withoutDARLING_TEST_PG; unrelated to this change).DarlingFileSecurityTests: 12/12.dotnet build … Darling.Tests -c Debug -t:Rebuildand… Darling.Service -c Debug -t:Rebuild— both 0 Warning(s), 0 Error(s).install-darling.ps1parses clean (Parser::ParseFile, 0 errors).Issue hygiene done alongside this
#1769 carries a comment with the full six-link chain at file:line (verified at
50012266) and an explicit decision block; it is retitled to the shipping scope; and #1792 was opened to hold Erik's three-way decision ondarling.jsonso it survives this issue closing.Note for the prompt maintainer
The client checklist's step-14 note currently says
INTERACTIVE:(R)"will be removed in a future build". That is now false and wants rewording to by-design-pending-#1792 — this PR does not changedarling.json's own DACL, so the observation stays expected on a field box. It also gains a new falsifiable check: post-this-build, anydarling.json.bak-*shows three identities only.🤖 Generated with Claude Code