Releases: Fripix/Frirss
Release list
v1.4.10
Article text is now extracted once for the whole instance instead of once per device, FriRSS speaks a tenth language, and a row can take you straight to the source.
✨ New
- Open an article at its source. An icon on each row opens the origin site in a new tab and selects the article, so it is marked read and keeps its place in the list. Asked for in #11.
- Choose which action icons a row shows. A new Layout section hides any of the four, one by one. Hiding Favorite or Read later also removes the long press that files an article into a category from the list; the section says so.
- 简体中文. Simplified Chinese is the tenth interface language, contributed by @zhhbinn in #12, with terminology following FreshRSS's own Chinese UI.
⚡ Reading
- Article text is extracted once for the whole instance. Ten readers of the same feeds used to mean ten identical extractions and ten requests to the origin site. With Redis the result is shared across devices and accounts; without it the server still extracts, it simply keeps nothing.
- Ten devices opening the same article at the same moment now trigger a single extraction.
- The article appears immediately, with no waiting screen. On auto-extract feeds the pane showed a grey rectangle while the full text loaded, although the feed's own content was already there.
- Swiping between articles is fluid again, and the next articles' images are ready before you reach them — fetched from their own site, not through the server.
- Ten articles prepared ahead instead of five, still one at a time.
🌍 Languages
- Dates finally appear in your language. Formatting had stopped at two: outside French everything fell back to the US format, so seven locales showed English dates in an otherwise translated interface.
- The page declares the interface language. It announced French whatever you had chosen — and screen readers take their voice and pronunciation rules from that attribute.
- Three strings were stuck in one language, including the top progress bar, which announced "Loading" to screen readers in every language. As it carries no text, that was the only word they spoke.
🔒 Security
- A site opened from FriRSS can no longer reach back into the tab left behind. The
Oshortcut and Open site opened a tab that kept a handle on FriRSS, which the target page could redirect. Links inside articles were already protected; these two were not. - Article URLs no longer reach the server log, neither in the access log nor in error lines. A signed CDN URL cannot end up there in the clear either.
- A DNS outage no longer poses as a refused server. A single lost packet during setup used to announce that the address pointed inside your network and had been blocked.
🐛 Fixes
- The article body no longer flickers while scrolling in the iOS PWA, where images disappeared and came back dozens of times a second.
- No more green band beside video thumbnails on wide screens, where the thumbnail was displayed at its own size instead of filling its frame.
- Smoother scrolling in the reading pane — the progress bar is measured once per image, not on every scroll event.
🧹 Under the hood
- Server-side extraction can no longer block the instance. Parsing a page freezes the single process that serves everyone, so the queue is now bounded and a full loop turn is handed back between two parses.
- Offline preparation keeps the articles the rate limit used to refuse it, when the server says when to come back.
- The access log records the path only, never the query string.
Upgrading
Nothing to do: no new environment variable, no migration.
One thing to check if you had lowered FRIRSS_PROXY_RATE_LIMIT: article extraction now draws on the same budget as the proxy instead of a separate one, which was a defect — two buckets silently doubled the stated cap.
Full detail in the CHANGELOG · image ghcr.io/fripix/frirss:1.4.10
v1.4.9
A security release: one dependency update, nothing else.
🔒 Security
qsupdated to 6.16.0, clearing two moderate advisories in the library Express uses to parse URL parameters: an array-limit bypass via bracket-key comma parsing (GHSA-x5fp-wj9c-mxmx) and a denial of service via an attacker-controlledisBuffer(GHSA-4mjr-xmp4-gh2g). Note that 6.15.3 was still affected — only 6.16.0 clears both. This was the sole vulnerability the published image carried; everything elsenpm auditreports lives in development dependencies, which are pruned out of the image at build time.
Nothing to do on upgrade, and no behaviour changes.
Full detail in the CHANGELOG. Image: ghcr.io/fripix/frirss:1.4.9.
v1.4.8
A short cycle: swiping lands on a whole page, and three long-standing bugs that quietly did the wrong thing are gone.
✨ New
- The next articles are warmed ahead of you, images included. On a feed set to auto-extract, FriRSS already prefetched the text of the articles you were about to reach — but not their images, so they arrived late and pushed the text down, which was very visible swiping through the iPhone PWA. The warm-up now covers body images too, and reaches ten articles ahead instead of five. It obeys your offline-images setting: on "none", nothing is downloaded.
🐛 Fixes
- Switching feeds mid-load no longer mixes two lists. When an infinite-scroll page arrived after you had moved to another feed, its articles were appended to the list on screen — a different feed's — and the mixture was written to the cache of the view you had left, so it came back on the next load, offline included. A page whose view is gone is now discarded.
- Infinite scroll inside a search stays inside the search. Reaching the bottom of a result list loaded more of the feed rather than more of the query, quietly mixing unrelated articles under a search box still showing your terms. Pagination now continues the search, in the same scope (feed, category, favorites, read-later).
- A FreshRSS problem no longer signs you out of FriRSS. An expired FreshRSS session, or an API password changed on the server, closed your FriRSS session and sent you back to the login screen although your FriRSS account was perfectly valid. The two authentications are now told apart: only your FriRSS session expiring signs you out.
Full detail in the CHANGELOG. Image: ghcr.io/fripix/frirss:1.4.8.
v1.4.7
Ticking an article read now clears it from the unread list — and a run of write bugs that had been failing quietly is fixed along the way.
✨ New
- Marking an article read removes it from the "Unread" list. Ticking the ✓ had no visible effect under the very filter it was about. The row now goes immediately; if the server refuses the write, it comes back where it was. (#10)
- Opening an article keeps its row. Only a deliberate tick removes one — reading, and mark-as-read-on-scroll, leave the list alone so you keep your place.
- The ✓ is now in the compact layout, where it was missing while every other layout had it.
- An empty list offers to load the next page. A view with nothing left to show while the feed is not exhausted — filtering on favorites in a feed whose first articles are not starred, say — used to claim there was nothing more. It now says so plainly and offers to go on.
🐛 Fixes
- A stale write token no longer breaks every write. FreshRSS's CSRF token was fetched once per browser session and never refreshed, so the moment it expired — FreshRSS session renewed, server restarted — every mark, star and read-later failed silently until you reloaded the page. FriRSS now fetches a fresh token and replays the write once.
- A lost write says so. A write the server refuses, or one queued while your connection is fine, used to leave no trace on screen. A message now explains both. Genuinely offline, nothing appears — the offline banner already covers it.
- Removing "read later" no longer loses the article when the server refuses. From that view the row left before the server had answered; on a refusal it never came back, and the counter announced an item above an empty list. Same correction the favorites got in 1.4.4.
- A row removed from "read later" no longer returns when you leave the view and come back.
- No more flicker when a day empties. Marking the last article of a date band read replayed the entry animation on every row of the following days.
Full detail in the CHANGELOG. Image: ghcr.io/fripix/frirss:1.4.7.
v1.4.6 — say why the connection failed
A small release with one purpose: when connecting a FreshRSS server fails, the screen now tells you why.
🐛 Fixes
- A FreshRSS on a private address now says so. Self-hosted instances usually sit on a LAN or a Docker network, and the proxy refuses private targets by default — the screen only said "failed to connect". It now names the block and the variable that lifts it,
PROXY_INTERNAL_HOSTS. (#8) - A refused API password is reported as a refusal, not an outage. FreshRSS answers the same way whether the API password is wrong or was never set, so the message covers both — and points out that this password is separate from your account password.
- Everything else stays generic, deliberately. An unreachable host, a dropped connection or an expired FriRSS session are never blamed on your credentials: only a cause the backend actually verified gets named.
📖 Documentation
- The install guide now covers the private-address case where you actually meet it, with the line to add and the log signature to look for.
- The Unraid Community Applications template gained the field.
PROXY_INTERNAL_HOSTSis offered up front instead of missing entirely, which is why it went unnoticed.
Existing containers keep their own copy of the Unraid template, so add the variable by hand there — a template update only reaches new installs.
Full detail in the CHANGELOG. Image: ghcr.io/fripix/frirss:1.4.6.
v1.4.5 — the interface pass
The interface pass. A full review of FriRSS's UI turned into this cycle: six themes instead of one, a command palette, and a long list of things that were quietly broken for anyone using a keyboard, a finger, or an eye that separates greys poorly.
🎨 Themes
- Six themes ship with it — Default, Riso (white paper, one fluorescent ink), Paper (sepia) and High Contrast in light; Night and Desk in dark.
- Follow your system — pick a light theme and a dark one, and FriRSS switches with the device, keeping your font sizes.
- Shipped themes now update — a preset used to freeze at the version you first received, so a fix to one never reached you.
⌨️ Reach
- A command palette on ⌘K / Ctrl+K — jump to a feed, category, label or view, switch FreshRSS server, run an action. Accents are ignored, so securite finds Sécurité.
- A shortcut sheet on
?, showing the keys you actually configured. - Keyboard focus is visible — it was not, anywhere: 137 buttons, four focus rings, all four on the login screen.
- Touch targets reach 44pt, icon-only buttons have names for screen readers, and reduced motion is honored everywhere.
✨ New
- Manage your categories — rename, delete, and move feeds between them, from Preferences → Feeds.
- Mark as read while scrolling, optionally. Off by default, never during a search.
- Share an article or copy its link — the system share sheet on mobile, the clipboard elsewhere.
- Confirmation messages — the app never used to say an action had worked.
- Recent searches, five per server.
🐛 Fixes
- iOS no longer zooms when you tap a text field — the rule meant to prevent it was losing to a CSS class.
- Text on filled buttons is readable — white was hardcoded on colors you choose, which on the default mint was 1.9:1.
- Unread articles are visible in the list — a bar on the left, where the state rested on font weight alone.
- The unread count is readable in the sidebar in every theme, not just the default one.
- Date separators carry a date, not just a weekday.
- Feed icons in the article list, so you recognise a source instead of reading it.
🧹 Under the hood
- React 19, with no source change — the APIs it removes were used nowhere.
- Article transitions, a title that morphs from the list into the reader, and a settling list — all off under reduced motion.
- Mobile menus open as a sheet from the bottom, within thumb reach.
Full detail in CHANGELOG.md · Image: ghcr.io/fripix/frirss:1.4.5
v1.4.4
A security and correctness release. No new features, and nothing to do when upgrading.
🔒 Security
- The FreshRSS token can no longer leak to a third party. The proxy matched the server URL as a plain string prefix, so
server.tld.attacker.tldandserver.tld@attacker.tldboth passed. Image and favicon URLs come from feed content, so a hostile feed was enough. It now compares parsed origins. - Registration is closed by default. The first account is always allowed, so you are never locked out; reopen sign-ups in Preferences → Administration. Existing instances keep their current setting.
- The backend no longer runs as root. Node runs as
PUID/PGID— 1000 by default. The data directory is adopted on start, so no action is required. - Proxied requests are rate-limited per user. 600 per minute by default (
FRIRSS_PROXY_RATE_LIMIT,0disables). - Authentication is checked before the request body is read.
- Preference writes are bounded — key length, value size, keys per request, and total per user.
- Static assets now carry the security headers. This matters most for
.svg, which browsers treat as a document that can carry script. - Article extraction no longer stores markup wider than it displays. Only the videos the click-to-play facade understands survive.
- OIDC discovery goes through the anti-SSRF guard — the last outgoing call that used a bare
fetch(). - The development server's open proxy is gone. It relayed anywhere while forwarding every client header.
🐛 Fixes
- Mark all as read is no longer offered in Starred and Read Later. There, it marked the whole reading list and zeroed every counter.
- Unstarring no longer makes the row vanish. The list reconciles on reload, like every other view.
- Starring and Read Later are saved to the offline cache. Only read state was.
- Offline actions are no longer replayed twice when the network comes back.
🧹 Under the hood
- Dead code removed: the proxy's unused
X-Freshrss-Authfallback, an orphan CSS rule, two unused dev dependencies. SECURITY.mdnow documents a known limit of the anti-SSRF guard: it does not pin the address it validated, which leaves DNS rebinding open.- CI asserts that no application process runs as root, and that
/sw.jscarries neither a CSP norimmutable.
📖 Full history in CHANGELOG.md · 🐳 ghcr.io/fripix/frirss:1.4.4
FriRSS 1.4.3
1.4.3 adds encrypted backup and restore, moves FreshRSS server management into Preferences, and clears ten advisories from the image.
Nothing to do on upgrade: no new environment variable, no manual migration.
Encrypted backup and restore
FriRSS can now hand you a single file that rebuilds the whole instance — accounts and their password hashes, the FreshRSS servers you configured, their tokens and the key that decrypts them, preferences, and instance settings. Your articles are not in it: they live in FreshRSS, and always did.
That file is enough to impersonate every account on the instance, so a passphrase is mandatory — 12 characters minimum, AES-256-GCM behind a scrypt key. Lose the passphrase and the file is permanently unusable. There is no recovery path, by design.
Restore it from Preferences → Administration, or from the first-run screen of a brand-new instance — which makes it a migration tool as much as a backup. Either way you see what the file holds before committing to anything, and restoring replaces the instance's contents entirely.
The README now has a Backups section covering this alongside the data directory and the snapshot script, and what distinguishes them.
FreshRSS servers, managed in Preferences
Adding, renaming, setting a default, deleting and switching servers now live on one screen in Preferences → Feeds, reachable in every configuration. The top bar stays a selector; its + and its right-click lead there.
This fixes a real hole: hiding the top bar made server management — and switching itself — unreachable, and renaming, setting a default and deleting simply did not exist in the iOS PWA, for want of a right-click.
The master token is now per server, configured and tested from that server's row without having to switch to it first.
Faster, clearer Preferences
The panel used to rebuild from scratch on every section change. Feeds and Administration paid a full network round trip — blank screen included — on every visit. They no longer do.
Administration's five unrelated groups are legible at a glance. Their headings were the smallest, palest text on the page; a heading quieter than its own content titles nothing.
Security
Alpine packages are now upgraded when the image is built. The base image is rebuilt on its own schedule, so it was shipping openssl 3.5.7-r0 with ten fixable advisories against it; the production stage pulls the patched 3.5.8-r0 from the branch the base already pins — patch level only, no distribution jump.
Fixes
Backup and restore
- Signing back in after a restore works the first time. The restore armed a 1.5 s deferred logout that no unmount cancelled. Since clearing the sessions returns you to the login screen well within that delay, a quick sign-in was destroyed by the stale timer — it took two or three attempts.
- Restoring onto an already-configured instance says exactly that, instead of "The operation failed. Try again." — advice that could not work.
- The restore form is readable on the login screen, where it had been borrowing the Administration panel's light palette: near-black text on near-black.
- The file picker is one control that carries its state, instead of an empty input with the chosen filename detached beside it.
- Checking a backup shows that it is working, and the preview that unfolds below announces itself instead of appearing unnoticed.
- The first-run screen puts the administrator note and the restore link on a readable surface, and states the note as a sentence.
Servers in Preferences
- Escape no longer closes the Preferences panel from under an open form, and focus returns to the button that opened it — without that, a second Escape did nothing.
- A refused rename keeps the form open along with what you typed.
- The server list tells its states apart: a pending load and a failed request were indistinguishable from a read-only legacy connection. The recovery action reads Retry, not Refresh — it reissues the request that failed.
- The active server's row expands when you click its body, and the rename form has a cancel button like the delete flow.
Elsewhere
- Login errors no longer lie. Every failure — rate limit, server error, dropped connection — announced "incorrect credentials", which sent you retyping a password that was right all along. Only an authentication refusal says so now.
- French and Italian use the typographic apostrophe instead of the straight one.
- 25 dead translation keys removed across the nine locales, and two counters given real plural forms — Polish and Ukrainian were receiving a French-shaped
user(s)where their grammar asks for four.
Under the hood
docs/FEATURES.md now inventories everything FriRSS does, with a test that fails when a server route, an environment variable or a translation family is missing from it. The 232-settings guard introduced in 1.4.2 walks the panel recursively: it was not descending into nested settings, so it guarded less than it claimed. And a CHANGELOG.md tracks the release history, backfilled to 1.3.0.
Docker: ghcr.io/fripix/frirss:1.4.3 · docker.io/fripix/frirss:1.4.3 (amd64 + arm64)
FriRSS 1.4.2
Refresh actually refreshes
The Refresh button used to re-read what FreshRSS already had in its database. If FreshRSS hadn't polled your feeds yet, it truthfully reported "Up to date" while nothing had been fetched — which was accurate and thoroughly misleading.
It now asks FreshRSS to go and collect new articles, then fills the list in as they land. Optional: it needs your FreshRSS master authentication token (Profile page in FreshRSS). Without one, the button behaves exactly as before.
Configure it in Preferences → Feeds, on the login screen, or when adding a server. The Test button checks the token you just typed against your server and waits for the real answer.
Read this before enabling it. That token also grants password-free access to all your articles and your subscription list. FreshRSS only accepts this call as a GET, so the token appears in your FreshRSS server's access logs. It is stored encrypted and never reaches the browser. Rotate it if those logs are exposed. The setting says all of this before you paste anything.
Preferences, rebuilt
Ten tabs in a horizontal row became five sections plus Administration, in a vertical navigation. The panel's width no longer depends on how many sections exist — it used to hug the tab bar, so every tab ever added made it wider.
- Language moved to General, where people look for it. It was under "Appearance".
- Colours, Sizes and Themes merged into Appearance. One subject, one place.
- Keyboard shortcuts joined General instead of owning a whole tab.
- A live preview of FriRSS sits above the theme settings and recomposes as you change colours and sizes — no more closing the panel to see the result. Hovering a colour still outlines the real element behind the panel; the preview adds what it will look like.
Phone and tablet
The Preferences panel had no mobile handling at all. It now has:
- a two-level navigation on phones, with safe areas for the notch and the home indicator;
- touch targets that meet the 44 pt minimum;
- colour highlighting that works by tapping — it fired on hover only, so it had never worked on a touch device.
Fixes
- Toggle switches rendered as vertical ovals on touch.
- The colour highlight dimmed the Preferences panel and the highlighted element along with everything else.
- Each colour's reset control was invisible until you hovered it.
- The Escape key cap reads
ESCin every language. - Built-in gesture labels no longer break across two lines.
Under the hood
Preferences.tsx went from 3 012 lines to 278, split into seven focused files. A test freezes an inventory of 232 settings and fails the build if any of them stops being reachable — no setting was lost in the move.
v1.4.1
Maintenance release.
Fixed
- The operator scripts were missing from the container image.
scripts/backup-db.jsandscripts/reset-password.jsare documented in the README but the production image never included them, so running either failed withMODULE_NOT_FOUND. Both are now shipped.
This matters for recovery: the database backup tool and the admin password reset are only useful if they are actually in the image you run.
Backing up
docker exec <container> node scripts/backup-db.js /app/data/backups
The snapshot goes through SQLite's .backup() API — atomic, and safe while the server is running and the database is in WAL mode. Copying frirss.db alone is not a valid backup: recent writes live in the -wal file alongside it.