Repository navigation
fix(privacy): reuse the reference of a private address this device holds - #497
Merged
Merged
Conversation
Deploying podnotes with
|
| Latest commit: |
21c8682
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f7a72457.podnotes.pages.dev |
| Branch Preview URL: | https://fix-reuse-feed-secret.podnotes.pages.dev |
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 free
to 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.
Why
A device still on 2.21.1 can sync a data.json that holds a raw private feed address. Every read that protected that address stored it under a new
podnotes-feed-url-<uuid>secret. PodNotes reads one incoming change two or three times, and SecretStorage's API has no delete, so each change left IDs listed for good. Protecting an address this device already holds now reuses its reference.Fixes #490
What changed
FeedUrlRepository.storereturns the PodNotes ID that already resolves to the URL on this device, without writing it again. It mints a new UUID only when no such ID exists.Scope
Tradeoffs
removePodcastclears a reference only when no other feed holds it, and the startup sweep clears only unreferenced IDs. No path rewrites a private feed's address in place, becauseattachrefuses a slot that holds another URL. If the shared address is missing on a device, setting it for one feed sets it for both, which is right for two feeds with one address.Blast Radius
Only the code that protects a raw private address calls
store: subscribe, OPML import, settings import, and load and sync adoption. Each call now lists SecretStorage once. #489's behavior is unchanged. Each incoming raw change gets one protective save, and nothing raw stays on disk.Verification
storecalls per change. Two came from overlapping change notifications and one from the fresh read before the protective save.sync-private-address.test.tsfail on the master bundle, with 9 and 6 new IDs for 3 changes, and pass with the fix. The new unit tests fail on master too.count:writesmatches origin/master 407bf43 for a normal session. Both record 0, 0, 1, 2 completed writes for idle time, opening the view, and two settings edits.npm run lint,format:check,typecheck,build,test(2,761 tests),docs:build, andtest:tooling(35 tests) under Node 22.23.2.npm run test:e2epassed all 230 tests from a fresh vault on 1.14.4 at this head.npm run sizereports 102,860 gzip bytes, up 17 from origin/master 407bf43 (102,843) and 140 under the budget.Mutation checks
Each wrong alternative fails at least one test.
main.persistence.test.ts, and both e2e triggers.attachwould refuse that ID, so the feed would keep its address for this session only.removePodcast, fails "keeps a private feed's address while another saved podcast uses it" and "keeps the address of a podcast protected with the same private URL as a removed one".The local fixture address is
http://priv3.supercast.com:<port>/feeds/FIX488_ADOPTED_SENTINEL. Logs, probe and mutation scripts, and check output hold the full results.Docs at release
None.
Note
Medium Risk
Changes how private feed URLs are stored in SecretStorage; every
storenow scanslistSecrets, and shared references affect removal semantics for duplicate addresses.Overview
Fixes SecretStorage reference sprawl when synced
data.jsonkeeps delivering the same raw private feed URL (e.g. from 2.21.1). Each protective read used to mint a newpodnotes-feed-url-<uuid>; with no real delete in SecretStorage, those IDs accumulated forever.FeedUrlRepository.storenow lists secrets and returns an existing PodNotes ID that already resolves to the trimmed URL on this device, only allocating a new UUID when none match. Cleared IDs, foreign-plugin keys, and slots holding a different URL are still never reused (documented in the class comment). Identical addresses on two saved feeds can share one reference; removal behavior for that case is covered by new library tests.Tests and e2e now assert one reference across repeated sync/handlers and plugin reload; persistence tests cover concurrent
onExternalSettingsChangecalls.Reviewed by Cursor Bugbot for commit 21c8682. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix
FeedUrlRepository.storeto reuse existing private-address referencesFeedUrlRepository.storenow lists available secrets and returns the first PodNotes-owned reference that resolves to the trimmed URL. A fresh UUID-based reference is still created when no valid match exists.storenow performs a secret listing and lookup on every call. If a stored reference resolves to a different value than expected, a new reference is allocated instead of reusing it — check the matching logic inFeedUrlRepository.storeif duplicate secret IDs appear.Macroscope summarized 21c8682.