Commit Graph
422 Commits
Author SHA1 Message Date
Jgolding1998 d03296f074 Add current English text to DIRTY translation comments (#9525)
* Add English references to DIRTY translation comments

Translators currently have to find the English source separately for each `// DIRTY` entry. The translation formatter now appends it on the same line, including English plural fallback for languages with additional plural forms. It keeps hints on one safe PHP comment line and refreshes them when English changes.

Only comments change in the locale files; translation values and plural rules are unchanged. Single-form translations show both English forms as `Singular: … | Plural: …`. Generated README contribution links match bare and annotated TODO/DIRTY markers and retain the end-of-line anchor.

Closes #9463.

* Show both English forms for single-form translations
2026-11-02 21:25:34 +01:00
Inverle 6b0c78b0f3 Unpublish unrelated files from docs (#9521)
Follow-up #9520
2026-11-01 17:20:31 +01:00
Alexandre Alapetite 0bce4f71b3 Fix Fontmatter (#9520)
https://github.com/FreshRSS/FreshRSS/pull/9509 broke CI
2026-11-01 16:56:32 +01:00
InverleandAlexandre Alapetite 4ca6e3c97d docs: Add threat model for security scanners (#9509)
* docs: Add threat model for security scanners

To help security scanners such as OSS Scanner and hopefully reduce the number of misleading security reports, see https://github.com/FreshRSS/FreshRSS/discussions/9472#discussioncomment-19181192.

The threat model was written with AI assistance and is intended as a starting point for review, and can be improved if needed.

* Remove regression checks list

* Partial review
Typographic quotes, cut long lines, minor structure edits, add info

* Use local links

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-11-01 15:38:44 +01:00
2ebfff6e7d Add feeds_after_actualize hook (#9438)
Extensions that process new articles in bulk (summaries, exports,
notifications) have no way to react to the end of a refresh:
freshrss_user_maintenance runs before the feeds are fetched, and new
articles stay in the temporary table until the whole loop is done.

The new FeedsAfterActualize hook runs once the new articles are
committed, with the refreshed feeds whose articles changed and the
number of new articles: after a cron or web refresh (actualizeAction,
including the commit-only request of the web interface) and after
actualizeFeedsAndCommit (CLI, API, WebSub, feed added).

Co-authored-by: ChrisB85 <792899+ChrisB85@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:38:49 +01:00
InverleandAlexandre Alapetite 76e8993579 Replace bcrypt challenge auth with direct password comparison (#9358)
* Replace bcrypt challenge auth with direct password comparison

Closes #2428

Highlights:

* Replaced the bcrypt challenge authentication flow with direct password comparison against the bcrypt hash stored in the user configuration.
  * Removed the `bcrypt.js` dependency, so authentication no longer depends on JavaScript.
  * Removed the nonce and challenge mechanism and the `nonce` endpoint from the `javascript` controller.
  * Removed the JavaScript and HTML code used for the previous authentication flow.
  * Removed the `<noscript>` blocks that were only needed for the previous authentication flow.
* Updated `FreshRSS_FormAuth::checkCredentials()` to accept the username, stored bcrypt hash, and plain-text password directly.
  * The stored password hash must still use the expected bcrypt format.
  * Empty or whitespace-only passwords are rejected even if the hash would otherwise match.
  * The bcrypt 72-byte limit is enforced during normal authentication.
  * Existing bcrypt passwords are handled correctly when the 72-byte limit cuts through a UTF-8 character.
  * Password input is no longer trimmed before authentication or password changes. Leading and trailing whitespace is therefore kept as part of the password.
* Added `FreshRSS_FormAuth::passwordRequirementsMet()` to replace `FreshRSS_password_Util::check()`.
  * New passwords must be between 7 and 72 bytes by default.
  * The minimum length check can be disabled where needed.
  * Password requirements are now checked server-side for all password creation and update paths.
* Added client-side byte-length validation for password fields using `TextEncoder` and `data-max-bytes`.
  * This gives better feedback for UTF-8 passwords.
  * Server-side validation remains authoritative.
* Updated password handling in authentication, re-authentication, self-deletion, user creation, and user update paths so passwords are not trimmed automatically.
* Added `FormAuthTest` to cover:
  * password length limits in bytes
  * UTF-8 passwords around the 72-byte limit
  * bcrypt truncation behavior during authentication
  * malformed and non-bcrypt hashes
  * empty passwords
  * authentication failures and log messages
* Removed `bcrypt.js` from the documented dependencies and updated the authentication documentation.
* Updated the installation and authentication views and JavaScript to match the new password handling.

This simplifies the authentication flow, removes the client-side bcrypt challenge, makes password validation consistent between the client and server, and adds more test coverage while keeping compatibility with existing bcrypt password hashes.

Because the password is now sent directly to the server instead of being processed by `bcrypt.js`, deployments should use HTTPS. Documentation for HTTPS setup can be handled separately.

* Add test case with password truncated down to 71 characters

* Compare log messages too during form auth tests

* Mark the `getLastLog()` method as impure

* Fix: Check password reqs against new password instead of current

* Fix: Add custom JS validator for UTF-8, handle cut-off UTF-8 during login

* Fix: Enforce password requirements everywhere server-side

* Add TODO for updateUser in CLI

* make readme

* Fix extra.js in install.php

* Don't report validity yet if the password is too short or not filled out

* Simplify checking of whether to report validity

* Fix: Do not trim password from user's input

* Also `trim: false` in some userController paths

* Use the proper argument order for PHPUnit's `assertSame()`

* Remove `maxlength` in favor of `data-max-bytes`

But add `pattern` again for some nojs support

* make readme

* composer run-script translations

* Update i18n string for web form admin setting

* i18n: fr

* Minor whitespace

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-30 22:21:26 +01:00
Alexandre Alapetite fd3f75b3a2 Require PHP 8.2+ (#9470)
Drop PHP 8.1 (EOL December 2025), drop Ubuntu 22.04 (EOL May 2027, and there is now 24.04 and 26.04 LTS).
PHP 8.1 is already EOL, and Debian, Alpine, Fedora, Red Hat, Synology have dropped PHP 8.1 already.
See https://github.com/FreshRSS/FreshRSS/discussions/5474#discussioncomment-16791937

With PHP 8.6 around the corner and in particular a relevant `FILTER_FLAG_GLOBAL_RANGE` in PHP 8.2+.
* https://www.php.net/releases/8.2
* https://www.php.net/filter.constants.validation#constant.filter-flag-global-range
* https://www.php.net/class.sensitiveparameter
* https://php.watch/versions/8.2/readonly-classes
* https://www.php.net/function.ini-parse-quantity

Allows updating to PHPUnit 11.x instead of 10.x
https://phpunit.de/supported-versions.html

Contributes to https://github.com/FreshRSS/FreshRSS/issues/9475

* Add regression test for FILTER_FLAG_GLOBAL_RANGE
https://github.com/FreshRSS/FreshRSS/pull/9470#discussion_r4371168720
2026-10-26 21:02:32 +01:00
spa77k 7e9e81d4c7 Update documentation of the article CSS selector (#9505)
Closes #3952

- Replace the outdated Rue89 walkthrough and the list of site-specific selectors (Rue89, PCINpact, Lesnumériques, Phoronix) with step-by-step instructions to find a selector, based on a generic HTML example: use the page source (JavaScript is not run), pick the element containing only the article text, prefer an id or class shared by all articles, and check with the preview button.
- Explain how the retrieved content is used: all matching elements are kept, only new or updated articles are retrieved (with *Reload articles* for existing ones), the content action, the conditions for content retrieval, what happens when nothing matches, sanitized `id`/`class` attributes, and the cookie and user agent options.
- Document *CSS selector of the elements to remove*, including its use without an article CSS selector, where classes must be matched with `[data-sanitized-class~="…"]`.
2026-10-25 13:51:45 +01:00
dependabot[bot] 2bdfdc33f5 chore(deps-dev): bump the bundler group in /docs with 5 updates (#9500)
Bumps the bundler group in /docs with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [jekyll-relative-links](https://github.com/benbalter/jekyll-relative-links) | `0.8.0` | `0.9.1` |
| [jekyll-optional-front-matter](https://github.com/benbalter/jekyll-optional-front-matter) | `0.3.3` | `0.3.4` |
| [jekyll-readme-index](https://github.com/benbalter/jekyll-readme-index) | `0.4.0` | `0.5.1` |
| [jekyll-default-layout](https://github.com/benbalter/jekyll-default-layout) | `0.2.0` | `0.2.1` |
| [jekyll-titles-from-headings](https://github.com/benbalter/jekyll-titles-from-headings) | `0.5.4` | `0.5.7` |


Updates `jekyll-relative-links` from 0.8.0 to 0.9.1
- [Release notes](https://github.com/benbalter/jekyll-relative-links/releases)
- [Changelog](https://github.com/benbalter/jekyll-relative-links/blob/main/CHANGELOG.md)
- [Commits](https://github.com/benbalter/jekyll-relative-links/compare/v0.8.0...v0.9.1)

Updates `jekyll-optional-front-matter` from 0.3.3 to 0.3.4
- [Release notes](https://github.com/benbalter/jekyll-optional-front-matter/releases)
- [Changelog](https://github.com/benbalter/jekyll-optional-front-matter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/benbalter/jekyll-optional-front-matter/compare/v0.3.3...v0.3.4)

Updates `jekyll-readme-index` from 0.4.0 to 0.5.1
- [Release notes](https://github.com/benbalter/jekyll-readme-index/releases)
- [Changelog](https://github.com/benbalter/jekyll-readme-index/blob/main/CHANGELOG.md)
- [Commits](https://github.com/benbalter/jekyll-readme-index/compare/v0.4.0...v0.5.1)

Updates `jekyll-default-layout` from 0.2.0 to 0.2.1
- [Release notes](https://github.com/benbalter/jekyll-default-layout/releases)
- [Changelog](https://github.com/benbalter/jekyll-default-layout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/benbalter/jekyll-default-layout/compare/v0.2.0...v0.2.1)

Updates `jekyll-titles-from-headings` from 0.5.4 to 0.5.7
- [Release notes](https://github.com/benbalter/jekyll-titles-from-headings/releases)
- [Changelog](https://github.com/benbalter/jekyll-titles-from-headings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/benbalter/jekyll-titles-from-headings/compare/v0.5.4...v0.5.7)

---
updated-dependencies:
- dependency-name: jekyll-relative-links
  dependency-version: 0.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: bundler
- dependency-name: jekyll-optional-front-matter
  dependency-version: 0.3.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: jekyll-readme-index
  dependency-version: 0.5.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: bundler
- dependency-name: jekyll-default-layout
  dependency-version: 0.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: jekyll-titles-from-headings
  dependency-version: 0.5.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-24 22:57:14 +02:00
Inverle b8b657488d Fix some broken links from CI scans (#9495)
* Fix some broken links from CI scans

Follow-up of https://github.com/FreshRSS/FreshRSS/pull/9209 and https://github.com/FreshRSS/FreshRSS/pull/9207.

See: https://github.com/FreshRSS/FreshRSS/pull/9209#issuecomment-6383267668

* Apply some selected redirects

* Escape . properly
2026-10-24 22:55:59 +02:00
Inverle b33749fe6c New hook: Minz_HookType::AutofillField for auth views (#9362)
* New hook: `Minz_HookType::AutofillField` for auth views

Currently to be used in the Demo extension, because it overrides both the formLogin and reauth views (for autofilling username/password), it can be broken every time there is a slight change in core.
For example, after https://github.com/FreshRSS/FreshRSS/pull/9358.

In the future the hook can be extended to support more fields.

Demo extension PR: https://github.com/FreshRSS/xExtension-Demo/pull/6

* Suggested alternative without returning HTML code
2026-10-19 23:10:38 +02:00
spa77k 6ada118555 Document force-https.txt (#9460)
Closes #4616

The _Force HTTPS_ mechanism (`./force-https.default.txt` and `./data/force-https.txt`) was not documented. This adds a short section to the admin page about configuration without a user interface.
2026-10-19 14:49:45 +02:00
Inverle c8d859b88d Update registerHook docs with newer PHP 8.1+ syntax for callables (#9443)
* Update registerHook docs with newer PHP 8.1+ syntax for callables

* Use the correct function name for the second hook

* Use correct return type for the second hook function
2026-10-15 19:51:52 +02:00
Alexandre Alapetite 7e8ef628d9 Better doc and log proxy cases (#9436)
Fix https://github.com/FreshRSS/FreshRSS/issues/9423
And strip redundant proxy scheme.
And add a few more unit tests.
2026-10-13 19:50:38 +02:00
spa77k 9c04882cc1 Document how to use the User CSS and User JS core extensions (#9424)
Closes #9394

## Summary

The _User CSS_ and _User JS_ sections already existed in the user documentation, but they did not say where to find the settings. This explains that each extension has a gear icon (_Manage_) next to its switch, and gives the steps to use both extensions.

## Changes

- `docs/en/users/05_Configuration.md`: describe the extension list (switch, gear icon, name), add the steps for User CSS, and the same for User JS with a note that it might ask to sign in again.
- `docs/en/users/05_Configuration.md`, `docs/en/admins/11_Themes.md`, `docs/en/contributing.md`: the "CustomCSS extension" links pointed to FreshRSS/Extensions, where it is no longer available. They now point to the built-in User CSS section.
- `docs/en/admins/15_extensions.md`, `lib/core-extensions/README.md`: list the two core extensions with links to their documentation. Also fix the `.lib/core-extensions` path.
- `CREDITS.md`: add my name.

Only the English documentation is changed.
2026-10-09 23:04:22 +02:00
dependabot[bot] 522c560922 chore(deps-dev): bump jekyll-default-layout from 0.1.5 to 0.2.0 in /docs (#9407)
Bumps [jekyll-default-layout](https://github.com/benbalter/jekyll-default-layout) from 0.1.5 to 0.2.0.
- [Release notes](https://github.com/benbalter/jekyll-default-layout/releases)
- [Changelog](https://github.com/benbalter/jekyll-default-layout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/benbalter/jekyll-default-layout/compare/v0.1.5...v0.2.0)

---
updated-dependencies:
- dependency-name: jekyll-default-layout
  dependency-version: 0.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-05 06:10:15 +02:00
dependabot[bot] fc099016d3 chore(deps-dev): bump jekyll-relative-links from 0.7.0 to 0.8.0 in /docs (#9405)
Bumps [jekyll-relative-links](https://github.com/benbalter/jekyll-relative-links) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/benbalter/jekyll-relative-links/releases)
- [Changelog](https://github.com/benbalter/jekyll-relative-links/blob/main/CHANGELOG.md)
- [Commits](https://github.com/benbalter/jekyll-relative-links/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: jekyll-relative-links
  dependency-version: 0.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-05 06:09:31 +02:00
dependabot[bot] a28ae9056e chore(deps-dev): bump jekyll-readme-index from 0.3.0 to 0.4.0 in /docs (#9403)
Bumps [jekyll-readme-index](https://github.com/benbalter/jekyll-readme-index) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/benbalter/jekyll-readme-index/releases)
- [Changelog](https://github.com/benbalter/jekyll-readme-index/blob/master/CHANGELOG.md)
- [Commits](https://github.com/benbalter/jekyll-readme-index/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: jekyll-readme-index
  dependency-version: 0.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-04 23:03:30 +02:00
dependabot[bot] e03c1616c6 chore(deps-dev): bump jekyll-optional-front-matter in /docs (#9404)
Bumps [jekyll-optional-front-matter](https://github.com/benbalter/jekyll-optional-front-matter) from 0.3.2 to 0.3.3.
- [Release notes](https://github.com/benbalter/jekyll-optional-front-matter/releases)
- [Changelog](https://github.com/benbalter/jekyll-optional-front-matter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/benbalter/jekyll-optional-front-matter/compare/v0.3.2...v0.3.3)

---
updated-dependencies:
- dependency-name: jekyll-optional-front-matter
  dependency-version: 0.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-04 22:57:57 +02:00
dependabot[bot] fc1b40a70a chore(deps-dev): bump jekyll-titles-from-headings in /docs (#9402)
Bumps [jekyll-titles-from-headings](https://github.com/benbalter/jekyll-titles-from-headings) from 0.5.3 to 0.5.4.
- [Release notes](https://github.com/benbalter/jekyll-titles-from-headings/releases)
- [Changelog](https://github.com/benbalter/jekyll-titles-from-headings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/benbalter/jekyll-titles-from-headings/compare/v0.5.3...v0.5.4)

---
updated-dependencies:
- dependency-name: jekyll-titles-from-headings
  dependency-version: 0.5.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-04 22:56:51 +02:00
InverleandAlexandre Alapetite 0285dc8970 Check SVG optimization in the tests CI workflow with SVGO (#9361)
* Check SVG optimization in the tests CI workflow with SVGO

Previously it was only recommended by the documentation that SVG images should be optimized using [SVGOMG](https://github.com/jakearchibald/svgomg), the web GUI version of https://github.com/svg/svgo (CLI)


Now all SVG images are optimized, and optimization is mandatory enforced by the tests CI workflow.
\+ SVGs are sanitized of potentially malicious JS code using the https://svgo.dev/docs/plugins/removeScripts/ plugin

* Output prettified SVGs instead and remove unchanged files from stdout

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Use `removeScripts` SVGO plugin to reduce risk of malicious SVGs

* Move the CLI options to the JS config instead

* Disable `removeComments` plugin included in the default preset

* editorconfig, and easier config

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-29 16:18:01 +02:00
Yuchen Shi 818b57a9a9 Update documentation of TRUSTED_PROXY syntax and document limitations. (#9283)
* Update documentation of TRUSTED_PROXY syntax and document limitations.

Single IPs are now supported as of #9301.
Document the existing limitation of domain names, see #9210.

For IPs without `/`, I manually tested the behavior and found requests got rejected on my server before #9301. After #9301 is merged, I've tested again and found that requests are now authorized as expected. So I've updated the corresponding documentation to say "_IPs or_ ranges" where applicable.

Domain names are still silently ignored as of writing. I looked at the code, as described in #9210 investigation steps, and concluded that they are WAI (working as _implemented_ , not sure if _intended_ ). I don't particularly like the existing behavior (failures with no debuggable warnings or logs) but I try to document it as-is without judgement.

* Update TRUSTED_PROXY comments accordingly for Docker usage.

Single IPs are now supported as of #9301.
Document the existing limitation of domain names, see #9210.
2026-09-28 08:53:12 +02:00
Pavel Miniutka fcc41b6ea0 Add Belarusian (be) translation (#9351)
Complete interface translation under app/i18n/be/. The locale is
registered in all app/i18n/*/gen.php, .typos.toml, docs and READMEs.
2026-09-26 14:57:04 +02:00
Zain QureshiandClaude Opus 5.5 0a5c0ca148 docs: fix dead section anchors in English First steps guide (#9348)
Changes proposed in this pull request:

- `docs/en/users/02_First_steps.md` links to four section anchors that don't exist on the English pages. The ids look like literal translations of the French headings, but the English headings are worded differently:
  - `04_Subscriptions.md#import-and-export` -> `#import-export` (`## Import / export`)
  - `04_Subscriptions.md#feed-management` -> `#subscription-management` (`## Subscription management`)
  - `05_Configuration.md#changing-the-view` -> `#display` (`## Display`, the section the French page links as "Personnaliser la vue")
  - `05_Configuration.md#reading-options` -> `#reading` (`## Reading`)
- The French page already resolves correctly, so this only touches the English source.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 08:53:17 +02:00
Alexandre AlapetiteandInverle cea88ace69 Reword explanations for edge/latest channels (#9270)
* Reword explanations for edge/latest channels
Highlight the fact that we do not backport bug fixes or security fixes to previous releases.
Simplify also a slightly obsolte MySQL doc to favour SQLite

* Merge i18n: az

* Update Polish translations to specify rolling/versioned releases

Co-authored-by: Inverle <inverle@proton.me>

* Update 05_Release_new_version.md

---------

Co-authored-by: Inverle <inverle@proton.me>
2026-09-08 23:25:18 +02:00
Inverle 0a44b7d017 Follow-up #9169 remove form IDs everywhere (#9205)
* Follow-up #9169 remove form IDs everywhere

* Add some `<form name>` alongside `class`
2026-08-29 23:21:39 +02:00
Luis Carlos SimitanaandAlexandre Alapetite 03e871e5be Add configurable log_level system setting (#9185)
* Add configurable log_level system setting

Minz_Log currently only distinguishes 'production' (errors and
warnings only) from any other environment (everything, including
debug messages), with no granularity in between.

Introduce an optional 'log_level' system setting (error, warning,
notice, info, or debug) that overrides the verbosity implied by
'environment'. It defaults to an empty string, which preserves the
exact current behaviour.

Fixes #7594

* Slight rewrite

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-08-25 16:18:30 +02:00
InverleandAlexandre Alapetite a84c0a8bc2 Fix all broken links in the entire repository (#9207)
* Fix all broken links in the entire repository

* Update docs/CHANGELOG-old2.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update config.default.php

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update README.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update docs/i18n/freshrss.fr.po

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update docs/i18n/templates/freshrss.pot

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update README.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update docs/fr/users/01_Installation.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update docs/fr/users/01_Installation.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Update docs/en/internationalization.md

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Manual fixes, preferences
* Prefer canonical when known, prefer shorter, prefer roots, prefer URLs with content negotation (e.g. language preference)
* Keep 302, 307 unchanged
* Use only `.example`, `example.net` or similarly reserved domains for URL examples

* Fix language negotiation for developer.mozilla.org

* Restore a Stackoverflow 302

* Fix some example.com

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-08-19 16:19:21 +02:00
CronandAlexandre Alapetite 09478222ce Docker/OIDC: redirect to a default URL on expired auth state instead of 400-ing (#9143)
## Problem

With OIDC on the official Docker image, interactive SSO logins intermittently return `HTTP 400` at the `/i/oidc/` callback. The failure tracks login duration. `mod_auth_openidc` sets an anti-CSRF state cookie bounded by `OIDCStateTimeout` (default 300 s). If a login through an external IdP takes longer than that (MFA, a slow provider screen, the user stepping away), the cookie is already gone when the callback returns. The image does not set `OIDCDefaultURL`, so the module has no fallback and returns an error instead of restarting the login.

Apache log:

```
oidc_response_proto_state_restore: state has expired
oidc_response_process: invalid authorization response state and
        no default SSO URL is set, sending an error...
→ GET /i/oidc/?code=…&state=… 400
```

This is [documented mod_auth_openidc behaviour](https://github.com/OpenIDC/mod_auth_openidc/blob/72c9f479c2d228477ff0a9518964f61879c83fb6/auth_openidc.conf#L723-L733) when `OIDCDefaultURL` is unset. Both `OIDCDefaultURL` and `OIDCStateTimeout` are `RSRC_CONF` (server config only), so the `./FreshRSS/p/i/.htaccess` escape hatch cannot set them and the fix has to live in the image.

Reproduce: log out, start SSO, wait more than 5 minutes on the IdP screen, complete.

## Fix

Add `OIDC_DEFAULT_URL`, wired through the existing `${OIDC_*}` env pattern, defaulting to `/i/` (the index, consistent with `OIDCRedirectURI /i/oidc/`). On an expired or unmatchable state the module now redirects to `/i/`, which is protected and starts a fresh login against the existing IdP session. `OIDCStateTimeout` stays at 300 s; the window is fine, the missing fallback was the bug.

`OIDC_STATE_TIMEOUT` is intentionally not exposed. It can be added the same way if there is a reason to tune the window.

* Docker/OIDC: redirect to a default URL on expired auth state instead of 400-ing

Adds OIDC_DEFAULT_URL (default /i/), wired through the existing ${OIDC_*} env
pattern, so an expired or unmatchable mod_auth_openidc state redirects to the
FreshRSS index and restarts the login instead of returning HTTP 400.

* git commit update

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-24 17:55:36 +02:00
ccf7c7a9dc Clarify archiving/purge documentation (#9135)
* Clarify archiving/purge documentation

Explain purge policy priority (feed > category > global), the discovery-date based purge logic, and that purging is only triggered on a random subset of feed refreshes (or via Purge now / cli/purge.php).

Fixes #6863.

Co-Authored-By: Claude <noreply@anthropic.com>

* Typographic quotes

* No changelog for (minor) documentation

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-23 14:08:54 +02:00
Frans de JongeandAlexandre Alapetite d519a8a9e6 AI skills: suggestion to limit the number of PRs (#9141)
* AI skills: suggestion to limit the number of PRs

* review comments

* tweak phrasing

* Bump to 10

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-23 13:13:47 +02:00
InverleandAlexandre Alapetite 48abfd58fa Improve SSRF mitigations by restricting CURLOPT_PROXY (#8950)
* Improve SSRF mitigations by restricting `CURLOPT_PROXY`

Follow-up of https://github.com/FreshRSS/FreshRSS/pull/8400
SimplePie PR: https://github.com/FreshRSS/simplepie/pull/83

* Reword docs

* Change single quote to backquote in docs

* Fix `cURL error 5: Could not resolve proxy: Array` error

* Sync SimplePie

* A few fixes

* Implement suggestion

* A few more fixes

* Sync composer
https://github.com/FreshRSS/simplepie/pull/83

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-23 13:05:53 +02:00
30968c4065 Document OpenBSD httpd API configuration (#9061)
* Document OpenBSD httpd API configuration

Closes #7493. Adds an OpenBSD httpd example for FreshRSS with PATH_INFO preserved for the Google Reader API. 

* Document caching considerations for OpenBSD httpd

* A few edits

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-20 18:02:10 +02:00
Gerard Alvear PorrasandGerard Alvear 88acb5bae5 Document HTTP proxy tunnelling option (#9054)
Closes #7209. Documents CURLOPT_HTTPPROXYTUNNEL with a data/config.php example for HTTPS feeds through HTTP CONNECT proxies.

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
2026-07-19 12:04:57 +02:00
44c6de6492 Add Nextcloud Bookmarks sharing service (#9032)
* Add Nextcloud Bookmarks sharing service

Adds a new self-hosted sharing option that posts articles to a
Nextcloud instance's Bookmarks app bookmarklet endpoint, following
the same pattern as the existing Linkding and Shaarli entries.

Closes #8053

* i18n: use Nextcloud Bookmarks translations

* Update i18n

* Fix wrong search&replace

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-18 20:33:01 +02:00
Gerard Alvear PorrasandGerard Alvear 8e8c8d26b8 Document TT-RSS article migration (#9062)
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
2026-07-18 20:21:51 +02:00
d2ee8f3d79 Add LinkAce as a sharing service (#9024)
* Add LinkAce as a sharing service

LinkAce is a self-hosted bookmark manager with a bookmarklet-style
add endpoint, similar to Linkding and Shaarli. Add a 'linkace' entry
to app/shares.php using its documented bookmarklet URL scheme, add
the corresponding translation key, and list it in the sharing
services documentation.

Fixes #6698

* i18n Ignore

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-18 20:12:31 +02:00
Gerard Alvear PorrasandGerard Alvear cf298e2680 Docker: use double quotes for CRON_MIN examples (#9011)
The Docker Quick Run / cron examples use single-quoted `-e 'CRON_MIN=1,31'`. This works on Linux/macOS shells and PowerShell, but not on Windows `cmd.exe`, which does not strip single quotes from arguments — they end up included literally in the variable, so the cron job never gets scheduled correctly. Using `-e CRON_MIN=*/20` (no quotes) works, which was the original report.


Fixes #4818

* Docs: remove CRON_MIN quoting

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
2026-07-15 13:45:02 +02:00
Gerard Alvear PorrasandGerard Alvear c1e711274c Mirror the official Fever API documentation (#9014)
The original Fever API docs at feedafever.com/api are offline, and only
an archive.org snapshot remains linked from our Fever API page. Add a
Markdown mirror of that content (with Alkarex's HTML-to-Markdown
translation) directly into the docs so it is preserved even if the
archive.org copy disappears.

The mirror is a faithful copy of Alkarex's translation, including the
"API Public Beta" intro paragraphs, with only the heading levels
adjusted to fit this page.

Fixes #7331

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
2026-07-13 00:04:14 +02:00
Gerard Alvear PorrasandGerard Alvear 5f94330503 Mailer: allow disabling SMTPAutoTLS via config (#9026)
PHPMailer enables SMTPAutoTLS by default, meaning it opportunistically
upgrades a plain SMTP connection to STARTTLS whenever the server
advertises support for it, regardless of the 'secure' => '' setting
documented in config.default.php. Minz_Mailer never read or set
$mail->SMTPAutoTLS, so this behaviour could not be turned off, making
it impossible to talk to SMTP servers with a self-signed or otherwise
untrusted certificate (e.g. a mail server on an internal Docker
network) without importing a custom CA.

Add a new 'auto_tls' key to the 'smtp' config array, defaulting to
true to preserve current behaviour, and wire it through to
$mail->SMTPAutoTLS in Minz_Mailer. Document the new option.

Fixes #2997

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
2026-07-12 23:55:32 +02:00
28bc73afed Dispatch a JS event when an entry finishes being marked read/unread (#9031)
* Dispatch a JS event when an entry finishes being marked read/unread

Extensions that render their own read/unread indicator outside of the
main article list (e.g. a custom reading pane) had no way to know when
a mark read/unread AJAX request completed, so their own UI could get
stuck (e.g. a spinner never reset).

Dispatch a `freshrss:entryStateChange` event on `document` with the
entry id and new state once the toggle has been applied, following the
existing `freshrss:*` JavaScript event convention used elsewhere in
main.js.

Fixes #8862

* Minor preferences

---------

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-12 23:04:12 +02:00
Gerard Alvear PorrasandGerard Alvear e95243ae68 Document OIDC token endpoint authentication method (#9027)
Explain that mod_auth_openidc defaults to client_secret_basic since
our reference Apache configuration does not set
OIDCProviderTokenEndpointAuth. If the identity provider's discovery
metadata advertises a token_endpoint_auth_methods_supported array
instead, mod_auth_openidc picks the first mutually supported method
from that array rather than falling back to client_secret_basic.
Also point to the .htaccess override for administrators who need
something else.

This only addresses the token-endpoint-auth-method part of #6102;
the redirect-URI-port question raised in that issue is out of scope
here.

Addresses the token-endpoint-auth-method part of #6102

Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
2026-07-11 17:35:01 +02:00
Jam Balaya 310bcb5e90 Add read status hook for entries (#8995)
- Add a new `EntriesRead` / `entries_read` hook for extensions.
- Trigger the hook when `markRead()` changes entry read/unread state for explicit entry IDs.
- Cover both the default DAO path and the SQLite/PGSQL DAO path.
- Add Minz hook tests for the new hook signature and argument passing.

## Why

Extensions can already react to favorite/bookmark changes through `EntriesFavorite`. This adds the matching read/unread hook requested in #4051 for extensions that need to collect stats or react to manual read state changes.

Closes #4051
2026-07-07 09:04:32 +02:00
Inverle cb13091b37 Fix GH issue links with label filter in docs (#8993)
The "good first issue" and "help wanted" links under https://freshrss.github.io/FreshRSS/en/contributing.html#fix-a-bug aren't working.
2026-07-06 13:22:52 +02:00
Inverle c3d0bd5b55 Clarify in docs favicons/ should not be skipped during backup (#8992)
Alternative for https://github.com/FreshRSS/FreshRSS/pull/8972, at least until the migration system is improved to support rollbacks.
2026-07-06 11:16:56 +02:00
ffc5a966c3 Migrate markdownlint-cli to markdownlint-cli2 (single config file) (#8987)
* Migrate markdownlint-cli to markdownlint-cli2

Replace markdownlint-cli with markdownlint-cli2 and consolidate the three
separate configuration points (.markdownlint.json rules, .markdownlintignore
excludes, and the '**/*.md' glob in the npm scripts) into a single
.markdownlint-cli2.jsonc holding config + globs + ignores.

The npm script names (markdownlint / markdownlint_fix) are unchanged, so the
CI step and the test/fix compound scripts need no change. markdownlint-cli2
v0.23.0 bundles the same markdownlint v0.41.0, so linting behaviour is
identical (112 files, 0 errors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add JSONC to .editorconfig

* Reduce obvious comments

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-05 14:18:49 +02:00
Jam BalayaandClaude Opus 4.8 cf2ceafca1 OPML: include each feed's refresh interval (TTL) in export/import (#8982)
Feeds moved between instances via OPML lost their refresh interval,
forcing manual reconfiguration. Add a 'frss:ttl' attribute (signed, so a
negative value also carries the muted state) to the OPML export, and read
it back on import. Documented in docs/en/developers/OPML.md.

For https://github.com/FreshRSS/FreshRSS/issues/5914

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:55:13 +02:00
InverleandAlexandre Alapetite dcec27c69d Add SSRF mitigations using filter_var and CURLOPT_RESOLVE (#8400)
* Add SSRF mitigations using `filter_var` and `CURLOPT_RESOLVE`
The idea is to prevent FreshRSS from sending any HTTP requests to internal services, except for the ones that are explicitly allowed in the config.

Based on https://github.com/moodle/moodle/blob/6e82b46a480826d1a85394d9e5087f7d82d1dd52/lib/filelib.php#L3818 and https://github.com/symfony/symfony/blob/8.1/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php

https://github.com/FreshRSS/simplepie/pull/76
https://github.com/FreshRSS/simplepie/pull/78

* Add allowlist setting in Web UI

* make readme

* Update app/i18n/fr/admin.php

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* make readme again

* make readme

* Further work

Still WIP and needs testing etc.

* Readd previous if check for domain combination allowlist

* Turn POST to GET after redirect

* Improve

* Update config.default.php

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* make readme

* Skip SSRF check if `CURLOPT_PROXY` is set

* make readme

* Fix `!empty()` mistake

* Respect max redirects feed option when fetching with `httpGet()`

* Respect max redirects during SimplePie fetching + fix bypass

bypass fix: `CURLOPT_FOLLOWLOCATION` was moved below so that emulated redirects are enforced.

* Avoid FreshRSS and Minz code in SimplePie
https://github.com/FreshRSS/FreshRSS/pull/8400#discussion_r2935375980

* Corrected hook code

* phpdoc wrong return type

* Add CIDR support in allowlist

* Implement simple DNS caching

* Suppress `dns_get_record()` warnings

* A bit of proof-reading

* Minor typo

* Fix proxy logic

* Fix HTTP POST redirect logic

* Proofread checkCIDR
Add fixes for several situations

* Remove credentials from URL in logs

* Ensure `CURLOPT_FOLLOWLOCATION` is `false` by setting it at the end

* Fix codesniffer long line

* Fix potential bypass due to wrong return value

If there were no records returned by `dns_get_record()`, no overrides to `CURLOPT_RESOLVE` would get passed,
and a potential bypass could occur, when cURL would try to resolve the domain by itself.

* Put the URL at the end in logs

* Add documentation and environment variable support

* make readme

* Fix wrong behavior in case of IP

* Fix duplicate selector in CSS

* Minor type check change

* i18n fr, en

* Minor type check change

* Fix whitespace i18n fr

* make fix-all

* Fix `$ips_ok` not being returned after domain records were cached

* make readme

* PHPStan fix

* make readme

* Minor syntax in SimplePie

* Only return `null` if no allowed IPs were found

* Add wildcard *, help message

* Consistent docs with help message

* i18n: pl

* SimplePie compatibility PHP 7.2

* make fix-all

* Sync SimplePie
* https://github.com/FreshRSS/simplepie/pull/76

* 💥 Breaking change in the Changelog

* Document `INTERNAL_HOST_ALLOWLIST` in Docker docs

* Remove `Cookie` and `Authorization` headers in `httpGet()` during cross-origin redirect

* Minor whitespace
And same comment convention than below

* Remove authentication headers and change POST to GET on redirect in SimplePie

* Remove .local in Docker example

* Fill in default ports when comparing URL origins

* Remove .local from other places than the Docker example

* Rewrite WebSub subscribe to use `httpGet()`

* make fix-all

* Also unset `CURLOPT_USERPWD` during redirects

* phpcs fix

* Always unset `CURLOPT_FOLLOWLOCATION`

* Bump SimplePie
https://github.com/FreshRSS/simplepie/pull/78

* Update logic for CURLOPT_FOLLOWLOCATION

* Fix PHPStan

* Changelog fix security section

* Update most common RSS Bridge case
https://hub.docker.com/r/rssbridge/rss-bridge

* Replace misleading 127.0.0.1:8080 example for Docker
This does not make sense for a Docker container

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-06-28 18:51:04 +02:00
dependabot[bot] 69eff07d92 chore(deps): bump concurrent-ruby (#8940)
Bumps the bundler group with 1 update in the /docs directory: [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).


Updates `concurrent-ruby` from 1.3.6 to 1.3.7
- [Release notes](https://github.com/ruby-concurrency/concurrent-ruby/releases)
- [Changelog](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ruby-concurrency/concurrent-ruby/compare/v1.3.6...v1.3.7)

---
updated-dependencies:
- dependency-name: concurrent-ruby
  dependency-version: 1.3.7
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 22:00:22 +02:00
dependabot[bot] 6f1b66eb2b chore(deps-dev): bump faraday (#8939)
Bumps the bundler group with 1 update in the /docs directory: [faraday](https://github.com/lostisland/faraday).


Updates `faraday` from 2.14.2 to 2.14.3
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lostisland/faraday/compare/v2.14.2...v2.14.3)

---
updated-dependencies:
- dependency-name: faraday
  dependency-version: 2.14.3
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 21:45:35 +02:00