Summary
When a node's identity is a bare IPv6 literal (e.g. node.hostname: 2001:db8::1), getReplicationCert() cannot find that node's own certificate, because the lookup key and the stored key use different IPv6 spellings.
security/keys.ts:128 looks the context up by the identity string:
const cert = secureTarget.secureContexts.get(getThisNodeName());
getThisNodeName() returns the compact form (2001:db8::1), while secureContexts is keyed from certificate SAN text parsed out of the cert, and OpenSSL/X509 renders IPv6 SANs expanded (2001:DB8:0:0:0:0:0:1; ::1 becomes 0:0:0:0:0:0:0:1). The two never match, so the get returns undefined.
Impact
getReplicationCert() resolves empty for an IPv6-literal identity.
reviewSelfSignedCert() then treats the existing certificate as absent and regenerates it on every startup.
getReplicationCertAuth() (security/keys.ts:139 — (await getReplicationCert()).options.cert) can dereference an absent result.
Failure is silent in normal logs — the node looks healthy until replication TLS misbehaves — which is what makes it worth tracking rather than leaving as folklore.
Scope is narrow: it needs the node identity to be a bare IPv6 literal. A hostname or IPv4 identity is unaffected.
Not a regression
This is a pre-existing limitation of the cert lookup, not something #2223 introduced. It was surfaced by the cross-model review of #2223, which makes IPv6-literal identities viable (canonicalizing identity to the unbracketed form and bracketing only for URL construction) and therefore reachable. #2223 deliberately scoped this out, since fixing it means changing cert-context keying rather than identity resolution.
Suggested fix
Canonicalize IP keys identically on both insertion and lookup — normalize an IPv6 literal to one canonical form on both sides (e.g. via net.isIP + a shared normalizer, cf. normalizeIPv6 in resources/analytics/hostnames.ts) — or locate the replication context by its certificate-record name instead of by SAN text.
Worth adding a generate → parse → getReplicationCert() test over an IPv6 identity, since that path currently has no end-to-end coverage.
Summary
When a node's identity is a bare IPv6 literal (e.g.
node.hostname: 2001:db8::1),getReplicationCert()cannot find that node's own certificate, because the lookup key and the stored key use different IPv6 spellings.security/keys.ts:128looks the context up by the identity string:getThisNodeName()returns the compact form (2001:db8::1), whilesecureContextsis keyed from certificate SAN text parsed out of the cert, and OpenSSL/X509 renders IPv6 SANs expanded (2001:DB8:0:0:0:0:0:1;::1becomes0:0:0:0:0:0:0:1). The two never match, so thegetreturnsundefined.Impact
getReplicationCert()resolves empty for an IPv6-literal identity.reviewSelfSignedCert()then treats the existing certificate as absent and regenerates it on every startup.getReplicationCertAuth()(security/keys.ts:139—(await getReplicationCert()).options.cert) can dereference an absent result.Failure is silent in normal logs — the node looks healthy until replication TLS misbehaves — which is what makes it worth tracking rather than leaving as folklore.
Scope is narrow: it needs the node identity to be a bare IPv6 literal. A hostname or IPv4 identity is unaffected.
Not a regression
This is a pre-existing limitation of the cert lookup, not something #2223 introduced. It was surfaced by the cross-model review of #2223, which makes IPv6-literal identities viable (canonicalizing identity to the unbracketed form and bracketing only for URL construction) and therefore reachable. #2223 deliberately scoped this out, since fixing it means changing cert-context keying rather than identity resolution.
Suggested fix
Canonicalize IP keys identically on both insertion and lookup — normalize an IPv6 literal to one canonical form on both sides (e.g. via
net.isIP+ a shared normalizer, cf.normalizeIPv6inresources/analytics/hostnames.ts) — or locate the replication context by its certificate-record name instead of by SAN text.Worth adding a generate → parse →
getReplicationCert()test over an IPv6 identity, since that path currently has no end-to-end coverage.