GHSA-2xmw-f8j8-wfxc

Suggest an improvement
Source
https://github.com/advisories/GHSA-2xmw-f8j8-wfxc
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-2xmw-f8j8-wfxc/GHSA-2xmw-f8j8-wfxc.json
JSON Data
https://api.test.osv.dev/v1/vulns/GHSA-2xmw-f8j8-wfxc
Aliases
Published
2026-07-28T22:25:29Z
Modified
2026-07-28T22:30:22.292697031Z
Severity
  • 6.9 (Medium) CVSS_V4 - 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 CVSS Calculator
Summary
Pagy I18n locale option is not validated before being used in a file path
Details

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-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.

Database specific
{
    "github_reviewed": true,
    "nvd_published_at": null,
    "cwe_ids": [
        "CWE-200",
        "CWE-22"
    ],
    "severity": "MODERATE",
    "github_reviewed_at": "2026-07-28T22:25:29Z"
}
References

Affected packages

RubyGems / pagy

Package

Name
pagy
Purl
pkg:gem/pagy

Affected ranges

Type
ECOSYSTEM
Events
Introduced
43.0.0
Fixed
43.5.6

Affected versions

43.*
43.0.0
43.0.1
43.0.2
43.0.3
43.0.4
43.0.5
43.0.6
43.0.7
43.1.0
43.1.1
43.1.2
43.1.3
43.1.4
43.1.5
43.1.6
43.1.7
43.1.8
43.2.0
43.2.1
43.2.2
43.2.3
43.2.4
43.2.5
43.2.6
43.2.7
43.2.8
43.2.9
43.2.10
43.3.0
43.3.1
43.3.2
43.3.3
43.4.0
43.4.1
43.4.2
43.4.3
43.4.4
43.5.0
43.5.1
43.5.2
43.5.3
43.5.4
43.5.5

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-2xmw-f8j8-wfxc/GHSA-2xmw-f8j8-wfxc.json"