Backport 0.9.41 / 0.9.44 fixes for possible path traversal issues
This commit is contained in:
parent
59abf5c2a9
commit
9579977a5a
4 changed files with 380 additions and 1 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Name: rubygem-%{gem_name}
|
||||
Version: 0.9.37
|
||||
Release: 4%{?dist}
|
||||
Release: 5%{?dist}
|
||||
|
||||
Summary: Documentation tool for consistent and usable documentation in Ruby
|
||||
|
||||
|
|
@ -20,6 +20,14 @@ Source0: https://rubygems.org/gems/%{gem_name}-%{version}.gem
|
|||
Source1: %{gem_name}-%{version}-test-missing-files.tar.gz
|
||||
# Source1 is created by $ bash %%SOURCE2 %%version
|
||||
Source2: yard-create-missing-test-files.sh
|
||||
# https://github.com/lsegal/yard/pull/1650
|
||||
Patch0: yard-pr1650-avoid-global-namespace-collision.patch
|
||||
# https://github.com/lsegal/yard/security/advisories/GHSA-pxcc-8665-phx8
|
||||
# https://github.com/lsegal/yard/commit/f78c19f0dd33a407085b4ed181bb60c0aa0078b4
|
||||
Patch1: yard-0.9.44-fix-possible-path-traversal.patch
|
||||
# https://github.com/lsegal/yard/security/advisories/GHSA-3jfp-46x4-xgfj
|
||||
# https://github.com/sysfce2/ruby-yard/commit/bdbff11302a035ce948d8ddad804c362c31c5be1
|
||||
Patch2: yard-0.9.41-fix-possible-path-traversal.patch
|
||||
|
||||
# The 'irb/notifier' might be required for parsing of some old Ruby code.
|
||||
# https://github.com/lsegal/yard/blob/v0.9.24/lib/yard/parser/ruby/legacy/irb/slex.rb#L13
|
||||
|
|
@ -58,6 +66,9 @@ Documentation for %{name}.
|
|||
|
||||
%prep
|
||||
%setup -q -n %{gem_name}-%{version} -b1
|
||||
%patch -P0 -p1
|
||||
%patch -P1 -p1
|
||||
%patch -P2 -p1
|
||||
mv ../%{gem_name}-%{version}.gemspec .
|
||||
|
||||
%build
|
||||
|
|
@ -112,6 +123,11 @@ rspec -r spec_helper spec
|
|||
%doc %{gem_instdir}/docs/
|
||||
|
||||
%changelog
|
||||
* Thu May 28 2026 Mamoru TASAKA <mtasaka@fedoraproject.org> - 0.9.37-5
|
||||
- Backport 0.9.41 / 0.9.44 fixes for possible path traversal issues
|
||||
- https://github.com/lsegal/yard/security/advisories/GHSA-3jfp-46x4-xgfj
|
||||
- https://github.com/lsegal/yard/security/advisories/GHSA-pxcc-8665-phx8
|
||||
|
||||
* Fri Jul 25 2025 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.37-4
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild
|
||||
|
||||
|
|
|
|||
53
yard-0.9.41-fix-possible-path-traversal.patch
Normal file
53
yard-0.9.41-fix-possible-path-traversal.patch
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
From bdbff11302a035ce948d8ddad804c362c31c5be1 Mon Sep 17 00:00:00 2001
|
||||
From: Loren Segal <lsegal@soen.ca>
|
||||
Date: Thu, 16 Apr 2026 10:44:19 -0700
|
||||
Subject: [PATCH] Fix possible path traversal with document_root set in yard
|
||||
server
|
||||
|
||||
See [GHSA-xfhh-rx56-rxcr](https://github.com/lsegal/yard/security/advisories/GHSA-xfhh-rx56-rxcr)
|
||||
|
||||
Closes #1448
|
||||
---
|
||||
CHANGELOG.md | 2 ++
|
||||
lib/yard/server/commands/base.rb | 2 +-
|
||||
spec/server/commands/base_spec.rb | 6 ++++++
|
||||
3 files changed, 9 insertions(+), 1 deletion(-)
|
||||
|
||||
@@ -11,6 +12,7 @@
|
||||
- Add support for `rdoc-image:...` syntax in HybridMarkup (#1676)
|
||||
- Add support for colon suffix code blocks in HybridMarkup (rdoc compatibility)
|
||||
- Fix responsiveness and state issues with nav frame links in `yard server`
|
||||
+
|
||||
# [0.9.40] - April 12th, 2026
|
||||
|
||||
[0.9.40]: https://github.com/lsegal/yard/compare/v0.9.39...v0.9.40
|
||||
diff --git a/lib/yard/server/commands/base.rb b/lib/yard/server/commands/base.rb
|
||||
index ef9d83f3a..c7d207cb3 100644
|
||||
--- a/lib/yard/server/commands/base.rb
|
||||
+++ b/lib/yard/server/commands/base.rb
|
||||
@@ -88,7 +88,7 @@ def initialize(opts = {})
|
||||
# of status, headers, and body wrapped in an array.
|
||||
def call(request)
|
||||
self.request = request
|
||||
- self.path ||= request.path_info[1..-1]
|
||||
+ self.path ||= File.cleanpath(request.path_info[1..-1])
|
||||
self.headers = {'Content-Type' => 'text/html'}
|
||||
self.body = ''
|
||||
self.status = 200
|
||||
diff --git a/spec/server/commands/base_spec.rb b/spec/server/commands/base_spec.rb
|
||||
index 82c9e3096..c1726845c 100644
|
||||
--- a/spec/server/commands/base_spec.rb
|
||||
+++ b/spec/server/commands/base_spec.rb
|
||||
@@ -48,6 +48,12 @@ def run; cache 'foo' end
|
||||
end
|
||||
|
||||
describe "#call" do
|
||||
+ it "sanitizes path_info" do
|
||||
+ cmd = MyProcCommand.new { self.body = path }
|
||||
+ _, _, b = *cmd.call(mock_request('/../../a/b/c'))
|
||||
+ expect(b).to eq ['a/b/c']
|
||||
+ end
|
||||
+
|
||||
it "handles a NotFoundError and use message as body" do
|
||||
cmd = MyProcCommand.new { raise NotFoundError, "hello world" }
|
||||
s, _, b = *cmd.call(mock_request('/foo'))
|
||||
153
yard-0.9.44-fix-possible-path-traversal.patch
Normal file
153
yard-0.9.44-fix-possible-path-traversal.patch
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
From f78c19f0dd33a407085b4ed181bb60c0aa0078b4 Mon Sep 17 00:00:00 2001
|
||||
From: Loren Segal <lsegal@soen.ca>
|
||||
Date: Mon, 25 May 2026 12:49:29 -0700
|
||||
Subject: [PATCH] Fix possible path traversal in StaticCaching
|
||||
|
||||
Fixes [GHSA-pxcc-8665-phx8](https://github.com/lsegal/yard/security/advisories/GHSA-pxcc-8665-phx8)
|
||||
---
|
||||
lib/yard/server/commands/base.rb | 12 +++------
|
||||
lib/yard/server/static_caching.rb | 41 ++++++++++++++++++++++++++----
|
||||
spec/server/commands/base_spec.rb | 7 +++++
|
||||
spec/server/static_caching_spec.rb | 6 +++++
|
||||
4 files changed, 52 insertions(+), 14 deletions(-)
|
||||
|
||||
diff --git a/lib/yard/server/commands/base.rb b/lib/yard/server/commands/base.rb
|
||||
index c7d207cb3..9739b4c9c 100644
|
||||
--- a/lib/yard/server/commands/base.rb
|
||||
+++ b/lib/yard/server/commands/base.rb
|
||||
@@ -1,6 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
-require 'fileutils'
|
||||
-
|
||||
module YARD
|
||||
module Server
|
||||
module Commands
|
||||
@@ -32,6 +30,8 @@ module Commands
|
||||
# @abstract
|
||||
# @see #run
|
||||
class Base
|
||||
+ include StaticCaching
|
||||
+
|
||||
# @group Basic Command and Adapter Options
|
||||
|
||||
# @return [Hash] the options passed to the command's constructor
|
||||
@@ -163,13 +163,7 @@ def render(object = nil)
|
||||
# @return [String] the same cached data (for chaining)
|
||||
# @see StaticCaching
|
||||
def cache(data)
|
||||
- if caching && adapter.document_root
|
||||
- path = File.join(adapter.document_root, request.path_info.sub(/\.html$/, '') + '.html')
|
||||
- path = path.sub(%r{/\.html$}, '.html')
|
||||
- FileUtils.mkdir_p(File.dirname(path))
|
||||
- log.debug "Caching data to #{path}"
|
||||
- File.open(path, 'wb') {|f| f.write(data) }
|
||||
- end
|
||||
+ super if caching
|
||||
self.body = data
|
||||
end
|
||||
|
||||
diff --git a/lib/yard/server/static_caching.rb b/lib/yard/server/static_caching.rb
|
||||
index ca43b15ac..f91fcd825 100644
|
||||
--- a/lib/yard/server/static_caching.rb
|
||||
+++ b/lib/yard/server/static_caching.rb
|
||||
@@ -1,4 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
+require 'fileutils'
|
||||
+
|
||||
module YARD
|
||||
module Server
|
||||
# Implements static caching for requests.
|
||||
@@ -10,9 +12,8 @@ module StaticCaching
|
||||
# implement your own +#check_static_cache+ method and mix the module into
|
||||
# the Router class.
|
||||
#
|
||||
- # Note that caching does not occur here. This method simply checks for
|
||||
- # the existence of cached data. To actually cache a response, see
|
||||
- # {Commands::Base#cache}.
|
||||
+ # This method checks for the existence of cached data. To actually cache
|
||||
+ # a response, see {#cache}.
|
||||
#
|
||||
# @example Implementing In-Memory Cache Checking
|
||||
# module MemoryCaching
|
||||
@@ -33,14 +34,44 @@ module StaticCaching
|
||||
# @see Commands::Base#cache
|
||||
def check_static_cache
|
||||
return nil unless adapter.document_root
|
||||
- cache_path = File.join(adapter.document_root, request.path.sub(/\.html$/, '') + '.html')
|
||||
- cache_path = cache_path.sub(%r{/\.html$}, '.html')
|
||||
+ cache_path = cache_path(request.path)
|
||||
+ return nil unless cache_path
|
||||
+
|
||||
if File.file?(cache_path)
|
||||
log.debug "Loading cache from disk: #{cache_path}"
|
||||
return [200, {'Content-Type' => 'text/html'}, [File.read_binary(cache_path)]]
|
||||
end
|
||||
nil
|
||||
end
|
||||
+
|
||||
+ # Caches rendered HTML response data to disk.
|
||||
+ #
|
||||
+ # @param [String] data the data to cache
|
||||
+ # @return [void]
|
||||
+ # @since 0.9.44
|
||||
+ def cache(data)
|
||||
+ return unless adapter.document_root
|
||||
+
|
||||
+ path = cache_path(request.path_info)
|
||||
+ return unless path
|
||||
+
|
||||
+ FileUtils.mkdir_p(File.dirname(path))
|
||||
+ log.debug "Caching data to #{path}"
|
||||
+ File.open(path, 'wb') {|f| f.write(data) }
|
||||
+ end
|
||||
+
|
||||
+ private
|
||||
+
|
||||
+ def cache_path(request_path)
|
||||
+ return nil if request_path.split(/[\/\\]/).include?('..')
|
||||
+
|
||||
+ path = request_path.sub(/\.html$/, '') + '.html'
|
||||
+ path = path.sub(%r{\A/+}, '')
|
||||
+ return nil if path =~ /\A[A-Za-z]:/
|
||||
+
|
||||
+ path = File.cleanpath(path)
|
||||
+ File.join(adapter.document_root, path)
|
||||
+ end
|
||||
end
|
||||
end
|
||||
end
|
||||
diff --git a/spec/server/commands/base_spec.rb b/spec/server/commands/base_spec.rb
|
||||
index c1726845c..568dda778 100644
|
||||
--- a/spec/server/commands/base_spec.rb
|
||||
+++ b/spec/server/commands/base_spec.rb
|
||||
@@ -36,6 +36,13 @@ def run; cache 'foo' end
|
||||
@command.request.path_info = '/path/to/file.html'
|
||||
@command.run
|
||||
end
|
||||
+
|
||||
+ it "does not cache paths containing parent directory components" do
|
||||
+ expect(FileUtils).not_to receive(:mkdir_p)
|
||||
+ expect(File).not_to receive(:open)
|
||||
+ @command.request.path_info = '/../path/to/file.html'
|
||||
+ @command.run
|
||||
+ end
|
||||
end
|
||||
|
||||
describe "#redirect" do
|
||||
diff --git a/spec/server/static_caching_spec.rb b/spec/server/static_caching_spec.rb
|
||||
index 57960e6dd..724741e43 100644
|
||||
--- a/spec/server/static_caching_spec.rb
|
||||
+++ b/spec/server/static_caching_spec.rb
|
||||
@@ -37,6 +37,12 @@ def request; @request ||= MockRequest.new end
|
||||
expect(check_static_cache).to eq nil
|
||||
end
|
||||
|
||||
+ it "does not read paths containing parent directory components" do
|
||||
+ request.path_info = '/../secret'
|
||||
+ expect(File).not_to receive(:file?)
|
||||
+ expect(check_static_cache).to eq nil
|
||||
+ end
|
||||
+
|
||||
it "adds mount point to cache location" do
|
||||
request.path_info = '/hello/world.html'
|
||||
request.script_name = '/mount/point'
|
||||
157
yard-pr1650-avoid-global-namespace-collision.patch
Normal file
157
yard-pr1650-avoid-global-namespace-collision.patch
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
From f7a51bf19d44fb38ddc477deec6ba89f9555e193 Mon Sep 17 00:00:00 2001
|
||||
From: Bo Anderson <mail@boanderson.me>
|
||||
Date: Thu, 5 Mar 2026 05:27:49 +0000
|
||||
Subject: [PATCH] Avoid polluting global namespace in TypesExplainer tests
|
||||
|
||||
---
|
||||
spec/tags/types_explainer_spec.rb | 70 +++++++++++++++----------------
|
||||
1 file changed, 34 insertions(+), 36 deletions(-)
|
||||
|
||||
diff --git a/spec/tags/types_explainer_spec.rb b/spec/tags/types_explainer_spec.rb
|
||||
index de7454d3c..a29296b66 100644
|
||||
--- a/spec/tags/types_explainer_spec.rb
|
||||
+++ b/spec/tags/types_explainer_spec.rb
|
||||
@@ -1,22 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe YARD::Tags::TypesExplainer do
|
||||
- Type = YARD::Tags::TypesExplainer::Type
|
||||
- CollectionType = YARD::Tags::TypesExplainer::CollectionType
|
||||
- FixedCollectionType = YARD::Tags::TypesExplainer::FixedCollectionType
|
||||
- HashCollectionType = YARD::Tags::TypesExplainer::HashCollectionType
|
||||
- Parser = YARD::Tags::TypesExplainer::Parser
|
||||
-
|
||||
- def parse(types)
|
||||
- Parser.new(types).parse
|
||||
+ def type(name)
|
||||
+ YARD::Tags::TypesExplainer::Type.new(name)
|
||||
end
|
||||
|
||||
- def parse_fail(types)
|
||||
- expect { parse(types) }.to raise_error(SyntaxError)
|
||||
- end
|
||||
-
|
||||
- describe Type, '#to_s' do
|
||||
- before { @t = Type.new(nil) }
|
||||
+ describe YARD::Tags::TypesExplainer::Type, '#to_s' do
|
||||
+ before { @t = described_class.new(nil) }
|
||||
|
||||
it "works for a class/module reference" do
|
||||
@t.name = "ClassModuleName"
|
||||
@@ -59,78 +49,86 @@ def parse_fail(types)
|
||||
end
|
||||
end
|
||||
|
||||
- describe CollectionType, '#to_s' do
|
||||
- before { @t = CollectionType.new("Array", nil) }
|
||||
+ describe YARD::Tags::TypesExplainer::CollectionType, '#to_s' do
|
||||
+ before { @t = described_class.new("Array", nil) }
|
||||
|
||||
it "can contain one item" do
|
||||
- @t.types = [Type.new("Object")]
|
||||
+ @t.types = [type("Object")]
|
||||
expect(@t.to_s).to eq "an Array of (Objects)"
|
||||
end
|
||||
|
||||
it "can contain more than one item" do
|
||||
- @t.types = [Type.new("Object"), Type.new("String"), Type.new("Symbol")]
|
||||
+ @t.types = [type("Object"), type("String"), type("Symbol")]
|
||||
expect(@t.to_s).to eq "an Array of (Objects, Strings or Symbols)"
|
||||
end
|
||||
|
||||
it "can contain nested collections" do
|
||||
- @t.types = [CollectionType.new("List", [Type.new("Object")])]
|
||||
+ @t.types = [described_class.new("List", [type("Object")])]
|
||||
expect(@t.to_s).to eq "an Array of (a List of (Objects))"
|
||||
end
|
||||
end
|
||||
|
||||
- describe FixedCollectionType, '#to_s' do
|
||||
- before { @t = FixedCollectionType.new("Array", nil) }
|
||||
+ describe YARD::Tags::TypesExplainer::FixedCollectionType, '#to_s' do
|
||||
+ before { @t = described_class.new("Array", nil) }
|
||||
|
||||
it "can contain one item" do
|
||||
- @t.types = [Type.new("Object")]
|
||||
+ @t.types = [type("Object")]
|
||||
expect(@t.to_s).to eq "an Array containing (an Object)"
|
||||
end
|
||||
|
||||
it "can contain more than one item" do
|
||||
- @t.types = [Type.new("Object"), Type.new("String"), Type.new("Symbol")]
|
||||
+ @t.types = [type("Object"), type("String"), type("Symbol")]
|
||||
expect(@t.to_s).to eq "an Array containing (an Object followed by a String followed by a Symbol)"
|
||||
end
|
||||
|
||||
it "can contain nested collections" do
|
||||
- @t.types = [FixedCollectionType.new("List", [Type.new("Object")])]
|
||||
+ @t.types = [described_class.new("List", [type("Object")])]
|
||||
expect(@t.to_s).to eq "an Array containing (a List containing (an Object))"
|
||||
end
|
||||
end
|
||||
|
||||
- describe FixedCollectionType, '#to_s' do
|
||||
- before { @t = HashCollectionType.new("Hash", nil, nil) }
|
||||
+ describe YARD::Tags::TypesExplainer::HashCollectionType, '#to_s' do
|
||||
+ before { @t = described_class.new("Hash", nil, nil) }
|
||||
|
||||
it "can contain a single key type and value type" do
|
||||
- @t.key_types = [Type.new("Object")]
|
||||
- @t.value_types = [Type.new("Object")]
|
||||
+ @t.key_types = [type("Object")]
|
||||
+ @t.value_types = [type("Object")]
|
||||
expect(@t.to_s).to eq "a Hash with keys made of (Objects) and values of (Objects)"
|
||||
end
|
||||
|
||||
it "can contain multiple key types" do
|
||||
- @t.key_types = [Type.new("Key"), Type.new("String")]
|
||||
- @t.value_types = [Type.new("Object")]
|
||||
+ @t.key_types = [type("Key"), type("String")]
|
||||
+ @t.value_types = [type("Object")]
|
||||
expect(@t.to_s).to eq "a Hash with keys made of (Keys or Strings) and values of (Objects)"
|
||||
end
|
||||
|
||||
it "can contain multiple value types" do
|
||||
- @t.key_types = [Type.new("String")]
|
||||
- @t.value_types = [Type.new("true"), Type.new("false")]
|
||||
+ @t.key_types = [type("String")]
|
||||
+ @t.value_types = [type("true"), type("false")]
|
||||
expect(@t.to_s).to eq "a Hash with keys made of (Strings) and values of (true or false)"
|
||||
end
|
||||
end
|
||||
|
||||
- describe Parser, '#parse' do
|
||||
+ describe YARD::Tags::TypesExplainer::Parser, '#parse' do
|
||||
+ def parse(types)
|
||||
+ described_class.new(types).parse
|
||||
+ end
|
||||
+
|
||||
+ def parse_fail(types)
|
||||
+ expect { parse(types) }.to raise_error(SyntaxError)
|
||||
+ end
|
||||
+
|
||||
it "parses a regular class name" do
|
||||
type = parse("MyClass")
|
||||
expect(type.size).to eq 1
|
||||
- expect(type.first).to be_a(Type)
|
||||
+ expect(type.first).to be_a(YARD::Tags::TypesExplainer::Type)
|
||||
expect(type.first.name).to eq "MyClass"
|
||||
end
|
||||
|
||||
it "parses a path reference name" do
|
||||
type = parse("A::B")
|
||||
expect(type.size).to eq 1
|
||||
- expect(type.first).to be_a(Type)
|
||||
+ expect(type.first).to be_a(YARD::Tags::TypesExplainer::Type)
|
||||
expect(type.first.name).to eq "A::B"
|
||||
end
|
||||
|
||||
@@ -145,7 +143,7 @@ def parse_fail(types)
|
||||
|
||||
it "parses a collection type" do
|
||||
type = parse("MyList<String>")
|
||||
- expect(type.first).to be_a(CollectionType)
|
||||
+ expect(type.first).to be_a(YARD::Tags::TypesExplainer::CollectionType)
|
||||
expect(type.first.types.size).to eq 1
|
||||
expect(type.first.name).to eq "MyList"
|
||||
expect(type.first.types.first.name).to eq "String"
|
||||
Loading…
Add table
Add a link
Reference in a new issue