Skip to content

fix(cache): gate capsule landing on request provenance, not loopback (#1654) - #141

Merged
MichaelTaylor3d merged 2 commits into
mainfrom
fix/1654-gate-capsule-landing-provenance
Aug 2, 2026
Merged

fix(cache): gate capsule landing on request provenance, not loopback (#1654)#141
MichaelTaylor3d merged 2 commits into
mainfrom
fix/1654-gate-capsule-landing-provenance

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

What changed

Closes DIG-Network/dig_ecosystem#1654.

A capsule landing (cache write) is what makes this node a discoverable DHT holder (refresh_dht_inventory, SPEC §14.3/§21.3) — a durable side effect. Those side effects were gated on the peer address being loopback, but "the peer address is loopback" is not the same predicate as "the operator asked for this." Two doors let an unauthorized caller trigger a landing:

  1. Browser CSRF on /s/*path. A cross-origin no-cors GET to http://localhost:<port>/s/... runs server-side regardless of CORS; http://localhost is potentially-trustworthy (not mixed-content-blocked). Chrome's PNA rejects it, but Firefox/Safari have no PNA — so a drive-by page could make the visitor's node fetch, cache, and DHT-announce attacker-chosen capsules.
  2. cache.fetchAndCache had no token gate on the HTTP surface (it is neither control.* nor wallet.*, so it fell through to dispatch untokened) — the same landing primitive, explicit.

The fix — provenance is a second, orthogonal axis over ReadOrigin

  • Door 1 — RequestProvenance { FirstParty, CrossSite }, derived from Sec-Fetch-Site. The effective landing predicate becomes origin == Local && provenance == FirstParty. The read always serves (consumption stays frictionless per §6.0); only the durable side effect (backfill / reshare / announce) is skipped for a cross-site request. Parser rule (load-bearing): cross-site ⇒ CrossSite; same-origin | same-site | none ⇒ FirstParty; absent header ⇒ FirstParty — CLI/SDK send no Sec-Fetch-*, so absence must never be treated as cross-site or frictionless reads would break. Only an explicit cross-site denies the side effect.
  • Door 2 — cache.fetchAndCache now requires the local control token (master or paired), gated in server.rs::rpc alongside control.*. The in-process FFI path bypasses rpc(), so the DIG Browser's open cache.* contract is unaffected.
  • Door 3 (reverse-proxy loopback impersonation) is open by construction and documented (SPEC §21.8), not falsely "solved": the Local label trusts the loopback boundary; X-Forwarded-For is explicitly not trusted; a trusted-proxy deployment must not expose the loopback port to untrusted networks.

ReadOrigin::{Local,Peer} (PR#108) stays as the transport axis; this is a second axis (provenance within a local request).

Verification

  • Parser tests (download.rs): sec_fetch_site_cross_site_is_cross_site, ..first_party_values_are_first_party, ..absent_is_first_party (the frictionless-CLI guard), ..is_case_insensitive_and_trims, ..unknown_value_is_first_party.
  • Integration tests (content_serve.rs +105, server.rs +40): a cross-site /s/ read serves bytes but performs no warm and no announce; a same-site / header-absent read still serves and lands; an untokened cache.fetchAndCache is refused (Unauthorized) while a tokened one dispatches.
  • SPEC.md §21.8 ("Landing has a SECOND axis — request provenance") added; the door-3 trusted-proxy caveat + the honest residuals (pre-Sec-Fetch UAs; shared-origin store-to-store landing) documented.

Version

fix: (security patch — tightens the cache.fetchAndCache HTTP contract; authorized callers via paired token / control token / FFI are unaffected) → [workspace.package].version 0.74.3 → 0.74.4.


Generated by Claude Code

MichaelTaylor3d and others added 2 commits August 2, 2026 09:59
…1654)

First failing-test anchor for gating capsule-landing side effects on request
provenance (Sec-Fetch-Site) rather than a loopback peer address.

Refs #1654
Co-Authored-By: Claude <noreply@anthropic.com>
…(#1654)

A loopback peer address proves the connection is local, not that the operator
authorized the request. A cross-site browser navigation (GET dig.local/s/<capsule>)
would otherwise LAND the attacker's chosen capsule (warm + reshare + DHT
holder-announce, SPEC §14.3/§21.3) — a remotely-triggerable amplification. The
bytes are harmless (public content); the durable holder side effect is the door.

- New RequestProvenance axis parsed from Sec-Fetch-Site (download.rs): only an
  explicit cross-site denies landing; absence (CLI/SDK) and every other value are
  first-party. Threaded into ContentServer::serve_content_plaintext + collapsed once
  via landing_origin(origin, provenance) so a cross-site read serves but folds
  landing to Peer. Reads are never blocked.
- cache.fetchAndCache over HTTP POST / now requires the control/paired token (it is
  an explicit become-a-holder call); the in-process FFI cache.* path stays open.
- SPEC (service §21.8/§21.9/§21.10 + core seam note), README, DEVELOPMENT_LOG.

Version bumped 0.74.3 -> 0.74.4.

Closes #1654
Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3d merged commit 8ae617f into main Aug 2, 2026
15 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the fix/1654-gate-capsule-landing-provenance branch August 2, 2026 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant