CVE-2026-54659
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
⚡ Watch CVE-2026-54659
Get an email if CVE-2026-54659 is added to CISA KEV, gains public exploit code, or a new advisory cites it — max one per day, one-click unsubscribe.
Exploitation outlook
- Low exploitation risk0.37% 30-day exploitation probability — currently an unlikely target, but scores change as exploit code circulates. Riskier than 30% of all EPSS-scored CVEs.
Advisory coverage (2)
External references
Embed the live status
— this badge updates automatically when the KEV or exploit status changes. How to embed it →
[](https://www.csirts.com/cve/CVE-2026-54659)