paritytech / paritytech/web3-storage
[Provider] Layer-0 read endpoints are unauthenticated, so bucket `visibility` is unenforced
@ilchu is already working on this.
Since Sep 4, 2026.
- Dominant language
- Rust
- Stars
- 13
- Forks
- 3
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 33
Description
Follow-up of https://github.com/paritytech/web3-storage/pull/330
Needs double-checking and investigation before acting on it.
Motivation
#330 introduces bucket visibility (Private ⇒ primaries serve reads only to members) and wires the check into the provider node's auth gate. But the gate only covers the Layer-1 endpoints. The raw Layer-0 read surface serves the same bytes to anyone, so a Private bucket is members-only through /fs/* and /s3/* and wide open through /node, /read, /mmr_peaks. The L0/L1 mismatch itself predates #330 (#228); visibility is what turns it from an inconsistency into an unenforced protocol rule.
This contradicts the design. The replica sync loop (provider-node/src/replica_sync.rs) walks GET /mmr_peaks then GET /node — both ungated — so a replica can sync a private bucket straight from its primaries. docs/design/scalable-web3-storage.md (Bucket Visibility & Access) states the opposite: a replica provider is not a bucket member, so honest primaries refuse it, and a born-private bucket therefore has no honest sync source. That impossibility is what the design calls the strongest privacy configuration, and what the "no on-chain gate on replica creation" rationale rests on. As implemented, it does not hold.
Current state
In provider-node/src/api.rs, only three Layer-0 endpoints check a role:
PUT /node,POST /commit→RequiredRole::WriterPOST /delete→RequiredRole::Admin
Every Layer-0 read endpoint has no auth at all: GET /node, GET /read, GET /chunk_proof, GET /mmr_proof, GET /commitment, GET /checkpoint-signature, GET /mmr_peaks, GET /mmr_subtree, POST /fetch_nodes, POST /exists, GET /buckets, GET /replica/historical_roots, GET /replica/sync_status. Their Layer-1 equivalents in provider-node/src/fs_api.rs and s3_api.rs all go through check_role(..., RequiredRole::Reader).
They split into two groups, which matters for any fix:
- Already bucket-bound — take
bucket_idand use it:/mmr_peaks,/mmr_subtree,/commitment,/checkpoint-signature,/mmr_proof,/exists,/replica/historical_roots,/replica/sync_status. These are gateable today with the samecheck_rolethe fs/s3 handlers use. - Keyed by hash or data root, with no bucket binding —
/node,/read,/chunk_proof, and/fetch_nodes(it acceptsbucket_idbut the handler ignores it). There is no reverse mapping to gate on:crates/providers/storagekeeps nodes in one globalnodescolumn family,check_existstakes_bucket_idand ignores it, and the declaredroot_to_bucketcolumn family is never written or read.
GET /buckets additionally lists every bucket on the node, unauthenticated.
Related
#228 (same question, L0/L1 role mismatch), #304 (auth scheme: binding + replay — orthogonal), #88 (provider validation inventory), #330 (added visibility, gated Layer-1 only), #94 (direct provider protocol).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.