fix(siteruntime): CertStoreActor — dispose listed certs + reject path-traversal thumbprints (T17)

This commit is contained in:
Joseph Doherty
2026-06-18 03:42:17 -04:00
parent a56805e681
commit 2d139442ba
2 changed files with 71 additions and 1 deletions
@@ -64,6 +64,13 @@ public class CertStoreActor : ReceiveActor
private void HandleWrite(WriteCertToLocalStore msg)
{
if (!IsSafeThumbprint(msg.Thumbprint))
{
_log.Warning("Rejecting write for invalid thumbprint {Thumbprint}", msg.Thumbprint);
Sender.Tell(new LocalCertOpAck(false, "invalid thumbprint", null));
return;
}
try
{
Directory.CreateDirectory(_trustedStoreDir);
@@ -82,6 +89,13 @@ public class CertStoreActor : ReceiveActor
private void HandleRemove(RemoveCertFromLocalStore msg)
{
if (!IsSafeThumbprint(msg.Thumbprint))
{
_log.Warning("Rejecting remove for invalid thumbprint {Thumbprint}", msg.Thumbprint);
Sender.Tell(new LocalCertOpAck(false, "invalid thumbprint", null));
return;
}
try
{
var path = Path.Combine(_trustedStoreDir, FileNameFor(msg.Thumbprint));
@@ -133,7 +147,10 @@ public class CertStoreActor : ReceiveActor
TrustedCertInfo? info = null;
try
{
var cert = X509CertificateLoader.LoadCertificate(File.ReadAllBytes(file));
// The TrustedCertInfo projection copies all needed strings/dates
// out before this scope ends, so disposing the cert is safe and
// releases the native handle held per loaded certificate.
using var cert = X509CertificateLoader.LoadCertificate(File.ReadAllBytes(file));
info = new TrustedCertInfo(
cert.Thumbprint,
cert.Subject,
@@ -156,4 +173,17 @@ public class CertStoreActor : ReceiveActor
}
private static string FileNameFor(string thumbprint) => $"{thumbprint}.der";
/// <summary>
/// Defense-in-depth path-traversal guard. In production a thumbprint is a
/// hex SHA1 (always safe), but <c>RemoveServerCertCommand</c> is CLI-exposed
/// and this actor accepts the string unchecked, so a thumbprint such as
/// <c>../../etc/foo</c> would otherwise resolve OUTSIDE the trusted-store
/// directory once combined into a <c>.der</c> file name. Rejects empty,
/// separator-bearing, or dot-dot thumbprints before any filesystem touch.
/// </summary>
private static bool IsSafeThumbprint(string thumbprint) =>
!string.IsNullOrWhiteSpace(thumbprint)
&& thumbprint.IndexOfAny(Path.GetInvalidFileNameChars()) < 0
&& !thumbprint.Contains("..", StringComparison.Ordinal);
}