GHSA-2MWR-XJCG-37J7
Vulnerability from github – Published: 2026-10-08 22:09 – Updated: 2026-10-08 22:09Summary
mechanize leaked credentials to the redirect target when an HTTP redirect crossed to another host. Credentials set through Mechanize#request_headers= leaked even when they were Authorization.
Details
Two defects, both in lib/mechanize/http/agent.rb.
1. Mechanize#request_headers= bypassed the redirect strip entirely. #request_add_headers copied @request_headers onto every request unconditionally, with no host check, including the request issued after a redirect. The strip in #response_redirect mutated only the per-request headers hash and never touched agent state. Because request_headers= is the documented way to set a default credential for every request, the header the code explicitly protected — Authorization — was the one most likely to leak.
2. The strip list omitted Proxy-Authorization and Cookie2. Only CREDENTIAL_HEADERS = ['Authorization'] and COOKIE_HEADERS = ['Cookie'] were removed from the per-request headers hash on a cross-host redirect.
Cookies held in Mechanize#cookie_jar and credentials held in Mechanize::HTTP::AuthStore are not affected. Both are looked up per-URI, so they never follow a redirect to a foreign host. The exposure was limited to headers the caller set by hand.
PoC
agent = Mechanize.new
agent.request_headers = { 'Authorization' => 'Bearer secret' }
agent.get('https://example.test/redirects-to-attacker')
# the request to the attacker's host carries "Authorization: Bearer secret"
Impact
An attacker who controls a redirect target — through an open redirect on the site being fetched, an attacker-supplied fetch URL, DNS rebinding, or MITM — captures bearer tokens and session cookies from any mechanize agent that sets credentials through request_headers= or the per-request headers argument. Disclosure only; no integrity or availability impact.
Patches
Fixed in mechanize v2.14.1.
- Credentials and cookies are withheld from a request that follows a redirect across an origin, from both header sources: the per-request
headersargument andMechanize#request_headers=. CREDENTIAL_HEADERSgainsProxy-Authorization;COOKIE_HEADERSgainsCookie2.
Proxy-Authorization is withheld here although curl does not withhold it, because Net::HTTP tunnels https: requests with CONNECT, so a caller-supplied Proxy-Authorization travels inside the tunnel to the origin server rather than to the proxy.
Headers set through Mechanize#request_headers= no longer reach a redirect target on another origin when they are credentials. Callers that relied on the previous behavior will see those headers withheld.
What this fix does not cover
Only the headers in CREDENTIAL_HEADERS and COOKIE_HEADERS are withheld. A caller-supplied header that carries a credential under some other name — X-API-Key, X-Vault-Token, or any bespoke token header — still follows a redirect to another origin, matching the behavior of curl's CURLOPT_HTTPHEADER. If you set such a header, do not enable redirect following for requests that carry it, or scope it to a single request whose destination you control.
Workarounds
Set Mechanize#redirect_ok = false and handle redirects explicitly, or avoid request_headers= for credentials and pass them per-request only to hosts you intend to authenticate to.
Credit
Reported by @SnailSploit.
{
"affected": [
{
"package": {
"ecosystem": "RubyGems",
"name": "mechanize"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.14.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-107715"
],
"database_specific": {
"cwe_ids": [
"CWE-200",
"CWE-522"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-08T22:09:00Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Summary\n\n`mechanize` leaked credentials to the redirect target when an HTTP redirect crossed to another host. Credentials set through `Mechanize#request_headers=` leaked even when they were `Authorization`.\n\n## Details\n\nTwo defects, both in `lib/mechanize/http/agent.rb`.\n\n**1. `Mechanize#request_headers=` bypassed the redirect strip entirely.** `#request_add_headers` copied `@request_headers` onto every request unconditionally, with no host check, including the request issued after a redirect. The strip in `#response_redirect` mutated only the per-request headers hash and never touched agent state. Because `request_headers=` is the documented way to set a default credential for every request, the header the code explicitly protected \u2014 `Authorization` \u2014 was the one most likely to leak.\n\n**2. The strip list omitted `Proxy-Authorization` and `Cookie2`.** Only `CREDENTIAL_HEADERS = [\u0027Authorization\u0027]` and `COOKIE_HEADERS = [\u0027Cookie\u0027]` were removed from the per-request headers hash on a cross-host redirect.\n\nCookies held in `Mechanize#cookie_jar` and credentials held in `Mechanize::HTTP::AuthStore` are **not** affected. Both are looked up per-URI, so they never follow a redirect to a foreign host. The exposure was limited to headers the caller set by hand.\n\n## PoC\n\n```ruby\nagent = Mechanize.new\nagent.request_headers = { \u0027Authorization\u0027 =\u003e \u0027Bearer secret\u0027 }\nagent.get(\u0027https://example.test/redirects-to-attacker\u0027)\n# the request to the attacker\u0027s host carries \"Authorization: Bearer secret\"\n```\n\n## Impact\n\nAn attacker who controls a redirect target \u2014 through an open redirect on the site being fetched, an attacker-supplied fetch URL, DNS rebinding, or MITM \u2014 captures bearer tokens and session cookies from any `mechanize` agent that sets credentials through `request_headers=` or the per-request `headers` argument. Disclosure only; no integrity or availability impact.\n\n## Patches\n\nFixed in `mechanize` v2.14.1.\n\n- Credentials and cookies are withheld from a request that follows a redirect across an origin, from **both** header sources: the per-request `headers` argument and `Mechanize#request_headers=`.\n- `CREDENTIAL_HEADERS` gains `Proxy-Authorization`; `COOKIE_HEADERS` gains `Cookie2`.\n\n`Proxy-Authorization` is withheld here although curl does not withhold it, because `Net::HTTP` tunnels `https:` requests with `CONNECT`, so a caller-supplied `Proxy-Authorization` travels inside the tunnel to the origin server rather than to the proxy.\n\nHeaders set through `Mechanize#request_headers=` no longer reach a redirect target on another origin when they are credentials. Callers that relied on the previous behavior will see those headers withheld.\n\n### What this fix does not cover\n\nOnly the headers in `CREDENTIAL_HEADERS` and `COOKIE_HEADERS` are withheld. A caller-supplied header that carries a credential under some other name \u2014 `X-API-Key`, `X-Vault-Token`, or any bespoke token header \u2014 still follows a redirect to another origin, matching the behavior of curl\u0027s `CURLOPT_HTTPHEADER`. If you set such a header, do not enable redirect following for requests that carry it, or scope it to a single request whose destination you control.\n\n## Workarounds\n\nSet `Mechanize#redirect_ok = false` and handle redirects explicitly, or avoid `request_headers=` for credentials and pass them per-request only to hosts you intend to authenticate to.\n\n## Credit\n\nReported by @SnailSploit.",
"id": "GHSA-2mwr-xjcg-37j7",
"modified": "2026-10-08T22:09:00Z",
"published": "2026-10-08T22:09:00Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/sparklemotion/mechanize/security/advisories/GHSA-2mwr-xjcg-37j7"
},
{
"type": "WEB",
"url": "https://github.com/sparklemotion/mechanize/pull/676"
},
{
"type": "WEB",
"url": "https://github.com/sparklemotion/mechanize/commit/02a1235842d6eda8d4a5a3d8f13aba2cecf52e4f"
},
{
"type": "WEB",
"url": "https://github.com/sparklemotion/mechanize/commit/94e0902867296be804f36eccbb47acf7d5018745"
},
{
"type": "WEB",
"url": "https://github.com/sparklemotion/mechanize/commit/ac49abf2869297d83c3b11bbfb8b18e63b588c95"
},
{
"type": "PACKAGE",
"url": "https://github.com/sparklemotion/mechanize"
},
{
"type": "WEB",
"url": "https://github.com/sparklemotion/mechanize/releases/tag/v2.14.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Mechanize sends credential headers to another host after an HTTP redirect"
}
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.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.