Skip to content

Commit 750d912

Browse files
committed
Harden hub rule matching for permanent state isolation
1 parent 79dafc5 commit 750d912

1 file changed

Lines changed: 45 additions & 35 deletions

File tree

extension/extension.js

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -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*(allow|block|reject)\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

Comments
 (0)