PT-2026-65636 · Rubygems · Pagy
Published
2026-07-28
·
Updated
2026-07-28
CVSS v4.0
6.9
Medium
| Vector | AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N |
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:
ruby
# 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:
ruby
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]
endAny 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:- Send a request with
?locale=../../../config/database(adjust the number of../to reach the app root from the gem'slocales/directory). - Pagy calls
YAML.load fileon the resulting…/config/database.yml. - The outcome differs by whether that
.ymlexists, is readable, parses as YAML, and has Pagy's expected structure — an existing, readableconfig/database.ymlraises a different error than a non-existent path (which silently falls back to the default locale). This yields a file-existence / readability oracle for.ymlpaths, and the targeted file is read into the process during the attempt.
Impact
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.Fix
Information Disclosure
Path traversal
Found an issue in the description? Have something to add? Feel free to write us 👾
Related Identifiers
Affected Products
Pagy