GHSA-8gmh-2hhj-2hv9: Addressable URI normalization resolves some dotted path segments to the wrong path

  • Severity: MEDIUM
  • CVE: CVE-2026-126354
  • Ecosystems: rubygems
  • Packages: addressable

### 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 `/`:

“`ruby
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 a…

Source: GitHub Security Advisories · 2026-11-12