gitlab-exporter 16.10.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a3b7fa869eab272c352626dfd0f8acc2cbcc3fb2df46da92ea0387471282d2b
4
- data.tar.gz: fc2f8a0a213b2a97a92ed5f417a5856f181c9f422cbbc4640c9cce45d86e4fcf
3
+ metadata.gz: 3cc0cadf1891c19066f388f384d51f47b9c489e8e0ee7c162565228fb661bada
4
+ data.tar.gz: 7ae7c0ac398b1832c58c2348297139fd88e3ff02e447669043990312160ed0a8
5
5
  SHA512:
6
- metadata.gz: 2f6603c161ae6c9346d5b3ecd919f4df54bceb8232e588db181d1142eedc2e90231039b15a357ceed1ae62ae735b7540567e2717bbacadcea5e83ddd5b366fb2
7
- data.tar.gz: 760580b43d2290a933842a4ffbd4182fe60660b401d03e9df6a7c8d85f6c3363054124b9aba2fd5a4983fb1989a11f2df28cf2f3cbdf7ca67e653bde4be95bf7
6
+ metadata.gz: 7a358dbc90fe7bed11fc33a179fe2eb726e0d218925a56e7f076cc4c51d14272de4adca57cd5ae084e0a57d51feed0cf331b2dbf3ae67a5c0a2a972c4dba1863
7
+ data.tar.gz: c937591c8d81ad5296955bd5a55f92bcdd6f2ecb0f1fe7cdd36e9f429e88f5bbf2da8bce8375e8897fec29eda18ea0dd60fcd1bbfc5070c49e9b886a0faf3037
data/Gemfile.lock CHANGED
@@ -1,17 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab-exporter (16.10.0)
4
+ gitlab-exporter (17.0.0)
5
+ base64 (= 0.2.0)
5
6
  connection_pool (= 2.5.5)
6
7
  deep_merge (~> 1.2.2)
7
8
  faraday (= 2.14.3)
8
9
  pg (= 1.6.3)
9
- puma (= 8.0.2)
10
10
  quantile (= 0.2.1)
11
11
  redis (= 4.8.1)
12
12
  redis-namespace (= 1.11.0)
13
13
  sidekiq (= 6.5.12)
14
- sinatra (~> 3.2.0)
15
14
  webrick (~> 1.9)
16
15
 
17
16
  GEM
@@ -31,23 +30,15 @@ GEM
31
30
  language_server-protocol (3.17.0.5)
32
31
  lint_roller (1.1.0)
33
32
  logger (1.7.0)
34
- mustermann (3.0.3)
35
- ruby2_keywords (~> 0.0.1)
36
- nio4r (2.7.4)
37
33
  parallel (1.27.0)
38
34
  parser (3.3.9.0)
39
35
  ast (~> 2.4.1)
40
36
  racc
41
37
  pg (1.6.3)
42
38
  prism (1.9.0)
43
- puma (8.0.2)
44
- nio4r (~> 2.0)
45
39
  quantile (0.2.1)
46
40
  racc (1.8.1)
47
41
  rack (2.2.15)
48
- rack-protection (3.2.0)
49
- base64 (>= 0.1.0)
50
- rack (~> 2.2, >= 2.2.4)
51
42
  rainbow (3.1.1)
52
43
  redis (4.8.1)
53
44
  redis-namespace (1.11.0)
@@ -81,17 +72,10 @@ GEM
81
72
  parser (>= 3.3.7.2)
82
73
  prism (~> 1.7)
83
74
  ruby-progressbar (1.13.0)
84
- ruby2_keywords (0.0.5)
85
75
  sidekiq (6.5.12)
86
76
  connection_pool (>= 2.2.5, < 3)
87
77
  rack (~> 2.0)
88
78
  redis (>= 4.5.0, < 5)
89
- sinatra (3.2.0)
90
- mustermann (~> 3.0)
91
- rack (~> 2.2, >= 2.2.4)
92
- rack-protection (= 3.2.0)
93
- tilt (~> 2.0)
94
- tilt (2.6.0)
95
79
  unicode-display_width (3.1.4)
