ScanBroadcaster.checkPredecessor (pkg/hold/pds/scan_broadcaster.go, ~line 1449)
returns true whenever a hold's captain record has a non-empty successor
field, without checking whether that successor is this hold. Result: every
hold on the network that has declared a successor (i.e. has been migrated)
gets its manifests queued for scanning by every other hold running proactive
discovery.
Repro
- Hold A (did:web:hold01.atcr.io) has captain.successor = "did:plc:wnbpdx...".
- Hold B (did:web:hold.styx.mrijke.nl) runs with scanner.rescan_interval > 0.
- A pusher pushes an image to hold A; manifest.holdDid = did:web:hold01.atcr.io.
- Hold B's discovery loop walks the pusher's PDS, sees the manifest, calls isOurManifest("did:web:hold01.atcr.io").
- isOurManifest misses the direct match, falls through to checkPredecessor, fetches hold A's captain record, sees successor != "", returns true.
- Hold B queues the manifest for scanning. Scanner fetches config blob from hold B's S3 → 404 (blob was never in hold B's storage).
Impact Any operator running a scanner with rescan_interval > 0 ends up scanning the migration backlog of every other migrated hold on the network. Scanner logs flood with 404 errors for foreign images; operator pays for Syft/Grype CPU and bandwidth on other people's images.
Fix: checkPredecessor should compare captain.Successor == sb.holdDID before returning true.