From 2f03abb581c884e5e70f0a83828cd521314fa64d Mon Sep 17 00:00:00 2001 From: matthew-pilot Date: Fri, 29 May 2026 15:16:01 +0000 Subject: [PATCH 1/2] fix: gate mutual and same-network auto-trust on registryBound (PILOT-228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handshake.go:603,630 — both the mutual handshake path and the same-network auto-trust path called markTrustedLocked() without the registryBound gate that protects the trusted-agents path at :659. A peer could claim any NodeID, present their own pubkey, sign with their own key, pass signature verify, and slip into auto-trust because the signature only proves key-possession, not that the pubkey belongs to the claimed node. Fix: add && registryBound to both conditionals. When the registry confirmed the (node_id, pubkey) binding, auto-trust is safe. When registryBound is false, both branches fall through to manual approval. Related: SEC-038, SEC-003, SEC-044. --- handshake.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/handshake.go b/handshake.go index d89d9cf..59b004d 100644 --- a/handshake.go +++ b/handshake.go @@ -600,8 +600,10 @@ func (hm *Manager) handleRequest(stream coreapi.Stream, msg *HandshakeMsg, regis } // Check if we have an outgoing request to this peer (mutual handshake) - if _, ok := hm.outgoing[peerNodeID]; ok { - // Mutual! Auto-approve + // SEC-038: mutual auto-trust is gated on registryBound so a peer + // cannot claim any NodeID with their own keypair and slip into trust. + if _, ok := hm.outgoing[peerNodeID]; ok && registryBound { + // Mutual! Auto-approve (registry confirmed pubkey binding) delete(hm.outgoing, peerNodeID) hm.markTrustedLocked(peerNodeID, &TrustRecord{ NodeID: peerNodeID, @@ -627,7 +629,9 @@ func (hm *Manager) handleRequest(stream coreapi.Stream, msg *HandshakeMsg, regis } // Check if peers are on the same network (network trust) - if hm.sameNetwork(peerNodeID) { + // SEC-038: same-network auto-trust is gated on registryBound so a + // peer cannot claim any NodeID with their own keypair and slip into trust. + if hm.sameNetwork(peerNodeID) && registryBound { hm.markTrustedLocked(peerNodeID, &TrustRecord{ NodeID: peerNodeID, PublicKey: msg.PublicKey, From 8f830feb24fc7ce4c6826f8db247b59679f3aaa7 Mon Sep 17 00:00:00 2001 From: Teodor Calin Date: Fri, 29 May 2026 14:21:19 -0700 Subject: [PATCH 2/2] test: pass registryBound=true to handleRequest auto-approve assertions This PR's whole point is to gate same-network + mutual auto-approve on registryBound (PILOT-228 / SEC-038). Two pre-existing tests covering the auto-approve paths fed registryBound=false, which after this gate correctly results in pending-approval instead of auto-trust. Update the two affected tests to pass registryBound=true so they continue to exercise the auto-approve branches: - TestHandleRequest_SameNetworkDirectPathAutoApproves - TestHandleRequestMutualAutoApprovesAndMarksMutual The other handleRequest(..., false) call sites in the same file cover negative/pending paths and are intentionally left as false. --- zz_ceiling_more_test.go | 2 +- zz_handshake_accept_request_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zz_ceiling_more_test.go b/zz_ceiling_more_test.go index bb221c7..bdce072 100644 --- a/zz_ceiling_more_test.go +++ b/zz_ceiling_more_test.go @@ -36,7 +36,7 @@ func TestHandleRequest_SameNetworkDirectPathAutoApproves(t *testing.T) { NodeID: 99, PublicKey: "peer-99-key", Timestamp: time.Now().Unix(), - }, false) + }, true) // registryBound=true: peer's pubkey was confirmed by the registry hm.mu.RLock() rec, trusted := hm.trusted[99] diff --git a/zz_handshake_accept_request_test.go b/zz_handshake_accept_request_test.go index c28120a..951eadf 100644 --- a/zz_handshake_accept_request_test.go +++ b/zz_handshake_accept_request_test.go @@ -104,7 +104,7 @@ func TestHandleRequestMutualAutoApprovesAndMarksMutual(t *testing.T) { PublicKey: "peer-99-key", Timestamp: time.Now().Unix(), } - hm.handleRequest(nil, msg, false) + hm.handleRequest(nil, msg, true) // registryBound=true: peer is verified by registry hm.mu.RLock() rec, ok := hm.trusted[99]