@@ -362,12 +362,29 @@ const isTrustedCidr = (peerHost, trustedCidrs) => {
362362 const hostFamily = hostType === 6 ? 'ipv6' : 'ipv4' ;
363363
364364 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 ) ) ;
365+ const cidrStr = String ( cidr ) ;
366+ const slash = cidrStr . lastIndexOf ( '/' ) ;
367+ let network ;
368+ let prefix ;
369+ if ( slash === - 1 ) {
370+ // Bare IP, no prefix. chia's ip_network() treats this as a host route
371+ // (/32 for IPv4, /128 for IPv6), so trust an exact-match peer.
372+ network = cidrStr ;
373+ const bareType = net . isIP ( network ) ;
374+ if ( bareType === 0 ) continue ;
375+ prefix = bareType === 6 ? 128 : 32 ;
376+ } else {
377+ network = cidrStr . slice ( 0 , slash ) ;
378+ const prefixStr = cidrStr . slice ( slash + 1 ) ;
379+ // Require an explicit decimal prefix. Number('') and Number(' ') both
380+ // coerce to 0, which would silently widen a typo'd CIDR like "10.0.0.0/"
381+ // into /0 and trust every peer; hex/float forms ("/0x10", "/1.5") would
382+ // also be misread. chia's ipaddress parser rejects all of these.
383+ if ( ! / ^ \d + $ / . test ( prefixStr ) ) continue ;
384+ prefix = Number ( prefixStr ) ;
385+ }
369386 const netType = net . isIP ( network ) ;
370- if ( netType === 0 || ! Number . isInteger ( prefix ) ) continue ;
387+ if ( netType === 0 ) continue ;
371388 try {
372389 const blockList = new net . BlockList ( ) ;
373390 blockList . addSubnet ( network , prefix , netType === 6 ? 'ipv6' : 'ipv4' ) ;
@@ -386,15 +403,23 @@ const isTrustedCidr = (peerHost, trustedCidrs) => {
386403 *
387404 * trusted = is_localhost(host) OR node_id in trusted_peers OR is_trusted_cidr
388405 *
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).
406+ * Returns `{ trusted, reason }` where `trusted` is true / false / 'unknown'.
407+ * Localhost is detectable from the peer host alone, so it is evaluated even
408+ * without the chia config. The trusted_peers / trusted_cidrs rules require
409+ * the config, so when it is unavailable (`configReadable === false`) a
410+ * non-localhost peer is 'unknown' rather than false -- CADT and chia can run
411+ * in separate containers where CADT has no access to the chia config.yaml,
412+ * and reporting 'untrusted' there would be wrong (the peer may well be
413+ * trusted via trusted_cidrs we simply can't see).
392414 */
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 ;
415+ const classifyTrust = ( peerHost , nodeId , normalizedTrustedSet , trustedCidrs , configReadable ) => {
416+ if ( isLocalhost ( peerHost ) ) return { trusted : true , reason : 'localhost' } ;
417+ if ( ! configReadable ) return { trusted : 'unknown' , reason : 'chia-config-unavailable' } ;
418+ if ( normalizedTrustedSet && normalizedTrustedSet . has ( normalizeNodeId ( nodeId ) ) ) {
419+ return { trusted : true , reason : 'configured' } ;
420+ }
421+ if ( isTrustedCidr ( peerHost , trustedCidrs ) ) return { trusted : true , reason : 'cidr' } ;
422+ return { trusted : false , reason : null } ;
398423} ;
399424
400425/**
@@ -409,11 +434,17 @@ const buildTrustedPeerView = (connectionsResult, chiaConfigResult) => {
409434 configuredTrustedCidrs : [ ] ,
410435 connected : [ ] ,
411436 hasTrustedConnection : false ,
437+ // True when trust could not be determined -- either the chia config was
438+ // unavailable (so trusted_peers/trusted_cidrs can't be checked) or the
439+ // wallet connections couldn't be enumerated at all. Distinguishes "known
440+ // untrusted" from "can't tell" so callers don't raise a false alarm.
441+ trustUnknown : false ,
412442 } ;
413443
444+ const configReadable = chiaConfigResult ?. ok === true ;
414445 let normalizedTrustedSet = null ;
415446 let trustedCidrs = [ ] ;
416- if ( chiaConfigResult ?. ok ) {
447+ if ( configReadable ) {
417448 const trustedPeerMap = _ . get ( chiaConfigResult . value , 'wallet.trusted_peers' , null ) ;
418449 if ( trustedPeerMap && typeof trustedPeerMap === 'object' ) {
419450 // trusted_peers is `{ <peer_node_id>: <cert_path or 'Does_not_matter'> }`
@@ -435,20 +466,43 @@ const buildTrustedPeerView = (connectionsResult, chiaConfigResult) => {
435466 if ( connectionsResult ?. ok && connectionsResult . value ?. success !== false ) {
436467 const connections = connectionsResult . value ?. connections || [ ] ;
437468 view . connected = connections . map ( ( c ) => {
438- const trustedReason = classifyTrust ( c . peerHost , c . nodeId , normalizedTrustedSet , trustedCidrs ) ;
439- const trusted = trustedReason !== null ;
440- if ( trusted ) view . hasTrustedConnection = true ;
441- return { peerHost : c . peerHost , peerPort : c . peerPort , type : c . type , trusted, trustedReason } ;
469+ const { trusted, reason } = classifyTrust (
470+ c . peerHost ,
471+ c . nodeId ,
472+ normalizedTrustedSet ,
473+ trustedCidrs ,
474+ configReadable ,
475+ ) ;
476+ if ( trusted === true ) view . hasTrustedConnection = true ;
477+ if ( trusted === 'unknown' ) view . trustUnknown = true ;
478+ return { peerHost : c . peerHost , peerPort : c . peerPort , type : c . type , trusted, trustedReason : reason } ;
442479 } ) ;
443480 } else if ( connectionsResult ) {
444481 view . connectionsError = connectionsResult . ok
445482 ? connectionsResult . value ?. error || 'wallet connections unavailable'
446483 : connectionsResult . error ;
484+ // Connections couldn't be enumerated -- we can't tell whether a trusted
485+ // peer exists, so treat it as unknown rather than "definitively untrusted".
486+ view . trustUnknown = true ;
447487 }
448488
449489 return view ;
450490} ;
451491
492+ /**
493+ * Whether to warn that the wallet is not connected to a trusted full-node
494+ * peer. Only warn when we can *definitively* say there is no trusted peer:
495+ * the wallet is reachable, nothing is trusted, and no peer's trust is
496+ * unknown. When trust is unknown (chia config unreadable, e.g. split
497+ * CADT/chia containers) we stay silent rather than raise a false warning.
498+ */
499+ const shouldWarnNoTrustedPeer = ( walletReachable , trustedFullNodePeers ) => {
500+ if ( ! walletReachable || ! trustedFullNodePeers ) return false ;
501+ if ( trustedFullNodePeers . hasTrustedConnection ) return false ;
502+ if ( trustedFullNodePeers . trustUnknown ) return false ;
503+ return true ;
504+ } ;
505+
452506/**
453507 * Build the full /diagnostics response payload.
454508 *
@@ -899,7 +953,7 @@ export const getDiagnosticsResponse = async () => {
899953 }
900954 }
901955
902- if ( walletReachable && walletSection . trustedFullNodePeers ?. hasTrustedConnection === false ) {
956+ if ( shouldWarnNoTrustedPeer ( walletReachable , walletSection . trustedFullNodePeers ) ) {
903957 walletStatus . escalate (
904958 'warning' ,
905959 'Performance is severely degraded when the Chia wallet is not connected to a trusted full node peer' ,
@@ -946,6 +1000,7 @@ export const __test = {
9461000 isLocalhost,
9471001 isTrustedCidr,
9481002 classifyTrust,
1003+ shouldWarnNoTrustedPeer,
9491004 collectOwnedStoreExpectations,
9501005 escalateLostOwnedStores,
9511006 StatusAccumulator,
0 commit comments