@@ -439,24 +439,37 @@ class UsbGuardPromptRuntime {
439439 const usbId = String ( identityCarrier . usbId ?? '' ) . trim ( ) . toLowerCase ( ) ;
440440 const viaPort = String ( identityCarrier . viaPort ?? '' ) . trim ( ) ;
441441 const rule = String ( identityCarrier . rule ?? '' ) ;
442+ const hasStrongSerial = serial && usbId && ! this . _isWeakSerial ( serial ) ;
443+ const hasStrongTopologyIdentity = Boolean ( hash || viaPort ) ;
442444
443445 if ( hash )
444446 add ( `hash:${ hash } ` ) ;
445447 if ( usbId && viaPort )
446448 add ( `id-port:${ usbId } |${ viaPort } ` ) ;
447- if ( serial && usbId )
449+ if ( hasStrongSerial )
448450 add ( `id-serial:${ usbId } |${ serial } ` ) ;
449451
450452 const normalizedRule = rule
451453 . replace ( / ^ \s * ( a l l o w | b l o c k | r e j e c t ) \b / i, '' )
452454 . replace ( / \s + / g, ' ' )
453455 . trim ( ) ;
454- if ( normalizedRule )
456+ if ( normalizedRule && ( hasStrongTopologyIdentity || hasStrongSerial ) )
455457 add ( `rule:${ normalizedRule } ` ) ;
456458
457459 return candidates ;
458460 }
459461
462+ _isWeakSerial ( serial ) {
463+ const value = String ( serial ?? '' ) . trim ( ) . toLowerCase ( ) ;
464+ if ( ! value )
465+ return true ;
466+ if ( / ^ 0 + $ / . test ( value ) )
467+ return true ;
468+ if ( value === 'unknown' || value === 'none' || value === 'n/a' )
469+ return true ;
470+ return false ;
471+ }
472+
460473 _normalizeTargetName ( targetName ) {
461474 const normalized = String ( targetName ?? '' ) . toLowerCase ( ) ;
462475 if ( normalized === 'reject' )
@@ -529,38 +542,24 @@ class UsbGuardPromptRuntime {
529542 return targets ;
530543 }
531544
532- _buildPermanentRuleIdsByIdentity ( rules ) {
533- const idsByIdentity = new Map ( ) ;
534- for ( const [ ruleId , ruleText ] of rules ) {
535- const target = this . _normalizeTargetName ( this . _extractRuleTarget ( ruleText ) ) ;
536- if ( target === 'unknown' )
537- continue ;
545+ _ruleMatchesDeviceForPermanentRemoval ( ruleText , device ) {
546+ const ruleHash = this . _extractRuleField ( ruleText , 'hash' ) ;
547+ if ( ruleHash && device . hash )
548+ return ruleHash === device . hash ;
538549
539- const keys = this . _buildIdentityCandidatesFromRuleText ( ruleText ) ;
540- for ( const key of keys ) {
541- if ( ! idsByIdentity . has ( key ) )
542- idsByIdentity . set ( key , new Set ( ) ) ;
543- idsByIdentity . get ( key ) . add ( ruleId ) ;
544- }
545- }
546- return idsByIdentity ;
547- }
550+ const ruleViaPort = this . _extractRuleField ( ruleText , 'via-port' ) ;
551+ if ( ruleViaPort && device . viaPort )
552+ return ruleViaPort === device . viaPort ;
548553
549- _collectPermanentRuleIdsForDevices ( devices , idsByIdentity ) {
550- const ruleIds = new Set ( ) ;
551- for ( const device of devices ) {
552- const keys = this . _buildIdentityCandidatesFromDevice ( device ) ;
553- for ( const key of keys ) {
554- const ids = idsByIdentity . get ( key ) ;
555- if ( ! ids )
556- continue ;
557- for ( const ruleId of ids )
558- ruleIds . add ( ruleId ) ;
559- // Use the most specific matching identity key only.
560- break ;
561- }
562- }
563- return [ ...ruleIds ] ;
554+ const ruleUsbId = this . _extractRuleUsbId ( ruleText ) ;
555+ const ruleSerial = this . _extractRuleField ( ruleText , 'serial' ) ;
556+ if ( ! ruleUsbId || ! ruleSerial || ! device . usbId || ! device . serial )
557+ return false ;
558+ if ( this . _isWeakSerial ( ruleSerial ) )
559+ return false ;
560+
561+ return ruleUsbId === String ( device . usbId ) . toLowerCase ( ) &&
562+ ruleSerial === String ( device . serial ) ;
564563 }
565564
566565 async _getPermanentTargetByIdentityMap ( ) {
@@ -639,9 +638,20 @@ class UsbGuardPromptRuntime {
639638 return false ;
640639 }
641640
642- const idsByIdentity = this . _buildPermanentRuleIdsByIdentity ( rules ) ;
643- const ruleIds = this . _collectPermanentRuleIdsForDevices ( devices , idsByIdentity ) ;
644- if ( ruleIds . length === 0 )
641+ const ruleIds = new Set ( ) ;
642+ for ( const [ ruleId , ruleText ] of rules ) {
643+ const target = this . _normalizeTargetName ( this . _extractRuleTarget ( ruleText ) ) ;
644+ if ( target === 'unknown' )
645+ continue ;
646+
647+ for ( const device of devices ) {
648+ if ( this . _ruleMatchesDeviceForPermanentRemoval ( ruleText , device ) ) {
649+ ruleIds . add ( ruleId ) ;
650+ break ;
651+ }
652+ }
653+ }
654+ if ( ruleIds . size === 0 )
645655 return true ;
646656
647657 const failedRuleIds = [ ] ;
0 commit comments