VDB
KO
MEDIUM

GHSA-2xmw-f8j8-wfxc

Pagy I18n locale option is not validated before being used in a file path

Quick fix

GHSA-2xmw-f8j8-wfxc — pagy: upgrade to the fixed version with the command below.

bundle update pagy

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:

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

Are you affected?

Enter the version of the package you're using.

Affected packages

RubyGems / pagy
Introduced in: 43.0.0 Fixed in: 43.5.6
Fix bundle update pagy

References