Skip to content

fix(dev-mode): avoid certificate store rescans#421

Merged
yordis merged 1 commit into
masterfrom
yordis/fix-dev-certificate-startup
Jul 4, 2026
Merged

fix(dev-mode): avoid certificate store rescans#421
yordis merged 1 commit into
masterfrom
yordis/fix-dev-certificate-startup

Conversation

@yordis

@yordis yordis commented Jul 4, 2026

Copy link
Copy Markdown
Member
  • Dev-mode startup should keep working when local certificate stores are unavailable or unreliable.
  • The development startup path should use the certificate it already resolved instead of depending on a second store lookup.

@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes TLS dev-certificate resolution and Unix store behavior at node startup; production paths using configured certs are unchanged, but mistakes could affect local HTTPS in dev mode.

Overview
Dev-mode startup no longer re-enumerates certificate stores after EnsureDevelopmentCertificate. It uses a new out X509Certificate2 overload so the same instance is used for trust checks, logging, and DevCertificateProvider.

Certificate listing on Linux (and other non-Windows/non-macOS platforms) skips LocalMachine personal stores instead of opening stores that are unreliable or unavailable, returning an empty list for those lookups.

Unix dev cert persistence falls back to an ephemeral key set when saving to the user store fails with home-directory access errors (UnauthorizedAccessException / DirectoryNotFoundException), so dev mode can still start when the cert store path is not writable.

The parameterless EnsureDevelopmentCertificate overload still disposes the certificate after the call for existing callers; only the startup path uses the overload that hands ownership to the host.

Reviewed by Cursor Bugbot for commit 2a8a450. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yordis, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bad5e19e-e2d9-484c-866e-57aa992bdea2

📥 Commits

Reviewing files that changed from the base of the PR and between a54c232 and 2a8a450.

📒 Files selected for processing (3)
  • src/EventStore.ClusterNode/Program.cs
  • src/EventStore.Common/DevCertificates/CertificateManager.cs
  • src/EventStore.Common/DevCertificates/UnixCertificateManager.cs

Walkthrough

This PR adds a configurable DevCertPath option to load or persist a dev-mode certificate from a specified PFX file, introduces a DevCertificateFile utility for loading/validating certificates and writing PEM public certificates, refactors CertificateManager.EnsureDevelopmentCertificate to return the certificate via an out parameter, and hardens Unix certificate saving with retry logic on home-directory access errors.

Changes

Configurable dev certificate path support

Layer / File(s) Summary
DevCertPath configuration option
src/EventStore.Core/Configuration/ClusterVNodeOptions.cs, src/EventStore.Core.XUnit.Tests/Configuration/ClusterVNodeOptionsTests.cs
Adds nullable DevCertPath option to DevModeOptions with a test confirming --dev-cert-path parsing.
DevCertificateFile load/write utility
src/EventStore.Common/DevCertificates/DevCertificateFile.cs, src/EventStore.Core.XUnit.Tests/Certificates/DevCertificateFileTests.cs
Adds TryLoad (validity/private-key/dev-OID checks) and WritePublicCertificate (PEM output with path derivation) plus tests for valid/expired/missing-key/corrupt certificates and write behavior.
CertificateManager out-parameter refactor
src/EventStore.Common/DevCertificates/CertificateManager.cs
Reshapes EnsureDevelopmentCertificate to output the selected/created certificate via an out parameter, updates disposal to preserve the returned instance, removes isNewCertificate tracking, and restricts LocalMachine store listing on unsupported platforms.
Unix certificate save retry handling
src/EventStore.Common/DevCertificates/UnixCertificateManager.cs
Refactors SaveCertificateCore to reload/write certificates with cleanup in finally, retrying with EphemeralKeySet when a new IsHomeDirectoryAccessError helper detects home-directory access failures.
Program.cs dev certificate wiring
src/EventStore.ClusterNode/Program.cs
Updates DEV-mode startup to load from DevCertPath, otherwise ensure/create via CertificateManager, write the public cert, apply Windows-only trust, and construct DevCertificateProvider from the resulting certificate.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Program
  participant DevCertificateFile
  participant CertificateManager
  participant DevCertificateProvider

  Program->>DevCertificateFile: TryLoad(DevCertPath)
  alt certificate loaded
    DevCertificateFile-->>Program: certificate
  else load failed or no path
    Program->>CertificateManager: EnsureDevelopmentCertificate(devCertPath)
    CertificateManager-->>Program: EnsureCertificateResult, certificate
    Program->>DevCertificateFile: WritePublicCertificate(certificate, DevCertPath)
  end
  Program->>CertificateManager: TrustCertificate (Windows only)
  Program->>DevCertificateProvider: construct with devCertificate
Loading

Poem

A rabbit hops through PFX files so neat,
Loading certs from paths beneath its feet. 🐰
No more digging in stores far and wide,
Just one true cert, trusted with pride.
Out params hop, retries take flight—
Dev mode certs are finally right! 🔐✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title matches a real part of the change: dev-mode now avoids repeated certificate store lookups.
Description check ✅ Passed The description is directly related to the changes, describing resilient dev-mode startup and reuse of an already resolved certificate.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch yordis/fix-dev-certificate-startup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a54c232. Configure here.

