diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Certificates/CertificateStoreManager.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Certificates/CertificateStoreManager.cs
index fb0f7cc6..062b0584 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Certificates/CertificateStoreManager.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Certificates/CertificateStoreManager.cs
@@ -29,6 +29,13 @@ public sealed class CertificateStoreManager
private readonly string _pkiRoot;
private readonly IAuditWriter _audit;
+ ///
+ /// The resolved PKI store root directory (e.g. pki). Derived once from
+ /// OpcUa:PkiStoreRoot in the production ctor; callers should read this property
+ /// rather than re-resolving the config key independently.
+ ///
+ public string PkiRoot => _pkiRoot;
+
/// Production ctor — reads OpcUa:PkiStoreRoot (default pki).
/// App configuration.
/// The audit writer that persists Trust/Untrust/Delete actions to ConfigAuditLog.
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Certificates.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Certificates.razor
index 3f2b6c25..e32f3cae 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Certificates.razor
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Certificates.razor
@@ -127,7 +127,7 @@ else
private void LoadAll()
{
DisposeRows();
- var pkiRoot = Config.GetValue("OpcUa:PkiStoreRoot") ?? "pki";
+ var pkiRoot = CertManager.PkiRoot; // Single source: manager already resolved OpcUa:PkiStoreRoot (default "pki").
_rows = new()
{
LoadStore("Own", StoreKind.Own, Path.Combine(pkiRoot, "own", "certs")),
@@ -200,8 +200,14 @@ else
{
StoreKind.Trusted => CertManager.Delete("trusted", p.Thumbprint, actor),
StoreKind.Rejected => CertManager.Delete("rejected", p.Thumbprint, actor),
+ // Unreachable defensive guard — action buttons only render for Trusted/Rejected stores
+ // + the 3 literal verbs (Trust/Untrust/Delete); this arm never executes, so it
+ // intentionally does not route through CertificateStoreManager/audit.
_ => CertActionResult.Fail($"cannot delete from {p.Kind}"),
},
+ // Unreachable defensive guard — action buttons only render for Trusted/Rejected stores
+ // + the 3 literal verbs (Trust/Untrust/Delete); this arm never executes, so it
+ // intentionally does not route through CertificateStoreManager/audit.
_ => CertActionResult.Fail("unknown action"),
};
_statusError = !result.Success;