Labels: bug
Two related defects in the multi-feed union, both independent of whether
manifest signatures get verified.
1. One feed can shadow the entire union
loadIndexes() sorts indexes by manifest.generatedAt and then lets later
entries overwrite earlier ones per key (src/lib/index-api.js:132-138):
indexes.sort((a, b) => a.manifest.generatedAt - b.manifest.generatedAt);
for (const index of indexes) {
for (const repo of index.repos) repoMap.set(repo.rid, repo);
for (const user of index.users) userMap.set(user.did, user);
}
generatedAt is an unauthenticated integer from the fetched document. A feed
claiming a large generatedAt wins every per-RID and per-DID conflict, so a
single feed in the user's list controls the displayed name, description, and
activity of every repo the others also carry. Since users add feeds themselves
(setIndexFeeds, :29), the failure mode is "adding a second feed silently
replaced my first one's data", with no indication in the UI.
Worth considering: surface per-key provenance (which feed a row came from) and
treat conflicts as presentable ambiguity rather than something to silently
resolve. At minimum, don't let an unverified field decide it.
2. A stale-but-genuine update is undetectable
Canopy fetches bzz://<feedRef>/… (:72-80) and never sees the feed's update
index, so it cannot tell a current update from an older genuine one replayed by
whatever served it. Signature verification does not fix this — a rolled-back
update is correctly signed.
There's in-org precedent for the fix. freedom-adblock-service specifies
downgrade protection in its manifest contract (src/manifest.ts:19):
clients MUST reject a manifest whose version is <= the one they have already
applied (downgrade protection)
…and has clients verify a per-blob sha256 before trusting it (:20-21). The
index protocol has no equivalent, and canopy has no equivalent. Adopting the
sibling service's rule would be consistent rather than novel.
Suggested mitigation: resolve feeds explicitly so the update index is visible
(resolveFeed() in swarmlite's js/src/feeds.js returns { reference, index }),
persist the highest index seen per feed, and refuse to regress. That turns an
undetectable rollback into a visible, reportable staleness condition. It is a
best-effort defense, not a proof — js/src/feeds.js:38-44 is explicit that no
feed reader can prove latestness client-side, and canopy's UI should say so
rather than imply freshness it can't establish.
Happy to PR the monotonicity check alongside the signature work.
Labels: bug
Two related defects in the multi-feed union, both independent of whether
manifest signatures get verified.
1. One feed can shadow the entire union
loadIndexes()sorts indexes bymanifest.generatedAtand then lets laterentries overwrite earlier ones per key (
src/lib/index-api.js:132-138):generatedAtis an unauthenticated integer from the fetched document. A feedclaiming a large
generatedAtwins every per-RID and per-DID conflict, so asingle feed in the user's list controls the displayed name, description, and
activity of every repo the others also carry. Since users add feeds themselves
(
setIndexFeeds,:29), the failure mode is "adding a second feed silentlyreplaced my first one's data", with no indication in the UI.
Worth considering: surface per-key provenance (which feed a row came from) and
treat conflicts as presentable ambiguity rather than something to silently
resolve. At minimum, don't let an unverified field decide it.
2. A stale-but-genuine update is undetectable
Canopy fetches
bzz://<feedRef>/…(:72-80) and never sees the feed's updateindex, so it cannot tell a current update from an older genuine one replayed by
whatever served it. Signature verification does not fix this — a rolled-back
update is correctly signed.
There's in-org precedent for the fix.
freedom-adblock-servicespecifiesdowngrade protection in its manifest contract (
src/manifest.ts:19):…and has clients verify a per-blob
sha256before trusting it (:20-21). Theindex protocol has no equivalent, and canopy has no equivalent. Adopting the
sibling service's rule would be consistent rather than novel.
Suggested mitigation: resolve feeds explicitly so the update index is visible
(
resolveFeed()in swarmlite'sjs/src/feeds.jsreturns{ reference, index }),persist the highest index seen per feed, and refuse to regress. That turns an
undetectable rollback into a visible, reportable staleness condition. It is a
best-effort defense, not a proof —
js/src/feeds.js:38-44is explicit that nofeed reader can prove latestness client-side, and canopy's UI should say so
rather than imply freshness it can't establish.
Happy to PR the monotonicity check alongside the signature work.