Jul 29, 2026·6 min read·2 visits
A path traversal flaw in Pagy's I18n locale setter enables unauthenticated remote attackers to determine the existence and validity of YAML configuration files on the host filesystem via a side-channel oracle.
Pagy, an agile pagination gem for plain Ruby, contains a directory traversal vulnerability in its internationalization (I18n) module prior to version 43.5.6. An unauthenticated remote attacker can exploit this flaw by submitting path traversal sequences to the locale setter, allowing them to probe the server filesystem. The resulting behavior creates a highly reliable file existence and readability side-channel oracle for YAML files.
The affected component is the internationalization (I18n) module of Pagy, a widely used, lightweight pagination gem for the Ruby programming language. The security boundary is breached within the Pagy::I18n.locale= setter, which manages localization lookup tables. Because this setter handles locale assignments globally or thread-locally, applications that pass untrusted user input directly to it expose a critical file system interaction point.
The vulnerability is classified under CWE-22 (Improper Limitation of a Pathname to a Restricted Directory / 'Path Traversal') and CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor). When exploited, this weakness permits an attacker to escape the intended directory constraint and probe the server filesystem. The attack surface is typically exposed through query parameters or HTTP request headers that configure user language preferences.
Although Pagy enforces structural schema validation on parsed files—which prevents direct retrieval of raw file contents—the application's response variations create a highly reliable file existence and readability oracle. This channel allows attackers to map the existence of configuration files, database configuration files, or other system YAML documents.
The root cause of CVE-2026-54659 is the total absence of input validation or sanitization within the Pagy::I18n.locale= setter method. In vulnerable versions, this method accepts any arbitrary string and writes it directly to the thread-local storage key :pagy_locale.
During translation lookups, the library constructs a file path by appending the user-controlled locale string directly to the localized file directory path. This string interpolation uses a standard file path resolution mechanism, ROOT.join('locales', "#{locale}.yml"). When the input contains path traversal sequences like ../ or absolute path directives, Ruby's Pathname#join resolves the sequence relative to the system root, allowing the path to escape the intended locales directory.
The application subsequently attempts to load the constructed path using the standard Ruby YAML library, specifically YAML.load_file(path). If an application is configured to pass raw user input (such as params[:locale]) directly to the Pagy setter, an attacker can influence the file system path supplied to the parser. The parser then attempts to load any YAML file on the local file system that matches the manipulated path.
To understand the exact mechanics of the vulnerability and its remediation, we must examine the implementation within gem/lib/pagy/modules/i18n/i18n.rb. The vulnerable implementation accepted any string input, converting it directly using to_s and writing it to the thread-local storage context.
# Vulnerable code in gem/lib/pagy/modules/i18n/i18n.rb
def locale=(value)
Thread.current[:pagy_locale] = value.to_s
endThe corrective patch, introduced in version 43.5.6, implements a strict validation system. The patch introduces a regular expression constant LOCALE_PATTERN conforming to BCP 47/RFC 4647 language tag standards. The setter now uses Ruby's String#[] syntax to slice the string, returning the string if it matches the pattern or nil if it fails validation.
# Patched code in gem/lib/pagy/modules/i18n/i18n.rb
module I18n
class KeyError < KeyError; end
# Regular expression validating only compliant BCP 47 language tags
LOCALE_PATTERN = /\A[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*\z/
extend self
# Assigns a validated locale to the current thread
def locale=(value)
Thread.current[:pagy_locale] = value.to_s[LOCALE_PATTERN]
end
endThis validation pattern restricts acceptable input to letters and hyphens, strictly bound by the start-of-string (\A) and end-of-string (\z) anchors. This prevents multiline bypass attempts and completely blocks traversal characters like / or .. If an attacker attempts to inject a path traversal sequence, the pattern match fails, returning nil. This causes Pagy to fall back safely to the default locale configuration without executing file operations.
An attacker exploits this vulnerability by manipulating the parameter mapped to Pagy::I18n.locale. For example, an HTTP request to GET /items?locale=../../../../config/database causes the application to construct the file path /path/to/app/gem/locales/../../../../config/database.yml. Because Ruby resolves relative path traversals, the engine attempts to parse /path/to/app/config/database.yml.
The exploitation payload relies on side-channel analysis of application errors. If the targeted file does not exist, the framework handles the missing file error internally and falls back to the default locale, resulting in a normal HTTP 200 response. If the target file exists and is a valid YAML configuration, the load succeeds but fails to find the required Pagy localization schema, raising a Pagy::I18n::KeyError and causing an HTTP 500 response.
If the file exists but contains invalid YAML formatting, the Ruby interpreter raises a parsing exception such as Psych::SyntaxError, returning a distinct HTTP 500 error or stack trace. By correlating these application behaviors, an attacker can map file structures, confirm the existence of configuration directories, and locate critical database or credentials files.
A secondary, parallel vulnerability was resolved alongside the path traversal issue within GitHub PR #908. This vulnerability occurs in Pagy's development tool helper (dev_tools), located in gem/lib/pagy/modules/abilities/configurable.rb. The component failed to sanitize or validate the wand_scale parameter, creating an injection vector when exposed to user-controlled values.
# Vulnerable implementation in configurable.rb
def dev_tools(wand_scale: 1)
<<~HTML
<script id="pagy-wand" data-scale="#{wand_scale}">
#{ROOT.join('javascripts/wand.js').read}
</script>
HTML
endIf an application exposed this helper parameter directly to query inputs (e.g., Pagy.dev_tools(wand_scale: params[:scale])), an attacker could pass a string containing HTML escape characters. A payload such as 1"></script><script>alert(document.cookie)</script> would successfully break out of the script tag attribute and execute arbitrary JavaScript in the context of the user's browser session.
# Patched implementation in configurable.rb
def dev_tools(wand_scale: 1)
<<~HTML
<script id="pagy-wand" data-scale="#{wand_scale.to_f}">
#{ROOT.join('javascripts/wand.js').read}
</script>
HTML
endThe remediation forces numeric type conversion by appending .to_f to the interpolated variable. When Ruby parses a string containing mixed malicious characters using .to_f, it truncates the input at the first non-numeric character. This turns the injection string into a harmless float value (e.g., 1.0), eliminating the cross-site scripting vector entirely.
The primary security impact of CVE-2026-54659 is localized information disclosure and filesystem reconnaissance. An unauthenticated remote attacker can probe the host filesystem to verify the presence of critical system files, framework configurations, and environment-specific database files. This provides the necessary intelligence to construct targeted secondary attacks.
The CVSS v4.0 base score is calculated at 6.9, reflecting a medium-severity profile. The metric string is 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. This indicates that while the vulnerability is remotely exploitable without authentication (AV:N/PR:N/UI:N), the impact is confined to low confidentiality loss (VC:L) without administrative modification capabilities or direct system compromise.
In combination with the secondary XSS vector in dev_tools, the overall exposure of unpatched installations is elevated in non-production or development-enabled environments. Developers must recognize that even though raw file content read capabilities are limited by schema validation checks, file existence oracles provide crucial telemetry to attackers mapping application environments.
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| Product | Affected Versions | Fixed Version |
|---|---|---|
pagy ddnexus | >= 43.0.0, < 43.5.6 | 43.5.6 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22, CWE-200 |
| Attack Vector | Network (Unauthenticated) |
| CVSS v4.0 Score | 6.9 (Medium) |
| Exploit Status | Proof of Concept / Patch Available |
| Impact | Local File Existence Reconnaissance & XSS |
| CISA KEV Status | Not Listed |
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
CVE-2025-6120 is a critical memory corruption vulnerability in the Open Asset Import Library (Assimp) affecting versions up to and including 5.4.3. The flaw is located in the Half-Life 1 MDL file format loader, specifically within the read_meshes function in HL1MDLLoader.cpp. It arises due to a lack of verification checks on array, bone, skin, or vertex indices parsed directly from a binary stream, resulting in a heap-based buffer overflow or out-of-bounds memory access.
A local file disclosure vulnerability exists in the Rust-based package skilo. When copying files during skill installation, the application recursively traverses directories but dereferences symbolic links, resulting in unauthorized local file reading.
CVE-2026-66064 is an access control list (ACL) and blocklist bypass vulnerability in the goshs file server prior to version 2.1.5. Due to an inconsistency between uncleaned raw URI path evaluation and normalized file access, remote unauthenticated attackers can retrieve protected files, including the configuration file containing password hashes, by appending a trailing slash to the requested path.
CVE-2026-54719 is a high-severity Access Control List (ACL) authorization bypass vulnerability in goshs, a lightweight HTTPS-capable server used for file sharing. The issue allows unauthenticated network attackers to completely bypass file-level and directory-level authentication mechanisms and blocklists by requesting protected resources via the bulk ZIP-download route (?bulk). This vulnerability represents a residual flaw following a partial remediation attempt for CVE-2026-40189.
NocoBase, an extensible low-code platform, was found to contain an information exposure vulnerability in its SQL Collection plugin. The validator designed to inspect SQL queries used an incomplete keyword-based blacklist, allowing users with administrative or collection creation privileges to bypass restrictions and execute arbitrary SELECT queries against PostgreSQL system catalogs. This flaw permits unauthenticated or privilege-escalated access to critical system files, active connection listings, and system user password hashes.
An issue in the ruby-oauth2 gem allows unauthenticated attackers to steal OAuth access tokens or perform Server-Side Request Forgery (SSRF) via a protocol-relative URI in HTTP Location redirect headers.