GitHub Advisoryadvisory · RubyGems
GHSA-2xmw-f8j8-wfxc Pagy I18n locale option is not validated before being used in a file path
MEDIUM
Official page ↗
- Aliases
- CVE-2026-54659
- Ecosystem
- RubyGems
- Packages
- pagy
- Severity
- Moderate
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
- Weaknesses
- CWE-200, CWE-22
- Reviewed
- 2026-07-28
Affected packages
• RubyGems pagy: >= 43.0.0, < 43.5.6 → fixed in 43.5.6
Description
Summary
`Pagy::I18n.locale=` did not validate its argument before using it as a
path component to load the matching dictionary file (`.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]
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 `.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-existence / readability oracle for `.yml` paths, 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.
Related
Alias · CVE (1)
- CVE-2026-54659 Pagy is agnostic pagination in plain Ruby. vulnerability