GHSA-2xmw-f8j8-wfxc: Pagy I18n locale option is not validated before being used in a file path
Summary
Pagy::I18n.locale= did not validate its argument before using it as a
path component to load the matching dictionary file (<locale>.yml). An
application that assigns untrusted input to the locale — e.g. the common
pattern Pagy::I18n.locale = params[:locale] — let that input influence
which file Pagy attempted to load.
Details
The setter stored the value as-is, and the loader joined it into a path
and read it:
gem/lib/pagy/modules/i18n/i18n.rb
def locale=(value)
Thread.current[:pagy_locale] = value.to_s
end
...later, when translating:
path = pathnames.reverse.map { |p| p.join("#{locale}.yml") }.find(&:exist?)
dictionary = YAML.load_file(path)[locale]
Because the locale was used verbatim, a value such as an absolute path or
a ../-style string redirected the lookup outside the locales directory.
Pagy's subsequent structural check (dictionary['pagy']['p11n'])
prevents the file's contents from being returned, so this is not a
direct file read.
Fixed in 43.5.6 by constraining the locale to a BCP 47 shape before use:
LOCALE_PATTERN = /\A[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*\z/
def locale=(value)
Thread.current[:pagy_locale] = value.to_s[LOCALE_PATTERN]
end
Any non-matching value (including nil) resolves to the default locale
and never reaches the file lookup.
PoC
In an application that sets Pagy::I18n.locale = params[:locale], the
loader appends .yml and reads <locale>.yml, so the request param
controls the target path. For example, pointing it at the app's
config/database.yml:
1. Send a request with ?locale=../../../config/database (adjust the
number of ../ to reach the app root from the gem's locales/
directory).
2. Pagy calls YAML.load_file on the resulting …/config/database.yml.
3. The outcome differs by whether that .yml exists, is readable, parses
as YAML, and has Pagy's expected structure — an existing, readable
config/database.yml raises a different error than a non-existent
path (which silently falls back to the default locale). This yields a
file
Details
Original advisory: https://github.com/advisories/GHSA-2xmw-f8j8-wfxc
Exploitation outlook
EPSS (FIRST.org) estimates each CVE’s probability of exploitation in the next 30 days — here is the CSIRTS.com read on those numbers.
- Low exploitation riskCVE-2026-546590.37% 30-day exploitation probability — currently an unlikely target, but scores change as exploit code circulates. Riskier than 30% of all scored CVEs.
Referenced CVEs
| CVE | CSIRTS overview | External |
|---|---|---|
| CVE-2026-54659 | coverage & exploitation status | NVD · CVE.org |
Same CVEs, other sources
How other CERTs, PSIRTs and databases cover the vulnerabilities in this advisory.
More from GitHub Security Advisories
- mediumGHSA-jr6p-8pjj-mfx6: Capsule has an incomplete fix of CVE-2026-22872: TenantResource RawItems and Generators s…2026-07-31
- mediumGHSA-68cj-mvg9-rgm2: Capsule: CapsuleConfiguration NodeMetadata regex fields lack webhook validation, allowing…2026-07-31
- mediumGHSA-ff84-5f28-78qj: re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex`…2026-07-31
- mediumGHSA-6hxr-mr5r-9836: re2: Global `String.prototype.match` with an empty-matchable pattern never advances → inf…2026-07-31
- mediumGHSA-x83g-979r-f5fh: Sylius Mollie Plugin has unauthenticated IDOR that leaks order token and customer PII2026-07-31