Skip to content

Viewer: anchor a relative Root Certificate to darling.json's directory, not the process working directory #1970

Closed

Description

@erikdarlingdata

Split out of #1953's security review rather than folded into PR #1968, because it changes Viewer behavior for every bring-your-own connection string, not just exported ones - that is a decision worth its own change.

The gap

ViewerSettings.Parse returns a BYO postgres.connectionString verbatim (Darling/PerformanceMonitor.Darling.Viewer/ViewerSettings.cs:134-141), so Npgsql resolves a relative Root Certificate=server.crt against the Viewer process's working directory - which is not necessarily the folder holding darling.json and the certificate beside it. A desktop shortcut's "Start in", or launching from Explorer's last-used directory, silently changes the anchor.

That makes the working directory the trust anchor for a verify-full pin. If the Viewer is ever launched with a working directory an attacker can write (Downloads, a shared folder), a planted server.crt becomes the pinned root; combined with LAN position that is interception of the store session and the credential inside it.

--export-viewer-config (#1953) deliberately emits the relative name - the service host cannot know the viewer machine's absolute path - and documents the caveat and the absolute-path alternative in three places. So this is a sharp edge that is currently documented, not removed.

The fix

In ViewerSettings.TryLoad, when the parsed BYO connection string's Root Certificate is a relative path, rewrite it to Path.Combine(Path.GetDirectoryName(resolvedConfigPath)!, value). The Viewer already knows the config path it loaded, so "the certificate beside darling.json" becomes true regardless of working directory, the exported folder keeps working when copied anywhere, and an absolute path set by hand is left alone.

Also worth pinning while in there

What Npgsql 10 does when the configured RootCertificate file does not exist. The shipped assembly's metadata suggests an existence guard with a fall-through to the machine trust store, which would mean VerifyFull validating against public CAs instead of the pinned PEM. A self-signed store certificate would not chain to a public root, so the practical outcome is probably a connection failure - but "probably" is not the standard for a pinning control, and a test should settle it.

Activity

  1. erikdarlingdata commented on Aug 1, 2026

    @erikdarlingdata
    OwnerAuthor

    Shipped in #1977. A relative or bare Root Certificate now anchors to the directory of the darling.json the viewer actually read, not the process working directory - which was the trust anchor for a VerifyFull pin and changed with a shortcut's Start-in or Explorer's last-used folder. One pure resolver (ViewerCertificateAnchor) is the single implementation, consumed by both the connection-string rewrite and the #1954 startup diagnostics, with a structural test that fails if either ever computes its own answer. Absolute paths pass through as written, the keyword is never added to a string that lacked it, and there is deliberately no SSL Mode gate - anchoring under Require changes nothing Npgsql reads, while gating would leave a stale working-directory anchor for the operator who later tightens the mode. The exported folder now works unedited wherever it is copied: the export's README/JSON caveat telling operators to hand-edit a full path is gone, and the pinned doc tests now assert its absence. Verified against shipped Npgsql 10.0.3 by reflection that Root Certificate round-trips through the builder with only the certificate value changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions