Uh oh!
There was an error while loading. Please reload this page.
fix(dig-node): enforce chain-anchor uniformly across local, peer-serve, gateway paths (#1764, #1765) - #188
Merged
Conversation
Co-Authored-By: Claude <noreply@anthropic.com>
The peer-serve arm (dig.fetchRange) validated only request shape then served any range that Merkle-verified against the CLIENT-named root, so a peer could fetch bytes of a forged or superseded generation that the local /s and dig.getContent read paths already refuse. Factor the read arm's #127 pin resolution into a shared resolve_enforced_pin helper and apply it to BOTH the dig.getContent read arm (pure refactor) and the dig.fetchRange serve arm (the fix), so one policy governs read and serve with no drift. Co-Authored-By: Claude <noreply@anthropic.com>
… paths (#1765) Unit 2 of the anchor-enforcement-coherence work. Adds the remaining coverage + coherence on top of the Unit 1 serve-arm gate (#1764): - serve_content_plaintext_fails_closed_for_an_unanchored_store_1765: the local /s HTTP tier refuses an unanchored store (Ok(None)) with ROOT_NOT_ANCHORED, completing the uniform-refusal proof across all three serve paths. - serve_content_plaintext_refuses_unanchored_identically_with_and_without_a_gateway_1765: the #1765 face — an unanchored read fails closed identically whether or not a gateway upstream is configured (the anchor gate precedes tier selection). - cold_start_gateway_serve integration test now also asserts x-dig-verified:false on the gateway leg under DIG_NODE_PIN=off, proving source is orthogonal to verified (no code change needed — verified is threaded uniformly to all legs). SPEC.md: anchor pin applies uniformly to dig.fetchRange (serve) and every read leg; x-dig-source is orthogonal to x-dig-verified; -32005 row updated. DEVELOPMENT_LOG.md: the fetchRange serve arm was the missing half of the invariant. Version bump 0.99.3 -> 0.99.4 (patch, fix:). Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3dforce-pushed
the
fix/1764-anchor-enforcement-coherence
branch
from
August 6, 2026 04:39
4ccae12 to
251820dCompareUh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Closes DIG-Network/dig_ecosystem#1764. Closes DIG-Network/dig_ecosystem#1765.
Unifies chain-anchor enforcement across all three content-serve paths so "chain-anchor verified" has ONE meaning. From the #1062 live demo: the same capsule was refused over local
/s(502 … pinned root unverifiable) but served happily over peer-RPCdig.fetchRange(200 x-dig-source: peer), and a reachable gateway 404'd unanchored content that a peer would serve.Root cause (single)
The read side (
/sHTTP,dig.getContent, the reader whole-resource bind) all applied the #127 fail-closed anchor pin. The serve side — thedig.fetchRangepeer-serve arm — applied no anchor gate: it validated request shape then served any range that Merkle-verified against the client-named root, delegating anchor authority entirely to the reader. That violated the SPEC invariant "the node MUST NOT serve content under a root it has not confirmed on-chain," which every other path honored — so a holder served unanchored/forged-generation bytes the local path refuses.Fix (Policy A — unify on the existing strict fail-closed invariant)
dig.fetchRangenow resolves the same anchor pin before serving. The fix(service): anchor identity + cache under the state dir, not $HOME #127 pin resolution thedig.getContentread arm used is factored into a sharedresolve_enforced_pinhelper and applied to both arms — one policy governs read and serve, no drift.dig.getContentbehaviour is byte-identical (pure refactor there).Ok(None)) is refused with-32005 ROOT_NOT_ANCHOREDuniformly on/s,dig.getContent, anddig.fetchRange. This removes the peer-serve-vs-gateway-fallback mutual-exclusivity — the serve side no longer disagrees with the read side, so the outcome is identical whether the gateway is reachable or not.x-dig-source(local|peer|rpc) reports which tier served the bytes; it is orthogonal tox-dig-verified, which reports only whether the bytes were bound to the chain-anchored root. A peer- or gateway-served resource is stillx-dig-verified: truebecause the reader re-binds to the anchor.x-dig-verified: falseappears only under theDIG_NODE_PIN=offdev opt-out, now emitted consistently across legs.Anti-rollback preserved (no #127/#2088 regression):
Ok(Some(tip))with a request root ≠ tip (a superseded/forged root — the real rollback attack) still hard-rejects-32005;Ok(None)cannot be a rollback;Err(chain unreachable) stays fail-closed. The serve arm gaining the gate only tightens. The genericdig-downloadcrate is unchanged — the anchor policy lives in dig-node.Tests (TDD)
dig.fetchRangefails closed for an unanchored store, for an unreachable chain, and for a superseded client root (rollback code).dig.getContent(read) anddig.fetchRange(serve)./sHTTP tier, peer, and gateway paths (content_serve.rs) — the #1765 face, gateway reachable vs unreachable now identical.latest_rootregressions kept green.Blast radius
crates/dig-node-core/src/seams/dig_rpc/dispatch.rs(dig.fetchRange+dig.getContentarms, the sharedresolve_enforced_pin),crates/dig-node-core/src/lib.rs(pin resolution + tests),crates/dig-node-service/tests/content_serve.rs. Downstreamx-dig-verified/x-dig-sourceconsumers (extension/browser shields) read but do not set these headers — no cross-repo code change; the SPEC/docs/SYSTEM.md coherence sweep is a companion.Version
root
[workspace.package].version0.99.3 → 0.99.4 (patch,fix:— tightens a serve path to the already-documented invariant; no wire/API/schema break; unanchored content that previously got a spurious200over peer-serve now correctly returns-32005).Cargo.lockregenerated. SPEC.md §4/§5 + the-32005row + DEVELOPMENT_LOG updated.Generated by Claude Code