Skip to content

Commit a6d3a3a

Browse files
iMacTiaclaude
andauthored
Merge commit from fork
Protocol-relative URLs (e.g. `//evil.com/path`) bypassed the existing relative-URL guard in `build_exclusive_url`, allowing an attacker-controlled URL to override the connection's base host. The `//` prefix matched the `/` check in `start_with?`, so these URLs were passed through to `URI#+` which treated them as authority references, replacing the host. Extend the guard condition so that URLs starting with `//` are also prefixed with `./`, neutralising the authority component and keeping requests scoped to the configured base host. Security: GHSA-33mh-2634-fwr2 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b23f710 commit a6d3a3a

File tree

3 files changed

+37
-3
lines changed

3 files changed

+37
-3
lines changed

.rubocop_todo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Metrics/AbcSize:
3131
# Offense count: 3
3232
# Configuration parameters: CountComments, CountAsOne.
3333
Metrics/ClassLength:
34-
Max: 230
34+
Max: 235
3535

3636
# Offense count: 9
3737
# Configuration parameters: AllowedMethods, AllowedPatterns.

lib/faraday/connection.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,8 +481,9 @@ def build_exclusive_url(url = nil, params = nil, params_encoder = nil)
481481
if url && !base.path.end_with?('/')
482482
base.path = "#{base.path}/" # ensure trailing slash
483483
end
484-
# Ensure relative url will be parsed correctly (such as `service:search` )
485-
url = "./#{url}" if url.respond_to?(:start_with?) && !url.start_with?('http://', 'https://', '/', './', '../')
484+
# Ensure relative url will be parsed correctly (such as `service:search` or `//evil.com`)
485+
url = "./#{url}" if url.respond_to?(:start_with?) &&
486+
(!url.start_with?('http://', 'https://', '/', './', '../') || url.start_with?('//'))
486487
uri = url ? base + url : base
487488
if params
488489
uri.query = params.to_query(params_encoder || options.params_encoder)

spec/faraday/connection_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,39 @@ def decode(params)
311311
end
312312
end
313313

314+
context 'with protocol-relative URL (GHSA-33mh-2634-fwr2)' do
315+
it 'does not allow host override with //evil.com/path' do
316+
conn.url_prefix = 'http://httpbingo.org/api'
317+
uri = conn.build_exclusive_url('//evil.com/path')
318+
expect(uri.host).to eq('httpbingo.org')
319+
end
320+
321+
it 'does not allow host override with //evil.com:8080/path' do
322+
conn.url_prefix = 'http://httpbingo.org/api'
323+
uri = conn.build_exclusive_url('//evil.com:8080/path')
324+
expect(uri.host).to eq('httpbingo.org')
325+
end
326+
327+
it 'does not allow host override with //user:pass@evil.com/path' do
328+
conn.url_prefix = 'http://httpbingo.org/api'
329+
uri = conn.build_exclusive_url('//user:pass@evil.com/path')
330+
expect(uri.host).to eq('httpbingo.org')
331+
end
332+
333+
it 'does not allow host override with ///evil.com' do
334+
conn.url_prefix = 'http://httpbingo.org/api'
335+
uri = conn.build_exclusive_url('///evil.com')
336+
expect(uri.host).to eq('httpbingo.org')
337+
end
338+
339+
it 'still allows single-slash absolute paths' do
340+
conn.url_prefix = 'http://httpbingo.org/api'
341+
uri = conn.build_exclusive_url('/safe/path')
342+
expect(uri.host).to eq('httpbingo.org')
343+
expect(uri.path).to eq('/safe/path')
344+
end
345+
end
346+
314347
context 'with a custom `default_uri_parser`' do
315348
let(:url) { 'http://httpbingo.org' }
316349
let(:parser) { Addressable::URI }

0 commit comments

Comments
 (0)