fix: route dign open through dig-urn-resolver (§5.3 ladder + branded errors) (#745) - #50
Conversation
092c540 to
87da503
Compare
…(#745) `dig-node/dign open <urn|chia://>` hard-rolled a single `http://localhost:9778/s/<store>:<root>/<path>` URL and surfaced the local node's raw `502: could not read the store's on-chain root` when that tier could not serve. Route it instead through the canonical dig-urn-resolver — the §5.3 ladder (dig.local -> localhost -> rpc.dig.net) with fail-closed verify — so it resolves EVEN WHEN the local /s/ chain-read 502s (#747, non-blocking), matching the extension URN bar (#308) and every other URN client (#668). On a verified Success it opens the best browser-navigable tier (.dig -> dig.local -> localhost, cheaply probed, first that serves). When no local tier can serve while the resolver succeeded via rpc, it serves the already-verified bytes over an EPHEMERAL LOOPBACK HTTP endpoint (http://127.0.0.1:<port>) and opens that. On IntegrityFailure/Unreachable/hard error it serves a branded DIG error asset from the resolver (never a raw 502 string). SECURITY (#745, closes the review-gate RCE): resolved bytes + content type + resource name are all attacker-controlled and 'verified' means chain-inclusion, NOT safe. The command NEVER writes resolved bytes to disk and NEVER OS-opens a local file (which would run an attacker .hta/.js with no Mark-of-the-Web -> RCE, or give HTML a file:// origin). It serves them over a sandboxed loopback http origin so the browser applies its normal download/MOTW/origin handling; attacker-influenced header values are stripped of CR/LF. The strict untrusted- input gate still runs FIRST — a hostile link never reaches the resolver or the launcher. The dig-urn-resolver dependency is in dig-node-service (the client shell) ONLY, never dig-node-core (the server engine), per the #668/#745 layering exception. Depends on the published crates.io dig-urn-resolver 0.3.1 (no git dep, #681). SPEC.md §8.5 updated. Version 0.38.2 -> 0.38.3 (patch: fix). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87da503 to
d796e8e
Compare
Security fix pushed — RCE closed (fix (a): serve over ephemeral loopback HTTP)Addressed the HIGH/near-CRITICAL RCE the security gate found in the Chosen: preferred fix (a). The content-fallback (and every branded-error case) now serves the bytes over an ephemeral Server hardening: Regression test added: Fail-closed + branded-error behavior (which passed) is preserved; the strict untrusted-input gate still runs FIRST (hostile link never reaches resolver/launcher/server). Evidence (local, ninja generator): Please re-run the security + review gate. Do NOT merge. |
TLDR
Fixes P0 #745:
dig-node/dign open <urn|chia://>no longer hard-rolls a singlehttp://localhost:9778/s/…URL and no longer surfaces the raw502: could not read the store's on-chain root. It now routes through the canonical dig-urn-resolver (§5.3 ladder + fail-closed verify) — resolving even when the local/s/tier 502s (#747, non-blocking) — and shows a branded DIG error asset when content can't load.Success-open flow (chosen + why)
On a verified
Success, probe the browser-navigable tiers in §5.3 order and open the FIRST that actually serves (matches the extension URN bar #308):http://<storeId>.dig/<path>— offered ONLY for a rootless link (the host can't pin a capsule root);http://dig.local/s/<storeId>[:<root>]/<path>;http://localhost:9778/s/<storeId>[:<root>]/<path>.If NO browser tier can serve it (e.g. the local chain-read 502s per #747) but the resolver still returned VERIFIED bytes via rpc.dig.net, write those verified bytes to a temp file and open THAT. Rationale: the resolver has already fail-closed-verified the bytes, so the user always sees the exact verified content and NEVER a raw error — while still preferring live browser navigation when a tier can serve.
Branded errors
IntegrityFailure/Unreachable/ hard resolve errors (not-found / rpc) → a static branded PNG fromdig_urn_resolver::images(assets/), written to a temp file + opened. No hand-rolled page, no raw502string. (Asserted: the integrity case writes exactlyimages::png(Integrity)and never probes/opens a browser tier.)Layering (the sanctioned #668/#745 exception)
The dig-urn-resolver dependency is in dig-node-service ONLY (the client shell's open-command path) — confirmed NOT in dig-node-core (the server engine), where it would be the real cycle. Depends on the published crates.io
dig-urn-resolver = "0.3.1"(no git dep, #681). The resolver's public API crosses onlyString/Vec<u8>, so its internal (crates.io) digstore-core instance never interoperates with core's git-pinned one.Security (audit notes)
USAGEand NEVER reaches the resolver or the launcher (regression testhostile_link_rejected_before_resolve_or_launch).Success; unverified/tampered bytes are never opened (the branded integrity asset is a static const PNG).Blast radius (gitnexus + review)
Single symbol group:
dig_node_service::open(run/run_with+ newopen_success/open_branded/candidate_urls). Sole caller isentrypoint.rs(Command::Open→open::run), whose signature is unchanged. No other module importsopen::. dig-node-core untouched.Version
0.38.2→0.38.3(patch —fix:, no public-API break). apps/nightlies: rides the next nightly; a stable tag is a manual dispatch.Evidence (this session, local Windows build via ninja generator)
cargo test -p dig-node-service open::→ 15 passed (success-tier selection, .dig-priority, root-pin-skips-.dig, verified-bytes fallback, integrity/unreachable/not-found branded assets, hostile-input security, URN reconstruction, ext derivation, preserved normalize validation).cargo test -p dig-node-service→ 216 lib + all integration passed, 0 failed.cargo fmt -- --checkclean;cargo clippy --all-targets -- -D warningsclean;cargo buildgreen.Do NOT merge — for the review + security gate.
🤖 Generated with Claude Code