# follow-redirects sensitiveHeaders regex escaping can expose credential headers

Incomplete regex escaping in follow-redirects' sensitiveHeaders filter lets declared credential headers containing ^ or | survive cross-origin redirects and leak to attackers.

- Payout: Research
- Severity: high
- Program: follow-redirects
- Found by: president-xd, hash3liZer, eros938
- Disclosed: 2026-10-20
- CVE: CVE-2026-111755
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-8r9p-f939-6h3c
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-8r9p-f939-6h3c
- Tags: area:oss domain:oss impact:data-leak kind:research platform:ghsa sev:high tech:nodejs technique:incomplete-fix technique:regression technique:source-review vuln:info-disclosure vuln:secrets-leak

**Target:** npm library redirect header-stripping filter (Node.js http client)
**Root cause:** escapeRegex escapes only ] \ / ( ) * + ? . $ and omits ^ | [ { }, so a declared sensitive header name containing ^ becomes a mid-pattern anchor that never matches, and one containing | splits into separate alternatives; the filter is also built once at construction, before beforeRedirect can add entries.
**Steps:**
1. Build filter as `new RegExp("^(?:" + sensitiveHeaders.concat(options.sensitiveHeaders).map(escapeRegex).join("|") + ")$", "i")` with the library's partial escaper.
2. Declare `sensitiveHeaders: ["X-Api^Key", "X-Token|Secret", "X-Control-Key"]` and send headers `X-Api^Key: SECRET-LEAKED-1`, `X-Token|Secret: SECRET-LEAKED-2`, `X-Control-Key: SHOULD-BE-STRIPPED`, `Authorization: Bearer BUILTIN-CTL`.
3. Have the origin return `302 Location: http://127.0.0.1:45902/` (cross-origin) to an attacker collector that echoes `req.headers`.
4. Observe `X-Api^Key` and `X-Token|Secret` forwarded intact while `X-Control-Key` and `Authorization` are stripped.
5. Confirm reachability: `X-A^B` and `X-A|B` are ACCEPTED by `setHeader`; `X-A[B`, `X-A{B`, `X-A}B` REJECTED with ERR_INVALID_HTTP_TOKEN.
6. Facet 2: push `"X-Late"` into `options.sensitiveHeaders` inside `beforeRedirect`; observe `X-Late` still forwarded to the redirect target.
**How found:** Reporter read the new sensitiveHeaders filter construction and noticed escapeRegex's character class omitted ^ | [ { }; reasoned that ^ becomes a mid-pattern anchor and | splits the alternation, then verified with a pure-regex harness and a live two-server redirect PoC, using metacharacter-free names as controls to prove the strip branch ran.
**Exploitation:** Attacker controls or compromises a redirect target; an app that adopted sensitiveHeaders for a custom credential header whose name contains ^ or | follows the cross-origin (or HTTPS→HTTP downgrade) redirect and the library silently forwards the credential header to the attacker, who reads it from the received request.
**Escalation:** Downstream wrappers such as axios delegate redirect handling to follow-redirects, so any stack passing this option inherits the silent bypass; protocol downgrade also exposes the credential in cleartext.
**Insight:** When a security control is implemented as a dynamically built regex, diff the escaper's character class against the full metacharacter set and test each declared value against the generated pattern; also check whether the filter is snapshotted before hooks that can mutate the protected list.

