NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
💎
💎 RubyGems
Not in CISA KEV
MEDIUM severity

CVE-2026-126354 — addressable

MEDIUMFix: sporkmonger/addressable@25c7c41

CVE-2026-126354 is a medium-severity (CVSS 4.8) vulnerability in addressable. A fix is available for addressable — see the affected versions and patch details below.

Addressable URI normalization resolves some dotted path segments to the wrong path

Also known asGHSA-8gmh-2hhj-2hv9
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Nov 13, 2026 · OSV.dev, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
💎addressable

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects RubyGems packages — download data is not available via public APIs for these ecosystems.

Description

Summary

Addressable::URI.normalize_path resolves some paths containing .. segments to a different path than RFC 3986 does. When a path climbs above the root before any other .. is resolved, the segments that later .. segments should remove are instead kept: /../../public/x/../../secret normalizes to /public/x/secret instead of /secret. URI#normalize, URI#join, URI#== and URI#=== all use this normalization. An application that checks a normalized Addressable path against an allowlist, but sends the original URI to something that resolves it according to RFC 3986, can be made to allow a request for a path outside the allowlist.

Details

normalize_path in 2.9.0 (lib/addressable/uri.rb) removes dot segments by repeating a set of rewrites until nothing changes. On each pass it finds the first /segment/../ pair with RULE_2B_2C, then replaces every textual copy of that pair in the path with /:

pair = normalized_path.match(RULE_2B_2C)
...
regexp = "/#{Regexp.escape(parent.to_s)}/\\.\\./|"
regexp += "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
...
mod ||= normalized_path.gsub!(Regexp.new(regexp), SLASH)

When the first pair found is /../../, where the "segment" is itself .., every /../../ in the path is replaced with /. That includes copies later in the path, where each .. should have removed the segment before it. For /../../public/x/../../secret:

  • RFC 3986 §5.2.4 drops the two leading .. segments, which can't go above the root, then removes x and public, giving /secret.
  • 2.9.0 replaces both copies of /../../ with / in one step, giving /public/x/secret.

Paths where an ordinary /segment/../ pair comes first, such as /a/../../b/c/../../d, are resolved correctly.

This was introduced in 2.2.5. Versions 2.0.0 through 2.2.4 and earlier versions resolve these paths correctly. It is fixed in 2.9.1, which replaces the rewrite loop with a single pass that correctly follows RFC 3986 §5.2.4.

Proof of concept

require "addressable/uri"
require "uri"

url = "http://example.com/../../public/x/../../secret"

# Addressable's normalized path, compared with RFC 3986 resolution by Ruby's URI library
puts Addressable::URI.parse(url).normalize.path
puts URI.join("http://example.com/", "../../public/x/../../secret").path

# join and == use the same normalization
puts Addressable::URI.join("http://example.com/", "../../public/x/../../secret").path
puts Addressable::URI.parse(url) == Addressable::URI.parse("http://example.com/public/x/secret")

# An allowlist check on the normalized path passes, but the request reaches /secret
def allowed?(url) = Addressable::URI.parse(url).normalize.path.start_with?("/public/")
puts allowed?(url)

Output with Addressable 2.9.0:

/public/x/secret
/secret
/public/x/secret
true
true

Output with Addressable 2.9.1:

/secret
/secret
/secret
false
false

Impact

This is an interpretation conflict (CWE-436) between Addressable and other RFC 3986 resolvers, which could lead to path traversal past an allowlist (CWE-22). An application is affected when it does both of these:

  • makes an access decision on a path normalized or joined by Addressable, such as "only paths under /public/ may be proxied" or "only these redirect targets are allowed";
  • sends the original, unnormalized URI to a component that resolves dot segments itself, such as an HTTP client, a proxy, a web server or a browser.

An attacker may be able to bypass a check by exploiting behavioral differences between URI parsers. Impact from this is application-dependent. Applications that use Addressable's normalized form for both the check and the request are unaffected, as are applications that reject paths containing .. segments.

As a work-around for unpatched versions, before making access decisions on a path, either reject any containing .. segments, including percent-encoded forms such as %2e%2e, or use the same normalized value for both the check and the request.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
💎RubyGemsaddressable≥ 2.2.5&&< 2.9.12.9.1bundle update addressable --conservative

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for addressable, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update addressable to 2.9.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-126354 is resolved across your whole dependency graph.

  3. Workarounds

    Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.

Frequently Asked Questions

### Summary `Addressable::URI.normalize_path` resolves some paths containing `..` segments to a different path than RFC 3986 does. When a path climbs above the root before any other `..` is resolved, the segments that later `..` segments should remove are instead kept: `/../../public/x/../../secret` normalizes to `/public/x/secret` instead of `/secret`. `URI#normalize`, `URI#join`, `URI#==` and `URI#===` all use this normalization. An application that checks a normalized Addressable path against an allowlist, but sends the original URI to something that resolves it according to RFC 3986, can
O3 Security · Impact-Aware SCA

Is CVE-2026-126354 in your dependencies?

Find it across RubyGems, including transitive dependencies.