1111 * middleware returning 403), so there is no reduced-information path.
1212 */
1313
14+ import net from 'net' ;
15+
1416import _ from 'lodash' ;
1517
1618import { getConfig , getConfigV2 } from '../utils/config-loader.js' ;
@@ -340,20 +342,77 @@ const normalizeNodeId = (id) => {
340342 return s . startsWith ( '0x' ) ? s . slice ( 2 ) : s ;
341343} ;
342344
345+ // Mirrors chia's chia.util.network.is_localhost. The wallet trusts any
346+ // localhost peer unconditionally, so a connection to 127.0.0.1 is trusted
347+ // even when wallet.trusted_peers is left at its default placeholder.
348+ const LOCALHOST_HOSTS = new Set ( [ '127.0.0.1' , 'localhost' , '::1' , '0:0:0:0:0:0:0:1' ] ) ;
349+ const stripBrackets = ( host ) => String ( host ?? '' ) . replace ( / ^ \[ / , '' ) . replace ( / \] $ / , '' ) ;
350+ const isLocalhost = ( peerHost ) => LOCALHOST_HOSTS . has ( stripBrackets ( peerHost ) ) ;
351+
343352/**
344- * Cross-reference connected wallet peers against the trusted_peers map in
345- * the chia config.yaml. Returns a small object suitable for embedding in
346- * the diagnostics response, including whether at least one connection is
347- * trusted.
353+ * Mirror chia's chia.util.network.is_trusted_cidr: true when peerHost parses
354+ * to an IP that falls inside any configured CIDR. Never throws -- a malformed
355+ * CIDR entry is skipped rather than failing the whole diagnostics response.
356+ */
357+ const isTrustedCidr = ( peerHost , trustedCidrs ) => {
358+ if ( ! Array . isArray ( trustedCidrs ) || trustedCidrs . length === 0 ) return false ;
359+ const host = stripBrackets ( peerHost ) ;
360+ const hostType = net . isIP ( host ) ;
361+ if ( hostType === 0 ) return false ;
362+ const hostFamily = hostType === 6 ? 'ipv6' : 'ipv4' ;
363+
364+ for ( const cidr of trustedCidrs ) {
365+ const slash = String ( cidr ) . lastIndexOf ( '/' ) ;
366+ if ( slash === - 1 ) continue ;
367+ const network = String ( cidr ) . slice ( 0 , slash ) ;
368+ const prefix = Number ( String ( cidr ) . slice ( slash + 1 ) ) ;
369+ const netType = net . isIP ( network ) ;
370+ if ( netType === 0 || ! Number . isInteger ( prefix ) ) continue ;
371+ try {
372+ const blockList = new net . BlockList ( ) ;
373+ blockList . addSubnet ( network , prefix , netType === 6 ? 'ipv6' : 'ipv4' ) ;
374+ if ( blockList . check ( host , hostFamily ) ) return true ;
375+ } catch {
376+ // Malformed CIDR (e.g. prefix out of range) -- skip it.
377+ }
378+ }
379+ return false ;
380+ } ;
381+
382+ /**
383+ * Decide whether a single connection is trusted, mirroring chia's
384+ * chia.util.network.is_trusted_peer (and the `chia peer`/`chia wallet show`
385+ * CLI, which computes trust client-side -- the RPC does not return it):
386+ *
387+ * trusted = is_localhost(host) OR node_id in trusted_peers OR is_trusted_cidr
388+ *
389+ * Returns the matched reason so the diagnostics output explains *why* a peer
390+ * is trusted (e.g. a localhost full node is trusted even when trusted_peers
391+ * still holds the default placeholder node id).
392+ */
393+ const classifyTrust = ( peerHost , nodeId , normalizedTrustedSet , trustedCidrs ) => {
394+ if ( isLocalhost ( peerHost ) ) return 'localhost' ;
395+ if ( normalizedTrustedSet && normalizedTrustedSet . has ( normalizeNodeId ( nodeId ) ) ) return 'configured' ;
396+ if ( isTrustedCidr ( peerHost , trustedCidrs ) ) return 'cidr' ;
397+ return null ;
398+ } ;
399+
400+ /**
401+ * Cross-reference connected wallet peers against the chia config.yaml trust
402+ * settings (wallet.trusted_peers + wallet.trusted_cidrs) plus chia's implicit
403+ * localhost rule. Returns a small object suitable for embedding in the
404+ * diagnostics response, including whether at least one connection is trusted.
348405 */
349406const buildTrustedPeerView = ( connectionsResult , chiaConfigResult ) => {
350407 const view = {
351408 configuredTrustedNodeIds : [ ] ,
409+ configuredTrustedCidrs : [ ] ,
352410 connected : [ ] ,
353411 hasTrustedConnection : false ,
354412 } ;
355413
356414 let normalizedTrustedSet = null ;
415+ let trustedCidrs = [ ] ;
357416 if ( chiaConfigResult ?. ok ) {
358417 const trustedPeerMap = _ . get ( chiaConfigResult . value , 'wallet.trusted_peers' , null ) ;
359418 if ( trustedPeerMap && typeof trustedPeerMap === 'object' ) {
@@ -364,18 +423,22 @@ const buildTrustedPeerView = (connectionsResult, chiaConfigResult) => {
364423 view . configuredTrustedNodeIds = keys ;
365424 normalizedTrustedSet = new Set ( keys . map ( normalizeNodeId ) ) ;
366425 }
426+ const configuredCidrs = _ . get ( chiaConfigResult . value , 'wallet.trusted_cidrs' , null ) ;
427+ if ( Array . isArray ( configuredCidrs ) ) {
428+ trustedCidrs = configuredCidrs ;
429+ view . configuredTrustedCidrs = configuredCidrs ;
430+ }
367431 } else if ( chiaConfigResult ) {
368432 view . chiaConfigError = chiaConfigResult . error ;
369433 }
370434
371435 if ( connectionsResult ?. ok && connectionsResult . value ?. success !== false ) {
372436 const connections = connectionsResult . value ?. connections || [ ] ;
373437 view . connected = connections . map ( ( c ) => {
374- const trusted = ! ! (
375- normalizedTrustedSet && normalizedTrustedSet . has ( normalizeNodeId ( c . nodeId ) )
376- ) ;
438+ const trustedReason = classifyTrust ( c . peerHost , c . nodeId , normalizedTrustedSet , trustedCidrs ) ;
439+ const trusted = trustedReason !== null ;
377440 if ( trusted ) view . hasTrustedConnection = true ;
378- return { peerHost : c . peerHost , peerPort : c . peerPort , type : c . type , trusted } ;
441+ return { peerHost : c . peerHost , peerPort : c . peerPort , type : c . type , trusted, trustedReason } ;
379442 } ) ;
380443 } else if ( connectionsResult ) {
381444 view . connectionsError = connectionsResult . ok
@@ -880,6 +943,9 @@ export const __test = {
880943 collectSubscriptions,
881944 buildTrustedPeerView,
882945 normalizeNodeId,
946+ isLocalhost,
947+ isTrustedCidr,
948+ classifyTrust,
883949 collectOwnedStoreExpectations,
884950 escalateLostOwnedStores,
885951 StatusAccumulator,
0 commit comments