CVE-2026-25765: Verified Repro With Script Download
CVE-2026-25765: Faraday: SSRF via protocol-relative URL overriding base authority
CVE-2026-25765 is verified against faraday · rubygems. Affected versions: faraday >= 1.0.0, <= 1.10.4 and >= 2.0.0, <= 2.14.0. Vulnerability class: SSRF. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00147.
What Is CVE-2026-25765?
CVE-2026-25765 (GHSA-33mh-2634-fwr2) is a medium-severity server-side request forgery (SSRF, CWE-918) vulnerability in the Ruby faraday HTTP client, where a protocol-relative request path can override the connection's configured base host. Pruva reproduced it (reproduction REPRO-2026-00147).
CVE-2026-25765 Severity & CVSS Score
CVE-2026-25765 is rated medium severity, with a CVSS base score of 5.8 out of 10.
Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.
Affected faraday Versions
faraday · rubygems versions faraday >= 1.0.0, <= 1.10.4 and >= 2.0.0, <= 2.14.0 are affected.
How to Reproduce CVE-2026-25765
pruva-verify REPRO-2026-00147 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00147/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-25765
Reproduced by Pruva's autonomous agents — 128 tool calls over 10 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-25765
Summary
The faraday Ruby HTTP client library contained a Server-Side Request Forgery (SSRF) vulnerability in its build_exclusive_url method. When a connection was configured with a trusted base URL (e.g., http://safe.local/api) and a request path starting with // (a protocol-relative or "network-path" reference) was supplied, the // prefix incorrectly passed the existing relative-URL guard. Ruby's URI#+ then interpreted the protocol-relative path as an authority override, replacing the base host with the attacker-specified host. This allowed an attacker-controlled path string to redirect outbound requests to arbitrary hosts, bypassing the intended scope of the Faraday connection.
Impact
- Package:
faraday(RubyGems) - Affected versions:
1.0.0–1.10.4and2.0.0–2.14.0 - Fixed versions:
1.10.5and2.14.1 - CWE: CWE-918 (Server-Side Request Forgery)
- Risk: Medium — CVSS 5.8
- Consequences: Any application that forwards user-influenced path strings to a Faraday connection without additional validation can be tricked into making requests to internal or attacker-controlled hosts, potentially exposing internal services, cloud metadata endpoints, or allowing request-smuggling attacks.
Root Cause
The vulnerable code lives in lib/faraday/connection.rb inside the build_exclusive_url method.
Vulnerable guard (v2.14.0):
url = "./#{url}" if url.respond_to?(:start_with?) && !url.start_with?('http://', 'https://', '/', './', '../')
Because a protocol-relative URL such as //evil.com/path begins with /, the guard condition !url.start_with?('/') evaluates to false, so the URL is not prefixed with ./. When URI#+ merges the base URL (http://safe.local) with //evil.com/path, RFC 3986 dictates that the authority component of the merge target overrides the base authority. The resulting URI becomes http://evil.com/path, completely leaving the configured base host.
Fix commit: a6d3a3a0bf59c2ab307d0abd91bc126aef5561bc
Fixed guard (v2.14.1):
url = "./#{url}" if url.respond_to?(:start_with?) &&
(!url.start_with?('http://', 'https://', '/', './', '../') || url.start_with?('//'))
The fix explicitly detects the // prefix and prepends ./, neutralising the authority component. The merged URI then stays scoped to the base host (http://safe.local///evil.com/path), preventing host override.
Reproduction Steps
- Run
repro/reproduction_steps.sh. - The script installs the vulnerable (
2.14.0) and fixed (2.14.1) gem versions. - It starts a local TCP listener on an ephemeral port to stand in for the attacker host.
- For each version it:
- Creates a Faraday connection with base URL
http://192.0.2.1(TEST-NET-1, guaranteed non-routable). - Calls
build_exclusive_url('//127.0.0.1:<port>/x')and records the resulting host. - Issues an actual HTTP
GETwith the same protocol-relative path. - Counts how many requests the local listener received.
- Creates a Faraday connection with base URL
- Expected evidence:
- Vulnerable (2.14.0):
built_hostis127.0.0.1, the request succeeds with status 404, and the local listener receives ≥1 request. - Fixed (2.14.1):
built_hostremains192.0.2.1, the request fails withConnection refused, and the local listener receives 0 requests.
- Vulnerable (2.14.0):
Evidence
logs/vulnerable.json— Faraday 2.14.0 runtime results:{ "built_url": "http://127.0.0.1:33953/x", "built_host": "127.0.0.1", "request_status": 404, "request_success": true }logs/fixed.json— Faraday 2.14.1 runtime results:{ "built_url": "http://192.0.2.1///127.0.0.1:33953/x", "built_host": "192.0.2.1", "request_error": "Faraday::ConnectionFailed: Failed to open TCP connection to 192.0.2.1:80 (Connection refused - connect(2) for \"192.0.2.1\" port 80)", "request_success": false }repro/runtime_manifest.json— consolidated verdict:{ "verdict": "confirmed", "vulnerable_version": "2.14.0", "fixed_version": "2.14.1", "vulnerable_built_host": "127.0.0.1", "fixed_built_host": "192.0.2.1", "vulnerable_request_succeeded": true, "fixed_request_succeeded": false, "vulnerable_listener_requests": 2, "fixed_listener_requests": 0 }
Recommendations / Next Steps
- Upgrade: Update
faradayto>= 1.10.5or>= 2.14.1immediately. - Input validation: Applications accepting user-influenced URL paths should validate that paths do not begin with
//before passing them to Faraday, as a defense-in-depth measure. - Regression testing: Add unit tests that assert
build_exclusive_urlrejects host override for//evil.com,//evil.com:8080,//user:pass@evil.com, and///evil.com(all covered by the upstream spec added in the fix commit). - Network segmentation: Where SSRF risk is high, restrict outbound connectivity from application servers to only required destinations.
Additional Notes
- Idempotency:
repro/reproduction_steps.shwas executed twice consecutively and produced identical verdicts both times. - Edge cases: The reproduction script uses
192.0.2.1(RFC 5737 TEST-NET-1) as the base host to guarantee the fixed version fails predictably withConnection refusedrather than relying on DNS non-resolution, which can be unreliable in environments with wildcard DNS or ISP hijacking. - Server log: The local HTTP listener received 2 requests during the vulnerable test and 0 requests during the fixed test, providing direct runtime proof that the protocol-relative path was neutralised by the patch.
CVE-2026-25765 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
git clone --depth=100 https://github.com/lostisland/faraday.git faraday_repoCloning into 'faraday_repo'...
cd faraday_repo && git show a6d3a3a0bf59c2ab307d0abd91bc126aef5561bc{"cwd":"/root/.pruva/runs/cve-2026-25765_20260522-175117","duration_ms":5,"exit_code":0,"stderr":"","stdout":"commit a6d3a3a0bf59c2ab307d0abd91bc126aef5561bc\nAuthor: Matt <iMacTia@users.noreply.github.com>\nDate: Sat Feb 7 15:11:41 2026 +0000\n\n Merge commit from fork\n \n Protocol-relative URLs (e.g. `//evil.com/path`) bypassed the existing\n relative-URL guard in `build_exclusive_url`, allowing an attacker-controlled\n URL to override the connection's base host. The `//` prefix matched the\n `/` check in `start_with?`, so these URLs were passed through to\n `URI#+` which treated them as authority references, replacing the host.\n \n Extend the guard condition so that URLs starting with `//` are also\n prefixed with `./`, neutralising the authority component and keeping\n requests scoped to the configured base host.\n \n Security: GHSA-33mh-2634-fwr2\n \n Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>\n\ndiff --git a/.rubocop_todo.yml b/.rubocop_todo.yml\nindex f3d0f2e..3f56b48 100644\n--- a/.rubocop_todo.yml\n+++ b/.rubocop_todo.yml\n@@ -31,7 +31,7 @@ Metrics/AbcSize:\n # Offense count: 3\n # Configuration parameters: Coun… [truncated]Artifacts and Evidence for CVE-2026-25765
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix CVE-2026-25765
FAQ: CVE-2026-25765
How does the CVE-2026-25765 SSRF attack work?
http://safe.local/api) but forwards an attacker-influenced path string into get()/post(); if that path is //evil.com/..., build_exclusive_url's guard lets it through and the merged URL's authority becomes evil.com instead of safe.local, so the outbound request leaves the configured host entirely.Which faraday versions are affected by CVE-2026-25765, and where is it fixed?
How severe is CVE-2026-25765?
How can I reproduce CVE-2026-25765?
//-prefixed path into a request and confirms the outbound connection is redirected to the attacker-chosen host instead of the configured base host.References for CVE-2026-25765
Authoritative sources for CVE-2026-25765 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.