96
80
  unicode-emoji (~> 4.0, >= 4.0.4)
97
81
  unicode-emoji (4.0.4)
@@ -6,7 +6,6 @@ db_common: &db_common
6
6
 
7
7
  # Web server config
8
8
  server:
9
- name: webrick # cf. https://github.com/sinatra/sinatra#available-settings
10
9
  listen_address: 0.0.0.0
11
10
  listen_port: 9168
12
11
  # Maximum amount of memory to use in megabytes, after which the process is killed
@@ -22,16 +22,18 @@ Gem::Specification.new do |s|
22
22
 
23
23
  s.required_ruby_version = Gem::Requirement.new(">= 3.0")
24
24
 
25
+ # base64 is required by sidekiq but not declared by it, and is no longer a
26
+ # default gem as of Ruby 3.4. It used to be pulled in transitively via
27
+ # sinatra -> rack-protection; declare it directly now that those are gone.
28
+ s.add_runtime_dependency "base64", "0.2.0"
25
29
  s.add_runtime_dependency "connection_pool", "2.5.5"
26
30
  s.add_runtime_dependency "deep_merge", "~> 1.2.2"
27
31
  s.add_runtime_dependency "faraday", "2.14.3"
28
32
  s.add_runtime_dependency "pg", "1.6.3"
29
- s.add_runtime_dependency "puma", "8.0.2"
30
33
  s.add_runtime_dependency "quantile", "0.2.1"
31
34
  s.add_runtime_dependency "redis", "4.8.1"
32
35
  s.add_runtime_dependency "redis-namespace", "1.11.0"
33
36
  s.add_runtime_dependency "sidekiq", "6.5.12"
34
- s.add_runtime_dependency "sinatra", "~> 3.2.0"
35
37
  s.add_runtime_dependency "webrick", "~> 1.9"
36
38
 
37
39
  s.add_development_dependency "rspec", "~> 3.12.0"
@@ -1,5 +1,5 @@
1
1
  module GitLab
2
2
  module Exporter
3
- VERSION = "16.10.0".freeze
3
+ VERSION = "17.0.0".freeze
4
4
  end
5
5
  end
@@ -1,146 +1,138 @@
1
- require "sinatra/base"
1
+ require "webrick"
2
2
  require "English"
3
3
  require "cgi"
4
+ require "stringio"
4
5
 
5
- require_relative "rack_vulndb_255039_patch"
6
6
  require_relative "tls_helper"
7
7
 
8
8
  module GitLab
9
9
  module Exporter