Comment thread src/EventStore.Common/DevCertificates/DevCertificateFile.cs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/EventStore.Common/DevCertificates/UnixCertificateManager.cs (1)

39-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Log the fallback to EphemeralKeySet.

When a home-directory access error triggers the fallback, the certificate is reloaded in-memory and, notably, is not re-added to the store, so it is not persisted. This happens silently. A warning here would make it clear (during debugging of "why does my dev cert keep regenerating?") that the store write was skipped and persistence now depends on --dev-cert-path.

♻️ Suggested log on fallback
 		catch (Exception ex) when (IsHomeDirectoryAccessError(ex))
 		{
+			Log.SaveCertificateInStoreError(ex.ToString());
 			certificate.Dispose();
 			certificate = X509CertificateLoader.LoadPkcs12(export, "",
 				X509KeyStorageFlags.EphemeralKeySet | X509KeyStorageFlags.Exportable);
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/EventStore.Common/DevCertificates/UnixCertificateManager.cs` around lines
39 - 44, Handle the home-directory access fallback in UnixCertificateManager by
adding a warning in the catch block around the EphemeralKeySet reload so it is
clear the certificate was reloaded in memory and not re-added to the store. Use
the existing IsHomeDirectoryAccessError path and the certificate reload logic in
UnixCertificateManager to log that persistence was skipped and that future
persistence depends on --dev-cert-path, so the fallback is visible during
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/EventStore.ClusterNode/Program.cs`:
- Around line 202-212: The trust-check branch in Program.Main is conflating two
different cases: an already-trusted certificate on Windows and an unsupported
platform. Update the conditional around manager.IsTrusted(devCertificate) and
RuntimeInformation.IsWindows so the Windows path only logs and calls
TrustCertificate when the cert is not trusted, and add a separate branch for the
already-trusted case that avoids the warning. Keep the unsupported-platform
warning only for non-Windows platforms, using the existing manager and
devCertificate symbols to make the split clear.

In `@src/EventStore.Common/DevCertificates/DevCertificateFile.cs`:
- Around line 11-40: The blanket catch in DevCertificateFile.TryLoad hides real
certificate load failures, so distinguish the expected missing-file case from
unexpected load errors. Keep the current null return behavior for
File.Exists(path) and invalid certificate checks, but in the
X509CertificateLoader.LoadPkcs12FromFile try/catch path log the caught exception
with context before returning null, using the TryLoad method and
CertificateManager.IsHttpsDevelopmentCertificate path to locate the fix. Ensure
the log captures the path and exception details so misconfigured mounts are
diagnosable.
- Around line 20-23: DevCertificateFile.TryLoad currently loads the PKCS#12
certificate with Exportable only, which can still depend on a writable profile;
update the X509CertificateLoader.LoadPkcs12FromFile call to match the Unix
import behavior by using X509KeyStorageFlags.EphemeralKeySet |
X509KeyStorageFlags.Exportable, or add the same home-directory fallback used in
the other import path. Keep the change localized to DevCertificateFile and its
certificate-loading logic so the import works without requiring a writable user
profile.

---

Nitpick comments:
In `@src/EventStore.Common/DevCertificates/UnixCertificateManager.cs`:
- Around line 39-44: Handle the home-directory access fallback in
UnixCertificateManager by adding a warning in the catch block around the
EphemeralKeySet reload so it is clear the certificate was reloaded in memory and
not re-added to the store. Use the existing IsHomeDirectoryAccessError path and
the certificate reload logic in UnixCertificateManager to log that persistence
was skipped and that future persistence depends on --dev-cert-path, so the
fallback is visible during debugging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5469b3ff-6fb7-4f16-bffe-20de86e9d02d

📥 Commits

Reviewing files that changed from the base of the PR and between c948398 and a54c232.

📒 Files selected for processing (7)
  • src/EventStore.ClusterNode/Program.cs
  • src/EventStore.Common/DevCertificates/CertificateManager.cs
  • src/EventStore.Common/DevCertificates/DevCertificateFile.cs
  • src/EventStore.Common/DevCertificates/UnixCertificateManager.cs
  • src/EventStore.Core.XUnit.Tests/Certificates/DevCertificateFileTests.cs
  • src/EventStore.Core.XUnit.Tests/Configuration/ClusterVNodeOptionsTests.cs
  • src/EventStore.Core/Configuration/ClusterVNodeOptions.cs

Comment thread src/EventStore.ClusterNode/Program.cs Outdated
Comment thread src/EventStore.Common/DevCertificates/DevCertificateFile.cs Outdated
Comment thread src/EventStore.Common/DevCertificates/DevCertificateFile.cs Outdated
@yordis yordis changed the title fix(dev-mode): support durable development certificates fix(dev-mode): avoid certificate store rescans Jul 4, 2026
@yordis yordis force-pushed the yordis/fix-dev-certificate-startup branch from a54c232 to 6d82e5f Compare July 4, 2026 08:21
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis yordis force-pushed the yordis/fix-dev-certificate-startup branch from 6d82e5f to 2a8a450 Compare July 4, 2026 08:29
@yordis yordis merged commit 4934657 into master Jul 4, 2026
21 checks passed
@yordis yordis deleted the yordis/fix-dev-certificate-startup branch July 4, 2026 08:51
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