CVE-2026-54659Medium▾ SunlitPagy I18n locale option is not validated before being used in a file path
▾ Sunlit zone — Low / medium · no exploitation signal
impact 27.5 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 29.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.4%
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.
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.
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:
?locale=../../../config/database (adjust the
number of ../ to reach the app root from the gem's locales/
directory).YAML.load_file on the resulting …/config/database.yml..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-existence / readability oracle for .yml paths, and the targeted
file is read into the process during the attempt.Information disclosure (CWE-22 / CWE-200): a file-existence / readability
oracle for .yml paths on the host, plus a server-side read of
attacker-chosen files into the process. The file contents are not
returned in the response.
Only applications that pass unsanitized end-user input into
Pagy::I18n.locale= are affected. Applications that set the locale from
trusted values are not affected.
Patched: pagy 43.5.6.
Workaround (if you cannot upgrade): validate the locale before
assigning it, e.g.
Pagy::I18n.locale = params[:locale].to_s[/\A[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*\z/],
or restrict it to your known set of locales.
pagy >= 43.0.0, < 43.5.6Upgrade to a patched release:
pagy 43.5.6Connected by shared product, vendor, weakness, or advisory.
CVE-2021-25122High· 7.5When responding to new h2c connection requests, Apache Tomcat versions 10.0.0-M1 to 10.0.0, 9.0.0.M1 to 9.0.41 and 8.5.0 to 8.5.61 could duplicate request headers and a limited amount of request body from one request to another meaning u…
CVE-2022-31746Medium· 6.5Internal URLs are protected by a secret UUID key, which could have been leaked to web page through the Referrer header
CVE-2026-45623High· 7.5postcss: PostCSS: Information disclosure and denial of service via crafted CSS input (CVE-2026-45623)
CVE-2026-47735HighArc is an open, SQL-native time-series database for telemetry
CVE-2026-49742HighTYPO3 CMS has Broken Access Control in its Media Module
CVE-2026-49356Low· 3.2@babel/core: Arbitrary File Read via sourceMappingURL Comment