10
- # Metrics web exporter
11
- class WebExporter < Sinatra::Base
12
- # A middleware to kill the process if we exceeded a certain threshold
13
- class MemoryKillerMiddleware
14
- def initialize(app, memory_threshold)
15
- @app = app
16
- @memory_threshold = memory_threshold.to_i * 1024
17
- end
18
-
19
- def call(env)
20
- if memory_usage > @memory_threshold
21
- puts "Memory usage of #{memory_usage} exceeded threshold of #{@memory_threshold}, signalling KILL"
22
- Process.kill("KILL", $PID)
23
- end
24
-
25
- @app.call(env)
26
- end
27
-
28
- private
29
-
30
- def memory_usage
31
- io = IO.popen(%W[ps -o rss= -p #{$PID}])
32
-
33
- mem = io.read
34
- io.close
35
-
36
- return 0 unless $CHILD_STATUS.to_i.zero?
37
-
38
- mem.to_i
39
- end
40
- end
41
-
42
- # Performs a major GC after each request. We found that this helps to free up
43
- # several MB of memory in conjunction with sricter malloc config.
44
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/297241
45
- class RunGC
46
- def initialize(app)
47
- @app = app
48
- end
49
-
50
- def call(env)
51
- @app.call(env).tap do
52
- GC.start
53
- end
54
- end
55
- end
10
+ # Metrics web exporter backed directly by WEBrick (no Sinatra/Rack).
11
+ class WebExporter
12
+ DEFAULT_WEB_SERVER = "webrick".freeze
13
+ DEFAULT_LISTEN_ADDRESS = "0.0.0.0".freeze
14
+ DEFAULT_LISTEN_PORT = 9168
15
+ DEFAULT_MEMORY_THRESHOLD = 1024
16
+
17
+ # Security response headers previously added by Sinatra's Rack::Protection
18
+ # middleware, which is gone now that we serve directly via WEBrick. Kept for
19
+ # parity so scanners and consumers see the same headers as before.
20
+ SECURITY_HEADERS = {
21
+ "X-Content-Type-Options" => "nosniff",
22
+ "X-Frame-Options" => "SAMEORIGIN",
23
+ "X-XSS-Protection" => "1; mode=block"
24
+ }.freeze
56
25
 
57
26
  class << self
58
27
  include TLSHelper
59
28
 
60
- DEFAULT_WEB_SERVER = "webrick".freeze
61
-
62
29
  def setup(config)
63
- setup_server(config[:server])
64
- setup_probes(config[:probes])
30
+ server_config = config[:server] || {}
65
31
 
66
- memory_threshold = (config[:server] && config[:server][:memory_threshold]) || 1024
67
- use MemoryKillerMiddleware, memory_threshold
68
- use Rack::Logger
69
- use RunGC
32
+ memory_threshold = server_config.fetch(:memory_threshold, DEFAULT_MEMORY_THRESHOLD)
33
+ @memory_threshold = memory_threshold.to_i * 1024
34
+
35
+ @server = build_server(server_config)
36
+ mount_probes(config[:probes])
70
37
 
71
38
  # Defrag heap after everything is loaded into memory.
72
39
  GC.compact
73
40
  end
74
41
 
75
- def logger
76
- request.logger
42
+ def run!
43
+ trap_signals
44
+ @server.start
77
45
  end
78
46
 
79
- def setup_server(config)
80
- config ||= {}
47
+ private
81
48
 
82
- set(:server, config.fetch(:name, DEFAULT_WEB_SERVER))
83
- set(:port, config.fetch(:listen_port, 9168))
49
+ def build_server(config)
50
+ warn_deprecated_server(config[:name])
51
+
52
+ options = {
53
+ Port: config.fetch(:listen_port, DEFAULT_LISTEN_PORT),
54
+ BindAddress: config.fetch(:listen_address, DEFAULT_LISTEN_ADDRESS)
55
+ }
84
56
 
85
- # Depending on whether TLS is enabled or not, bind string
86
- # will be different.
87
57
  if config.fetch(:tls_enabled, "false").to_s == "true"
88
- set_tls_config(config)
89
- else
90
- set(:bind, config.fetch(:listen_address, "0.0.0.0"))
58
+ validate_tls_config(config)
59
+ options.merge!(webrick_tls_config(config))
91
60
  end
61
+
62
+ WEBrick::HTTPServer.new(**options)
92
63
  end
93
64
 
94
- def set_tls_config(config) # rubocop:disable Naming/AccessorMethodName
95
- validate_tls_config(config)
65
+ # The web server used to be configurable (e.g. Puma) via the `server.name`
66
+ # setting when the exporter ran on top of Sinatra. It now always uses WEBrick,
67
+ # so the setting is ignored. Warn operators whose config still sets it.
68
+ #
69
+ # TODO: This is step one of a two-step deprecation. In a future major release,
70
+ # raise instead of warning so the ignored `server.name` setting fails loudly.
71
+ def warn_deprecated_server(name)
72
+ return if name.nil? || name.to_s == DEFAULT_WEB_SERVER
73
+
74
+ warn "DEPRECATION: `server.name` (#{name.inspect}) is no longer supported and is ignored; " \
75
+ "the exporter now always uses WEBrick. Please remove `server.name` from your config."
76
+ end
96
77
 
97
- web_server = config.fetch(:name, DEFAULT_WEB_SERVER)
98
- if web_server == "webrick"
99
- set_webrick_tls(config)
100
- elsif web_server == "puma"
101
- set_puma_tls(config)
102
- else
103
- fail "TLS not supported for web server `#{web_server}`."
78
+ def mount_probes(probes)
79
+ (probes || {}).each do |probe_name, params|
80
+ opts =
81
+ if params.delete(:multiple)
82
+ params
83
+ else
84
+ { probe_name => params }
85
+ end
86
+
87
+ @server.mount_proc("/#{probe_name}") do |_request, response|
88
+ handle_probe(response, opts)
89
+ end
104
90
  end
105
91
  end
106
92
 
107
- def set_webrick_tls(config) # rubocop:disable Naming/AccessorMethodName
108
- server_settings = {}
109
- server_settings.merge!(webrick_tls_config(config))
93
+ def handle_probe(response, opts)
94
+ # A memory killer that kills the process if we exceeded a certain threshold.
95
+ check_memory_threshold!
110
96
 
111
- set(:bind, config.fetch(:listen_address, "0.0.0.0"))
112
- set(:server_settings, server_settings)
113
- end
97
+ prober = Prober.new(metrics: PrometheusMetrics.new(include_timestamp: false), logger: @server.logger, **opts)
98
+ prober.probe_all
99
+
100
+ buffer = StringIO.new
101
+ prober.write_to(buffer)
114
102
 
115
- def set_puma_tls(config) # rubocop:disable Naming/AccessorMethodName
116
- listen_address = config.fetch(:listen_address, "0.0.0.0")
117
- listen_port = config.fetch(:listen_port, 8443)
118
- tls_cert_path = CGI.escape(config.fetch(:tls_cert_path))
119
- tls_key_path = CGI.escape(config.fetch(:tls_key_path))
103
+ SECURITY_HEADERS.each do |header, value|
104
+ response[header] = value
105
+ end
106
+ response.content_type = "text/plain; version=0.0.4; charset=utf-8"
107
+ response.body = buffer.string
108
+ ensure
109
+ # Performs a major GC after each request. We found that this helps to free up
110
+ # several MB of memory in conjunction with stricter malloc config.
111
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/297241
112
+ GC.start
113
+ end
120
114
 
121
- bind_string = "ssl://#{listen_address}:#{listen_port}?cert=#{tls_cert_path}&key=#{tls_key_path}"
115
+ def check_memory_threshold!
116
+ usage = memory_usage
117
+ return if usage <= @memory_threshold
122
118
 
123
- set(:bind, bind_string)
119
+ puts "Memory usage of #{usage} exceeded threshold of #{@memory_threshold}, signalling KILL"
120
+ Process.kill("KILL", $PID)
124
121
  end
125
122
 
126
- def setup_probes(config)
127
- (config || {}).each do |probe_name, params|
128
- opts =
129
- if params.delete(:multiple)
130
- params
131
- else
132
- { probe_name => params }
133
- end
123
+ def memory_usage
124
+ # Use the block form so the pipe is closed automatically even if the
125
+ # read raises, avoiding a file-descriptor leak.
126
+ mem = IO.popen(%W[ps -o rss= -p #{$PID}], &:read)
134
127
 
135
- get "/#{probe_name}" do
136
- content_type "text/plain; version=0.0.4; charset=utf-8"
137
- prober = Prober.new(metrics: PrometheusMetrics.new(include_timestamp: false), logger: logger, **opts)
128
+ return 0 unless $CHILD_STATUS.to_i.zero?
138
129
 
139
- prober.probe_all
140
- prober.write_to(response)
130
+ mem.to_i
131
+ end
141
132
 
142
- response
143
- end
133
+ def trap_signals
134
+ %w[INT TERM].each do |signal|
135
+ trap(signal) { @server.shutdown }
144
136
  end
145
137
  end
146
138
  end
@@ -0,0 +1,127 @@
1
+ require "spec_helper"
2
+ require "gitlab_exporter/web_exporter"
3
+ require "net/http"
4
+ require "timeout"
5
+
6
+ describe GitLab::Exporter::WebExporter do
7
+ # Each test builds a fresh server via `.setup`; drop the reference afterwards
8
+ # so class-level state does not leak between examples.
9
+ after do
10
+ described_class.instance_variable_set(:@server, nil)
11
+ end
12
+
13
+ describe ".setup" do
14
+ let(:probes) { {} }
15
+
16
+ it "builds a WEBrick server with the configured port and bind address" do
17
+ options = capture_server_options(server: { listen_address: "127.0.0.1", listen_port: 12_345 })
18
+
19
+ expect(options).to include(Port: 12_345, BindAddress: "127.0.0.1")
20
+ end
21
+
22
+ it "falls back to the default port and bind address" do
23
+ options = capture_server_options({})
24
+
25
+ expect(options).to include(Port: 9168, BindAddress: "0.0.0.0")
26
+ end
27
+
28
+ it "mounts a handler for each configured probe" do
29
+ server = instance_double(WEBrick::HTTPServer)
30
+ allow(WEBrick::HTTPServer).to receive(:new).and_return(server)
31
+
32
+ expect(server).to receive(:mount_proc).with("/ruby")
33
+
34
+ described_class.setup(server: {}, probes: { ruby: { methods: %w[probe_gc], opts: {} } })
35
+ end
36
+
37
+ it "raises when TLS is enabled but no certificate is configured" do
38
+ expect {
39
+ described_class.setup(server: { tls_enabled: true }, probes: probes)
40
+ }.to raise_error(/tls_cert_path not specified/)
41
+ end
42
+
43
+ # Capture the keyword options passed to WEBrick without binding a real socket.
44
+ def capture_server_options(config)
45
+ captured = nil
46
+ allow(WEBrick::HTTPServer).to receive(:new) do |**options|
47
+ captured = options
48
+ instance_double(WEBrick::HTTPServer)
49
+ end
50
+
51
+ described_class.setup(config.merge(probes: {}))
52
+ captured
53
+ end
54
+ end
55
+
56
+ describe "deprecating the server.name setting" do
57
+ it "warns when a non-WEBrick server is requested" do
58
+ expect {
59
+ described_class.send(:warn_deprecated_server, "puma")
60
+ }.to output(/DEPRECATION.*no longer supported/).to_stderr
61
+ end
62
+
63
+ it "does not warn when the server is webrick" do
64
+ expect { described_class.send(:warn_deprecated_server, "webrick") }.not_to output.to_stderr
65
+ end
66
+
67
+ it "does not warn when no server name is set" do
68
+ expect { described_class.send(:warn_deprecated_server, nil) }.not_to output.to_stderr
69
+ end
70
+ end
71
+
72
+ describe "serving probe requests" do
73
+ let(:config) do
74
+ {
75
+ server: { listen_address: "127.0.0.1", listen_port: 0 },
76
+ probes: { ruby: { methods: %w[probe_gc], opts: {} } }
77
+ }
78
+ end
79
+
80
+ let(:server) { described_class.instance_variable_get(:@server) }
81
+ let(:base_uri) { "http://127.0.0.1:#{server.listeners.first.addr[1]}" }
82
+
83
+ before do
84
+ # WEBrick binds its logger and access log to $stderr at construction time
85
+ # and emits a startup banner; silence $stderr around setup so the log
86
+ # device is a throwaway buffer and the test output stays clean.
87
+ original_stderr = $stderr
88
+ $stderr = StringIO.new
89
+ begin
90
+ described_class.setup(config)
91
+ ensure
92
+ $stderr = original_stderr
93
+ end
94
+
95
+ @server_thread = Thread.new { server.start }
96
+ Timeout.timeout(5) { sleep 0.01 until server.status == :Running }
97
+ end
98
+
99
+ after do
100
+ server.shutdown
101
+ @server_thread&.join(5)
102
+ end
103
+
104
+ it "returns Prometheus metrics for a known probe" do
105
+ response = Net::HTTP.get_response(URI("#{base_uri}/ruby"))
106
+
107
+ expect(response.code).to eq("200")
108
+ expect(response.content_type).to eq("text/plain")
109
+ expect(response["Content-Type"]).to include("version=0.0.4")
110
+ expect(response.body).to match(/ruby_gc_stat_count \d+/)
111
+ end
112
+
113
+ it "sets the security headers previously provided by Rack::Protection" do
114
+ response = Net::HTTP.get_response(URI("#{base_uri}/ruby"))
115
+
116
+ expect(response["X-Content-Type-Options"]).to eq("nosniff")
117
+ expect(response["X-Frame-Options"]).to eq("SAMEORIGIN")
118
+ expect(response["X-XSS-Protection"]).to eq("1; mode=block")
119
+ end
120
+
121
+ it "returns 404 for unknown paths" do
122
+ response = Net::HTTP.get_response(URI("#{base_uri}/does-not-exist"))
123
+
124
+ expect(response.code).to eq("404")
125
+ end
126
+ end
127
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-exporter
3
3
  version: !ruby/object:Gem::Version
4
- version: 16.10.0
4
+ version: 17.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Carranza
@@ -10,6 +10,20 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2016-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.0
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: connection_pool
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -66,20 +80,6 @@ dependencies:
66
80
  - - '='
67
81
  - !ruby/object:Gem::Version
68
82
  version: 1.6.3
69
- - !ruby/object:Gem::Dependency
70
- name: puma
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - '='
74
- - !ruby/object:Gem::Version
75
- version: 8.0.2
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - '='
81
- - !ruby/object:Gem::Version
82
- version: 8.0.2
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: quantile
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -136,20 +136,6 @@ dependencies:
136
136
  - - '='
137
137
  - !ruby/object:Gem::Version
138
138
  version: 6.5.12
139
- - !ruby/object:Gem::Dependency
140
- name: sinatra
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: 3.2.0
146
- type: :runtime
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: 3.2.0
153
139
  - !ruby/object:Gem::Dependency
154
140
  name: webrick
155
141
  requirement: !ruby/object:Gem::Requirement
@@ -233,7 +219,6 @@ files:
233
219
  - lib/gitlab_exporter/prober.rb
234
220
  - lib/gitlab_exporter/process.rb
235
221
  - lib/gitlab_exporter/prometheus.rb
236
- - lib/gitlab_exporter/rack_vulndb_255039_patch.rb
237
222
  - lib/gitlab_exporter/ruby.rb
238
223
  - lib/gitlab_exporter/sidekiq.rb
239
224
  - lib/gitlab_exporter/tls_helper.rb
@@ -258,6 +243,7 @@ files:
258
243
  - spec/sidekiq_spec.rb
259
244
  - spec/spec_helper.rb
260
245
  - spec/util_spec.rb
246
+ - spec/web_exporter_spec.rb
261
247
  homepage: https://gitlab.com/gitlab-org/ruby/gems/gitlab-exporter
262
248
  licenses:
263
249
  - MIT
@@ -300,3 +286,4 @@ test_files:
300
286
  - spec/sidekiq_spec.rb
301
287
  - spec/spec_helper.rb
302
288
  - spec/util_spec.rb
289
+ - spec/web_exporter_spec.rb
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- if Gem.loaded_specs["rack"].version >= Gem::Version.new("3.0.0")
4
- fail <<~ERR
5
- This patch is unnecessary in Rack versions 3.0.0 or newer.
6
- Please remove this file and the associated spec.
7
-
8
- See https://github.com/rack/rack/blob/main/CHANGELOG.md#security (issue #1733)
9
- ERR
10
- end
11
-
12
- # Patches a cache poisoning attack vector in Rack by not allowing semicolons
13
- # to delimit query parameters.
14
- # See https://github.com/rack/rack/issues/1732.
15
- #
16
- # Solution is taken from the same issue.
17
- #
18
- # The actual patch is due for release in Rack 3.0.0.
19
- module Rack
20
- class Request # rubocop:disable Style/Documentation
21
- Helpers.module_eval do
22
- # rubocop: disable Naming/MethodName
23
- def GET
24
- if get_header(RACK_REQUEST_QUERY_STRING) == query_string
25
- get_header(RACK_REQUEST_QUERY_HASH)
26
- else
27
- query_hash = parse_query(query_string, "&") # only allow ampersand here
28
- set_header(RACK_REQUEST_QUERY_STRING, query_string)
29
- set_header(RACK_REQUEST_QUERY_HASH, query_hash)
30
- end
31
- end
32
- # rubocop: enable Naming/MethodName
33
- end
34
- end
35
- end