Skip to content

Commit a17cb99

Browse files
jeremyevansioquatix
authored andcommitted
Fix root prefix bug in Rack::Static
This is similar to the fix of CVE-2026-22860 for Rack::Directory.
1 parent 59a0966 commit a17cb99

3 files changed

Lines changed: 20 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. For info on
1515
- [CVE-2026-34826](https://github.com/advisories/GHSA-x8cg-fq8g-mxfx) Multipart byte range processing allows denial of service via excessive overlapping ranges.
1616
- [CVE-2026-34835](https://github.com/advisories/GHSA-g2pf-xv49-m2h5) `Rack::Request` accepts invalid Host characters, enabling host allowlist bypass.
1717
- [CVE-2026-34830](https://github.com/advisories/GHSA-qv7j-4883-hwh7) `Rack::Sendfile` header-based `X-Accel-Mapping` regex injection enables unauthorized `X-Accel-Redirect`.
18+
- [CVE-2026-34785](https://github.com/advisories/GHSA-h2jq-g4cq-5ppq) `Rack::Static` prefix matching can expose unintended files under the static root.
1819

1920
## [3.1.20] - 2026-02-16
2021

lib/rack/static.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ class Static
9393
def initialize(app, options = {})
9494
@app = app
9595
@urls = options[:urls] || ["/favicon.ico"]
96+
if @urls.kind_of?(Array)
97+
@urls = @urls.map { |url| [url, url.end_with?('/') ? url : "#{url}/".freeze].freeze }.freeze
98+
end
9699
@index = options[:index]
97100
@gzip = options[:gzip]
98101
@cascade = options[:cascade]
@@ -115,7 +118,7 @@ def overwrite_file_path(path)
115118
end
116119

117120
def route_file(path)
118-
@urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 }
121+
@urls.kind_of?(Array) && @urls.any? { |url, url_slash| path == url || path.start_with?(url_slash) }
119122
end
120123

121124
def can_serve(path)

test/spec_static.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,21 @@ def static(app, *args)
266266
res.headers['cache-control'].must_be_nil
267267
end
268268

269+
it "not allow directory traversal via root prefix bypass" do
270+
Dir.mktmpdir do |dir|
271+
root = File.join(dir, "root")
272+
outside = "#{root}_test"
273+
FileUtils.mkdir_p(root)
274+
FileUtils.mkdir_p(outside)
275+
FileUtils.touch(File.join(outside, "test.txt"))
276+
277+
app = Rack::Static.new(proc { |env| [403, {}, ""] }, root: dir, urls: ["/root"])
278+
res = Rack::MockRequest.new(app).get("/root_test/test.txt")
279+
280+
res.must_be :forbidden?
281+
end
282+
end
283+
269284
it "expands the root path upon the middleware initialization" do
270285
relative_path = STATIC_OPTIONS[:root].sub("#{Dir.pwd}/", '')
271286
opts = { urls: [""], root: relative_path, index: 'index.html' }

0 commit comments

Comments
 (0)