- 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