fix(sync): bind a synced capsule to its chain-anchored root before it lands (and is announced) - #138
Merged
MichaelTaylor3d merged 1 commit intoAug 2, 2026
Conversation
… (#1623)
The whole-store sync (`sync_module_from` -> `cache_fetch_and_cache`) wrote a
downloaded module straight to the cache, and the mere existence of that file
makes this node a discoverable DHT holder (SPEC §14.1). The backfill then
announced the node as a holder WITHOUT ever binding the bytes to a
chain-confirmed root, so the node advertised itself as a holder of unverified
content — poisoning holder reputation and multiplying unverified copies through
the #1576 reshare flywheel.
Reuse the #1576 reshare leg's `ChainAnchoredModuleVerifier` at the sync seam:
resolve the store's chain-anchored root from the chain (never the serving peer),
re-hash the downloaded module against it, and refuse anything that is not the
store's confirmed generation BEFORE the write. An unverified capsule therefore
never lands and is never announced — matching the reshare leg, which rejects
outright rather than caching-but-not-announcing (one verification shape at every
seam that admits a capsule; defense in depth, never relying on a downstream gate).
The stale rationale on `sync_module_from` ("a tampered module fails the SERVE
gate, not this sync") no longer holds: the serve gate never runs for a peer that
only learns this node holds the capsule from its DHT announce.
The local-serve verify (content_serve) is untouched — it already verifies before
decrypt; this is about the announce/holder-reputation seam.
Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
deleted the
fix/1623-chain-anchored-verify-before-announce
branch
August 2, 2026 08:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Closes DIG-Network/dig_ecosystem#1623.
Whole-store sync landed a capsule to disk and the backfill then announced this node as a DHT holder of it — without ever binding the bytes to a chain-confirmed root.
sync_module_fromonly ever checked the served bytes were non-empty (download_capsuletrustedwant_root; theclone_whole_storefallback verified emptiness only). Because landing a capsule is what makes this node a discoverable holder (SPEC §14.1), the node advertised itself as a holder of content no chain confirmed — poisoning holder reputation and multiplying unverified copies through the reshare flywheel (#1576), regardless of the downstream read-serve gate.The synced module is now bound to the store's chain-anchored generation before it lands, using the same verification the #1576 reshare leg applies at its own seam:
Node::verify_synced_capsule_is_chain_anchored— resolves the generation root through the chain (anchored_root_resolver, never from the serving peer), refuses a served root that is not the chain generation (a moved/forged head), then reusesChainAnchoredModuleVerifier::for_generation+dig_download::ModuleAnchorVerifier::verify_module_anchorto re-hash the bytes against the anchor.sync_module_fromimmediately beforewrite_atomic. An unverified capsule never reaches disk, and since the announce (refresh_dht_inventory) fires only on a fresh land insidecache_fetch_and_cache, no-land ⇒ no-announce by construction.ModuleAnchor::Unavailable→Err).Design decision — reject, don't cache
Reject outright, matching the reshare leg (which stages outside the cache and promotes only on success). The gate is central — production reaches sync only through
cache_fetch_and_cache— so every sync caller is covered by one verification shape, and an unverified module never touches the cache whose mere presence is an announcement. This is defense-in-depth: verify at the seam that admits the artifact, not reliance on the downstream serve gate that may not run before an announce.Blast radius
sync_module_from(lib.rs) is the single funnel every sync caller reaches throughcache_fetch_and_cache; the new gate is a pre-write_atomicguard on that one path. The announce (refresh_dht_inventory) and the local-serve verify (content_serve.rs, untouched — it already verifies before decrypt) are unaffected. ReusesChainAnchoredModuleVerifier/ModuleAnchorVerifierunchanged.Verification
a_capsule_whose_root_is_not_chain_confirmed_is_never_announced— installs an inventory-refresher spy; proves an unconfirmed capsule errs, does not land, and never announces. With the gate call removed the test FAILS for the right reason (Ok((93, "1010…"))— the capsule lands + announces); restoring it passes.a_chain_confirmed_capsule_lands_and_is_announced— the fail-closed-not-fail-shut companion (a legitimate chain-confirmed capsule still lands + announces)..digdata sections (they had asserted the buggy "land unverified fixture bytes" behavior).cargo fmt --all -- --check→ exit 0 ·cargo clippy -p dig-node-core --all-targets --all-features -D warnings→ clean · all 14 sync-path tests pass. (9 pre-existing port-binding/dual-stack env tests fail identically on a clean tree — not from this diff; they pass in CI.)SPEC.md§14.3 "Verify-before-announce invariant" +DEVELOPMENT_LOG.mdentry added in the same unit of work.Version
fix:(security bug fix, backwards-compatible) → patch bump[workspace.package].version0.74.1 → 0.74.2.Generated by Claude Code