From daf95a25cd476bb3cb14143f1b8e9da3142d3c26 Mon Sep 17 00:00:00 2001 From: rays Date: Fri, 23 Oct 2026 14:56:36 +0000 Subject: [PATCH] Only a subscriber or an admin marks a feed private (#333) patch_feed let private = true through for anyone signed in, as the field's doc comment meant for subscribers, but never checked that the caller subscribed. Ids are slugs of titles, so anyone could mark another person's feed, or a whole OPML (whose shows go with it), private, keeping it out of the directory, and promote a derived feed they did not read out of its OPML; only an admin could undo either. A non-admin now needs a subscription to the feed, or gets 404, as the per-person settings above already do. Co-Authored-By: Claude Opus 5.5 --- crates/ipx/CHANGELOG.md | 1 + crates/ipx/src/web.rs | 5 +++++ tests/ui/app.spec.js | 2 ++ 3 files changed, 8 insertions(+) diff --git a/crates/ipx/CHANGELOG.md b/crates/ipx/CHANGELOG.md index b6bef30..4fd964e 100644 --- a/crates/ipx/CHANGELOG.md +++ b/crates/ipx/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ipx user passwd` signs the account out of every browser, and says how many of its API tokens still work. A session signed in with the old password went on working (#334). - Live updates about a feed go only to the people who read it, and admins. Everyone signed in had every feed's, errors included, which carried a paid feed's address with its key (#325). - An open page stops getting live updates within half a minute of its account being removed. They went on until the page was closed (#335). +- Only someone subscribed to a feed, or an admin, may mark it private. Anyone signed in could keep anyone's feed, or a whole OPML's, out of the directory, and only an admin could undo it (#333). ## [1.4.0] - 2026-10-19 diff --git a/crates/ipx/src/web.rs b/crates/ipx/src/web.rs index 31d6a62..8c487b7 100644 --- a/crates/ipx/src/web.rs +++ b/crates/ipx/src/web.rs @@ -1822,6 +1822,11 @@ async fn patch_feed( "the feed's address, folder, schedule and category, and whether it may be shared, are the same for everyone, so only an admin changes them", )); } + // Marking a feed private is for those who read it: anyone could keep anyone's feed, or a + // whole OPML's, out of the directory, and only an admin could undo it (#333). + if !user.is_admin && state.ctx.db.subscription(user.id, &id).instrument(step("subscription")).await?.is_none() { + return Err(ApiError::not_found("you do not subscribe to that feed")); + } let mut cfg = state.ctx.edit_cfg().await; // Derived feeds have no config entry. Editing one is the moment it earns a real diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index 2039afa..2754b11 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -694,6 +694,8 @@ test('a second person has their own feeds and their own read state', async ({ br // Hiding the way in is not the guard; the server is. expect((await page.request.get('/api/users')).status()).toBe(403); expect((await page.request.get('/api/logs')).status()).toBe(403); + // Nor may Sam mark a feed they do not read private, keeping it out of the directory (#333). + expect((await page.request.patch('/api/feeds/test-show', { data: { private: true } })).status()).toBe(404); // Subscribing to a feed the admin already has costs no second fetch: same feed, same // files, but Sam's own read state.