Skip to content

[Security] The cluster hello identity is not bound to the TLS peer certificate, so mTLS admits a node but never verifies which node it is #912

Description

@pathosDev

Component: src/cluster/Transport.ts
Severity (assessment): MEDIUM — see the note at the end, it is arguable
CWE: CWE-287 (improper authentication)

The hello frame carries a NodeAddress and nothing else. Even on a fully
mTLS-configured cluster, nothing ties the identity a peer claims in hello
to the identity its certificate proves. mTLS answers "is this peer allowed
in the cluster"; it never answers "is this peer the node it says it is".

Why this matters now

The gossip-authority rules landed in #562/#564/#572 are all keyed on the
connection's peer — deliberately, because the payload's from is the one
field an attacker fully controls. Those rules are exactly as strong as the
connection identity underneath them, and today that identity is self-declared.

So a single compromised or malicious CA-signed node can still:

  • announce itself under another member's address and, once the leader
    promotes it, satisfy the "sender must be an active member" rule for
    third-party claims;
  • receive traffic addressed to the impersonated node, because
    TcpTransport.byPeer is keyed on the claimed address.

The duplicate-identity guard (Transport.ts:299-313) only blocks claiming an
address that is already mapped. A fresh claim, or a claim made after the
real holder's connection drops, is accepted.

Evidence

src/cluster/Transport.tsonMessage accepts any hello and registers it:

if (message.kind === 'hello') {
  const peer = NodeAddress.fromJSON(message.self);
  const peerKey = peer.toString();
  // ... hijack guard: only rejects an ALREADY-mapped peerKey ...
  connection.peer = peer;
  this.byPeer.set(peerKey, connection);

TcpSocketLike (src/runtime/tcp/TcpBackend.ts:44-48) exposes only
write / end / remoteAddress — the peer certificate is not surfaced by
the abstraction at all, so no code above the adapters could check it today.

This is also stated as a known residual in the #565 verifier notes ("the only
check is a duplicate-identity guard … not a fresh claimed identity") but was
never filed on its own.

Proposed approach

  1. Surface the verified peer certificate on TcpSocketLike — Node exposes
    tlsSocket.getPeerCertificate(), Bun has an equivalent; on Deno the
    listener half of mTLS is unavailable anyway ([Security] DenoTcpBackend silently drops ca, requestClientCert and the client cert/key — cluster mTLS cannot be enabled at all on Deno #576), so the field is
    undefined there and the check degrades to today's behaviour.
  2. When the listener required a client certificate, reject a hello whose
    claimed systemName@host is not covered by the certificate's CN or a SAN
    entry. Log and drop the connection, the same way the frame guards do.
  3. Leave plain-TCP clusters as they are — the transport is documented as
    unauthenticated there, and this check has nothing to work with.

Acceptance criteria

  • A peer presenting a valid CA-signed certificate for node-a cannot
    complete a hello claiming to be node-b.
  • A matching certificate still completes the handshake unchanged.
  • With no client certificate required, behaviour is unchanged.
  • docs/.../operations/security/cluster-security.mdx (EN + DE) drops the
    "This is not authentication" caveat added alongside the gossip-authority
    rules, since it would no longer be true for mTLS clusters.

Severity note

Filed medium to match how the tracker already calibrates "one authenticated
member can impersonate another" (#574, #582, #719 are all medium). An argument
for high exists: this is the assumption every one of the new authority rules
rests on, so it is less a finding of its own than the floor beneath several
others. Retag if you read the boundary that way.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: highTop priority — high impact, plan nextproduction-goalBlocks or defines the path to production readinesssecuritySecurity-relevant — see severity label for impact tierseverity: mediumModerate impact or requires specific conditions

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions