Skip to content

Commit d0fc049

Browse files
iMacTiaclaude
andauthored
Backport security fix for CVE-2026-25765 to 1.x branch (#1665)
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 prefixed with `./`, neutralizing the authority component and keeping requests scoped to the configured base host. This backport maintains backward compatibility with the 1.x branch's colon-escaping behavior for opaque URIs like `service:search`. Security: CVE-2026-25765, GHSA-33mh-2634-fwr2 Co-authored-by: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
1 parent 41c990e commit d0fc049

File tree

2 files changed

+35
-0
lines changed

2 files changed

+35
-0
lines changed

lib/faraday/connection.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,8 @@ def build_exclusive_url(url = nil, params = nil, params_encoder = nil)
545545
if url && base.path && base.path !~ %r{/$}
546546
base.path = "#{base.path}/" # ensure trailing slash
547547
end
548+
# Ensure protocol-relative URLs are handled correctly (CVE-2026-25765)
549+
url = "./#{url}" if url.respond_to?(:start_with?) && url.start_with?('//')
548550
url = url && URI.parse(url.to_s).opaque ? url.to_s.gsub(':', '%3A') : url
549551
uri = url ? base + url : base
550552
if params

spec/faraday/connection_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,39 @@
307307
expect(uri.to_s).to eq('http://service.com/api/service%3Asearch?limit=400')
308308
end
309309
end
310+
311+
context 'with protocol-relative URL (CVE-2026-25765)' do
312+
it 'does not allow host override with //evil.com/path' do
313+
conn.url_prefix = 'http://httpbingo.org/api'
314+
uri = conn.build_exclusive_url('//evil.com/path')
315+
expect(uri.host).to eq('httpbingo.org')
316+
end
317+
318+
it 'does not allow host override with //evil.com:8080/path' do
319+
conn.url_prefix = 'http://httpbingo.org/api'
320+
uri = conn.build_exclusive_url('//evil.com:8080/path')
321+
expect(uri.host).to eq('httpbingo.org')
322+
end
323+
324+
it 'does not allow host override with //user:pass@evil.com/path' do
325+
conn.url_prefix = 'http://httpbingo.org/api'
326+
uri = conn.build_exclusive_url('//user:pass@evil.com/path')
327+
expect(uri.host).to eq('httpbingo.org')
328+
end
329+
330+
it 'does not allow host override with ///evil.com' do
331+
conn.url_prefix = 'http://httpbingo.org/api'
332+
uri = conn.build_exclusive_url('///evil.com')
333+
expect(uri.host).to eq('httpbingo.org')
334+
end
335+
336+
it 'still allows single-slash absolute paths' do
337+
conn.url_prefix = 'http://httpbingo.org/api'
338+
uri = conn.build_exclusive_url('/safe/path')
339+
expect(uri.host).to eq('httpbingo.org')
340+
expect(uri.path).to eq('/safe/path')
341+
end
342+
end
310343
end
311344

312345
describe '#build_url' do

0 commit comments

Comments
 (0)