Version in base suite: 3.1.18-1~deb13u1 Base version: ruby-rack_3.1.18-1~deb13u1 Target version: ruby-rack_3.1.20-0+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/r/ruby-rack/ruby-rack_3.1.18-1~deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/r/ruby-rack/ruby-rack_3.1.20-0+deb13u1.dsc CHANGELOG.md | 19 +++++++++++++++++++ debian/changelog | 10 ++++++++++ debian/gbp.conf | 4 ++-- lib/rack/directory.rb | 7 +++++-- lib/rack/multipart/parser.rb | 2 +- lib/rack/version.rb | 2 +- test/spec_directory.rb | 17 ++++++++++++++++- test/spec_multipart.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 96 insertions(+), 7 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmphdhy2vdy/ruby-rack_3.1.18-1~deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmphdhy2vdy/ruby-rack_3.1.20-0+deb13u1.dsc: no acceptable signature found diff -Nru ruby-rack-3.1.18/CHANGELOG.md ruby-rack-3.1.20/CHANGELOG.md --- ruby-rack-3.1.18/CHANGELOG.md 2025-10-10 00:39:16.000000000 +0000 +++ ruby-rack-3.1.20/CHANGELOG.md 2026-02-16 03:35:22.000000000 +0000 @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference [Keep A Changelog](https://keepachangelog.com/en/1.0.0/). +## Unreleased + +### Security + +- [CVE-2026-25500](https://github.com/advisories/GHSA-whrj-4476-wvmp) XSS injection via malicious filename in `Rack::Directory`. +- [CVE-2026-22860](https://github.com/advisories/GHSA-mxw3-3hh2-x2mh) Directory traversal via root prefix bypass in `Rack::Directory`. + +## [3.1.19] - 2025-11-03 + +### Fixed + +- Multipart parser: limit MIME header size check to the unread buffer region to avoid false `multipart mime part header too large` errors when previously read data accumulates in the scan buffer. ([#2392](https://github.com/rack/rack/pull/2392), [@alpaca-tc](https://github.com/alpaca-tc), [@willnet](https://github.com/willnet), [@krororo](https://github.com/krororo)) + ## [3.1.18] - 2025-10-10 ### Security @@ -380,6 +393,12 @@ - Fix multipart filename generation for filenames that contain spaces. Encode spaces as "%20" instead of "+" which will be decoded properly by the multipart parser. ([#1736](https://github.com/rack/rack/pull/1645), [@muirdm](https://github.com/muirdm)) - `Rack::Request#scheme` returns `ws` or `wss` when one of the `X-Forwarded-Scheme` / `X-Forwarded-Proto` headers is set to `ws` or `wss`, respectively. ([#1730](https://github.com/rack/rack/issues/1730), [@erwanst](https://github.com/erwanst)) +## [2.2.21] - 2025-11-03 + +### Fixed + +- Multipart parser: limit MIME header size check to the unread buffer region to avoid false `multipart mime part header too large` errors when previously read data accumulates in the scan buffer. ([#2392](https://github.com/rack/rack/pull/2392), [@alpaca-tc](https://github.com/alpaca-tc), [@willnet](https://github.com/willnet), [@krororo](https://github.com/krororo)) + ## [2.2.20] - 2025-10-10 ### Security diff -Nru ruby-rack-3.1.18/debian/changelog ruby-rack-3.1.20/debian/changelog --- ruby-rack-3.1.18/debian/changelog 2025-10-22 07:52:58.000000000 +0000 +++ ruby-rack-3.1.20/debian/changelog 2026-03-10 04:14:22.000000000 +0000 @@ -1,3 +1,13 @@ +ruby-rack (3.1.20-0+deb13u1) trixie-security; urgency=high + + * New upstream version 3.1.20. + - CVE-2026-25500: XSS injection via malicious filename + in `Rack::Directory`. (Closes: #1128480) + - CVE-2026-22860: Directory traversal via root prefix + bypass in `Rack::Directory`. (Closes: #1128479) + + -- Utkarsh Gupta Tue, 10 Mar 2026 09:44:22 +0530 + ruby-rack (3.1.18-1~deb13u1) trixie-security; urgency=medium * New upstream version 3.1.18. diff -Nru ruby-rack-3.1.18/debian/gbp.conf ruby-rack-3.1.20/debian/gbp.conf --- ruby-rack-3.1.18/debian/gbp.conf 2025-10-22 07:52:58.000000000 +0000 +++ ruby-rack-3.1.20/debian/gbp.conf 2026-03-10 04:14:22.000000000 +0000 @@ -1,4 +1,4 @@ [DEFAULT] -debian-branch = debian/latest -upstream-branch = upstream/latest +debian-branch = debian/trixie +upstream-branch = upstream-trixie pristine-tar = True diff -Nru ruby-rack-3.1.18/lib/rack/directory.rb ruby-rack-3.1.20/lib/rack/directory.rb --- ruby-rack-3.1.18/lib/rack/directory.rb 2025-10-10 00:39:16.000000000 +0000 +++ ruby-rack-3.1.20/lib/rack/directory.rb 2026-02-16 03:35:22.000000000 +0000 @@ -17,7 +17,7 @@ # If +app+ is not specified, a Rack::Files of the same +root+ will be used. class Directory - DIR_FILE = "%s%s%s%s\n" + DIR_FILE = "%s%s%s%s\n" DIR_PAGE_HEADER = <<-PAGE %s @@ -82,6 +82,7 @@ # Set the root directory and application for serving files. def initialize(root, app = nil) @root = ::File.expand_path(root) + @root_with_separator = @root.end_with?(::File::SEPARATOR) ? @root : "#{@root}#{::File::SEPARATOR}" @app = app || Files.new(@root) @head = Head.new(method(:get)) end @@ -118,7 +119,9 @@ # Rack response to use for requests with paths outside the root, or nil if path is inside the root. def check_forbidden(path_info) return unless path_info.include? ".." - return if ::File.expand_path(::File.join(@root, path_info)).start_with?(@root) + + expanded_path = ::File.expand_path(::File.join(@root, path_info)) + return if expanded_path == @root || expanded_path.start_with?(@root_with_separator) body = "Forbidden\n" [403, { CONTENT_TYPE => "text/plain", diff -Nru ruby-rack-3.1.18/lib/rack/multipart/parser.rb ruby-rack-3.1.20/lib/rack/multipart/parser.rb --- ruby-rack-3.1.18/lib/rack/multipart/parser.rb 2025-10-10 00:39:16.000000000 +0000 +++ ruby-rack-3.1.20/lib/rack/multipart/parser.rb 2026-02-16 03:35:22.000000000 +0000 @@ -444,7 +444,7 @@ else # We raise if the mime part header is too large, to avoid unbounded memory # buffering. Note that the actual limit is the higher of 64KB and the buffer size (1MB by default) - raise Error, "multipart mime part header too large" if @sbuf.string.bytesize > MIME_HEADER_BYTESIZE_LIMIT + raise Error, "multipart mime part header too large" if @sbuf.rest.bytesize > MIME_HEADER_BYTESIZE_LIMIT return :want_read end diff -Nru ruby-rack-3.1.18/lib/rack/version.rb ruby-rack-3.1.20/lib/rack/version.rb --- ruby-rack-3.1.18/lib/rack/version.rb 2025-10-10 00:39:16.000000000 +0000 +++ ruby-rack-3.1.20/lib/rack/version.rb 2026-02-16 03:35:22.000000000 +0000 @@ -12,7 +12,7 @@ # so it should be enough just to require 'rack' in your code. module Rack - RELEASE = "3.1.18" + RELEASE = "3.1.20" # Return the Rack release as a dotted string. def self.release diff -Nru ruby-rack-3.1.18/test/spec_directory.rb ruby-rack-3.1.20/test/spec_directory.rb --- ruby-rack-3.1.18/test/spec_directory.rb 2025-10-10 00:39:16.000000000 +0000 +++ ruby-rack-3.1.20/test/spec_directory.rb 2026-02-16 03:35:22.000000000 +0000 @@ -46,7 +46,7 @@ res.must_be :ok? assert_includes(res.body, '') - assert_includes(res.body, "href='cgi") + assert_includes(res.body, "href='./cgi") end it "serve directory indices" do @@ -149,6 +149,21 @@ res.must_be :forbidden? end + it "not allow directory traversal via root prefix bypass" do + Dir.mktmpdir do |dir| + root = File.join(dir, "root") + outside = "#{root}_test" + FileUtils.mkdir_p(root) + FileUtils.mkdir_p(outside) + FileUtils.touch(File.join(outside, "test.txt")) + + app = Rack::Directory.new(root) + res = Rack::MockRequest.new(app).get("/../#{File.basename(outside)}/") + + res.must_be :forbidden? + end + end + it "not allow dir globs" do Dir.mktmpdir do |dir| weirds = "uploads/.?/.?" diff -Nru ruby-rack-3.1.18/test/spec_multipart.rb ruby-rack-3.1.20/test/spec_multipart.rb --- ruby-rack-3.1.18/test/spec_multipart.rb 2025-10-10 00:39:16.000000000 +0000 +++ ruby-rack-3.1.20/test/spec_multipart.rb 2026-02-16 03:35:22.000000000 +0000 @@ -288,6 +288,48 @@ wr.close end + it "parses when the MIME head terminator straddles the BUFSIZE boundary" do + boundary = '------WebKitFormBoundaryysVLFAjttLkewYBx' + + data = StringIO.new + data.write("--#{boundary}") + data.write("\r\n") + + data.write('content-disposition: form-data; name="a"') + data.write("\r\n") + data.write("\r\n") + # Fill to the end of the first 1MB chunk so the header's `\r\n` is in the next chunk. + data.write("0" * (1024 * 1024 - 174)) + data.write("\r\n") + data.write("--#{boundary}") + data.write("\r\n") + data.write('content-disposition: form-data; name="b"') + # First 1MB chunk separator is here + data.write("\r\n") + data.write("\r\n") + data.write("0" * (1024 * 1024 - 88)) + data.write("\r\n") + data.write("--#{boundary}") + data.write("\r\n") + data.write('content-disposition: form-data; name="c"') + # Second 1MB chunk separator is here + data.write("\r\n") + data.write("\r\n") + data.write("hello") + data.write("\r\n") + data.write("--#{boundary}--\r\n") + data.rewind + + fixture = { + "CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}", + "CONTENT_LENGTH" => data.length.to_s, + :input => data, + } + + env = Rack::MockRequest.env_for '/', fixture + Rack::Multipart.parse_multipart(env).keys.must_equal(["a", "b", "c"]) + end + it "rejects excessive buffered mime data size in a single parameter" do rd, wr = IO.pipe def rd.rewind; end