ssrf_filter 1.4.0 → 1.5.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: 8044ad57435e5aa1af62c8defb3087ee7dce64fdfb9530b8d5d73a0793607f6e
4
- data.tar.gz: 914546724fc94a99915d4c710c21e6ec89f24f5aa3b32cca8a2ec4613719a616
3
+ metadata.gz: d68f597eb7885a7c9941fc8f134f7a6b87eec03fc0c92b6b6684a00d1be00069
4
+ data.tar.gz: 2c934e958e0542c2ae1606b0657cb2096d54647969f291aa41579fd3f0363c67
5
5
  SHA512:
6
- metadata.gz: 9b5842cc8b3a2b4bef7f82cd3956def19d1d96e7cb85c6524a36bffe4f04cfaf58a01630203d0ab824063507802c4a681503199accc3154e684b0f2bbd594b47
7
- data.tar.gz: 494fff3091b5deb2aa76507a482114a55375322395f9e9fb4cf4139b1fabc5415d8ffa5a31497eb05217b70d8793960c4a808967536c64605f9787b776c07d92
6
+ metadata.gz: 231e5f001e5540067710bc8894364592707fde79adafcbed2d4ea019646eb4b71fe60fae76caa562e8161090cffcb49390521045f57d153d8569eeeeaef85e79
7
+ data.tar.gz: 6854c8b312de12da5ea27bf842533eb48719ed98668d83838b4e07c1408ccacb302cfa7830b23c810e27d8f46e0f95cfcabd9919c29c86b351b8f285dac25934
@@ -91,6 +91,8 @@ class SsrfFilter
91
91
 
92
92
  DEFAULT_ALLOW_UNFOLLOWED_REDIRECTS = false
93
93
  DEFAULT_MAX_REDIRECTS = 10
94
+ DEFAULT_SENSITIVE_HEADERS = %w[authorization cookie].freeze
95
+ DEFAULT_ON_CROSS_ORIGIN_REDIRECT = :strip
94
96
 
95
97
  VERB_MAP = {
96
98
  get: ::Net::HTTP::Get,
@@ -119,14 +121,19 @@ class SsrfFilter
119
121
  class CRLFInjection < Error
120
122
  end
121
123
 
124
+ class CredentialLeakage < Error
125
+ end
126
+
122
127
  VERB_MAP.each_key do |method|
123
128
  define_singleton_method(method) do |url, options = {}, &block|
129
+ url = url.to_s
124
130
  original_url = url
131
+ original_uri = URI(url)
125
132
  scheme_whitelist = options.fetch(:scheme_whitelist, DEFAULT_SCHEME_WHITELIST)
126
133
  resolver = options.fetch(:resolver, DEFAULT_RESOLVER)
127
134
  allow_unfollowed_redirects = options.fetch(:allow_unfollowed_redirects, DEFAULT_ALLOW_UNFOLLOWED_REDIRECTS)
128
135
  max_redirects = options.fetch(:max_redirects, DEFAULT_MAX_REDIRECTS)
129
- url = url.to_s
136
+ sensitive_headers = options.fetch(:sensitive_headers, DEFAULT_SENSITIVE_HEADERS)
130
137
 
131
138
  response = nil
132
139
  (max_redirects + 1).times do
@@ -143,7 +150,14 @@ class SsrfFilter
143
150
  public_addresses = ip_addresses.reject(&method(:unsafe_ip_address?))
144
151
  raise PrivateIPAddress, "Hostname '#{hostname}' has no public ip addresses" if public_addresses.empty?
145
152
 
146
- response, url = fetch_once(uri, public_addresses.sample.to_s, method, options, &block)
153
+ headers_to_strip = if !sensitive_headers.empty? && different_origin?(original_uri, uri)
154
+ sensitive_headers
155
+ else
156
+ []
157
+ end
158
+
159
+ response, url = fetch_once(uri, public_addresses.sample.to_s, method,
160
+ options.merge(headers_to_strip: headers_to_strip), &block)
147
161
  return response if url.nil?
148
162
  end
149
163
 
@@ -178,6 +192,10 @@ class SsrfFilter
178
192
  range.first != range.last
179
193
  end
180
194
 
195
+ private_class_method def self.different_origin?(uri1, uri2)
196
+ uri1.scheme != uri2.scheme || uri1.hostname != uri2.hostname || uri1.port != uri2.port
197
+ end
198
+
181
199
  private_class_method def self.normalized_hostname(uri)
182
200
  # Attach port for non-default as per RFC2616
183
201
  if (uri.port == 80 && uri.scheme == 'http') ||
@@ -205,6 +223,20 @@ class SsrfFilter
205
223
  request.body = options[:body] if options[:body]
206
224
 
207
225
  options[:request_proc].call(request) if options[:request_proc].respond_to?(:call)
226
+
227
+ headers_to_strip = Array(options[:headers_to_strip])
228
+ unless headers_to_strip.empty?
229
+ if options[:on_cross_origin_redirect] == :raise
230
+ leaking = headers_to_strip.select { |h| request[h] }
231
+ unless leaking.empty?
232
+ raise CredentialLeakage,
233
+ "Cross-origin redirect would leak sensitive headers: #{leaking.join(', ')}"
234
+ end
235
+ else
236
+ headers_to_strip.each { |h| request.delete(h) }
237
+ end
238
+ end
239
+
208
240
  validate_request(request)
209
241
 
210
242
  http_options = (options[:http_options] || {}).merge(
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class SsrfFilter
4
- VERSION = '1.4.0'
4
+ VERSION = '1.5.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ssrf_filter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arkadiy Tetelman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-31 00:00:00.000000000 Z
11
+ date: 2026-04-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64