{"id":"GHSA-p2f4-r6v6-j797","summary":"electron-updater: Cross-origin redirect leaks `PRIVATE-TOKEN` and mixed-case `Authorization` credentials in `builder-util-runtime`","details":"## Summary\n\nIn `electron-builder`'s `builder-util-runtime` package, the HTTP redirect handler (`HttpExecutor.prepareRedirectUrlOptions`) only stripped a credential header whose key string matched exactly lowercase `\"authorization\"`. Other credential-bearing headers — most notably `PRIVATE-TOKEN` (used by GitLab's personal access token flow) and mixed-case `Authorization` (used by GitLab's Bearer/OAuth flow) — were not stripped and could be forwarded to an attacker-controlled cross-origin redirect destination.\n\n---\n\n## Details\n\n### Root cause\n\n`HttpExecutor.prepareRedirectUrlOptions` (introduced in `builder-util-runtime` via [PR #9211](https://github.com/electron-userland/electron-builder/pull/9211), first released in `v26.0.20`) performed its cross-origin credential strip with a single case-sensitive property check:\n\n```typescript\n// vulnerable code (electron-builder v26.0.20 – v26.14.x) [via builder-util-runtime \u003c9.7.0]\nif (headers?.authorization) {\n  if (HttpExecutor.isCrossOriginRedirect(originalUrl, parsedRedirectUrl)) {\n    delete headers.authorization   // only removes the exact key \"authorization\"\n  }\n}\n```\n\nJavaScript object property access is case-sensitive. The guard `headers?.authorization` evaluates to `undefined` (falsy) when the key is `\"Authorization\"`, `\"AUTHORIZATION\"`, or any other casing, so the branch is never entered and **no header is deleted** for those cases.\n\n### Affected updater flows\n\nThe clearest reproduced path is the private GitLab updater flow.\n\n`packages/electron-updater/src/providers/GitLabProvider.ts` sets one of two credential headers depending on the token type:\n\n```typescript\n// GitLabProvider.setAuthHeaderForToken (affected versions)\nif (token.startsWith(\"Bearer\")) {\n  headers.authorization = token        // Bearer / OAuth token  →  key is lowercase\n} else {\n  headers[\"PRIVATE-TOKEN\"] = token     // personal access token →  key is \"PRIVATE-TOKEN\"\n}\n```\n\nDuring a private release update check, the updater requests a release asset through GitLab's `direct_asset_url`. GitLab commonly redirects asset downloads to an external object-storage origin (e.g., S3, GCS). Because the redirect crosses origins:\n\n1. A personal access token in `PRIVATE-TOKEN` **is never inspected** by the vulnerable strip guard — it is forwarded intact.\n2. A Bearer token set as `headers.authorization` (lowercase) **is stripped** correctly.\n3. A Bearer token set as `headers.Authorization` (capital A) or any other mixed-case variant **bypasses the guard** and is forwarded intact.\n\nGitLab is the concrete reproduced case; any other provider or custom updater configuration that places credentials in a non-lowercase-`authorization` header is equally affected.\n\n### Before v26.0.20\n\nVersions prior to `v26.0.20` did not contain `prepareRedirectUrlOptions` at all. All credential headers were forwarded unchanged on every redirect, regardless of origin. This represents a broader, pre-existing version of the same class of vulnerability.\n\n---\n\n## Proof of concept (reproduction shape)\n\n1. Configure the updater with an authenticated GitLab provider, supplying a personal access token (non-Bearer). The provider will set `PRIVATE-TOKEN: \u003ctoken\u003e` on requests.\n2. Trigger an update check. The updater fetches release metadata and then requests a release asset URL.\n3. The trusted GitLab origin returns a 3xx cross-origin redirect (e.g., to S3 object storage).\n4. `HttpExecutor.prepareRedirectUrlOptions` is called. The guard `headers?.authorization` is falsy (the key is `\"PRIVATE-TOKEN\"`). No header is deleted.\n5. The request to the redirect destination is issued with `PRIVATE-TOKEN: \u003ctoken\u003e` present in the headers.\n\n**Observed result:** The personal access token is forwarded to the redirect destination. An attacker who controls or can observe the redirect destination receives the token.\n\n---\n\n## Impact\n\nThis is a credential disclosure vulnerability. An automatic update check can leak:\n\n- GitLab personal access tokens (`PRIVATE-TOKEN`)\n- Bearer/OAuth tokens sent under a mixed-case `Authorization` key\n- Any other credential header not named exactly `\"authorization\"` in lowercase\n\nDisclosure of a GitLab PAT grants the attacker whatever repository and API permissions the token carries, enabling unauthorized access to private source code, packages, or release artifacts.\n\n---\n\n## Patches\n\nFixed in electron-builder `v26.15.0` [included via `v9.7.0` `builder-util-runtime`] via [PR #9834](https://github.com/electron-userland/electron-builder/pull/9834) (commit [`22a7532bd`](https://github.com/electron-userland/electron-builder/commit/22a7532bd01b9fb42cff7c58d599c7ad683569fe)).\n\nThe incomplete property-access guard was replaced with a separator-agnostic, case-insensitive lookup against a registry of known sensitive header names:\n\n```typescript\n// fixed code (v9.7.0+)\nconst normalizeName = (name: string): string =\u003e\n  name.toLowerCase().replace(/[-_]/g, \"\")\n\nconst SENSITIVE_REDIRECT_HEADERS = new Set([\n  \"authorization\", \"proxyauthorization\", \"privatetoken\",\n  \"xapikey\", \"xauthtoken\", \"xaccesstoken\", \"xgitlabtoken\",\n  \"cookie\", \"xcsrftoken\",\n])\n\n// In prepareRedirectUrlOptions, on cross-origin redirect:\nfor (const key of Object.keys(headers)) {\n  if (SENSITIVE_REDIRECT_HEADERS.has(normalizeName(key))) {\n    delete (headers as Record\u003cstring, unknown\u003e)[key]\n  }\n}\n```\n\n`normalizeName` converts to lowercase and strips `-` and `_` separators, so `PRIVATE-TOKEN`, `Private-Token`, `Authorization`, `AUTHORIZATION`, `X-Api-Key`, etc. are all matched. The fix also exports `addSensitiveRedirectHeader()` to allow custom publishers to register additional headers.\n\n**Upgrade path:** Update `builder-util-runtime` to `\u003e= 9.7.0`.\n\n---\n\n## Workarounds\n\nThere is no configuration-level workaround that prevents header forwarding in affected versions. The only mitigation short of upgrading is to avoid authenticated GitLab updater flows on versions `\u003c 9.7.0`.\n\nIf operating in a network environment where you control all possible redirect destinations, you may be able to prevent the token from reaching an attacker-controlled host through network-layer controls, but this is not a reliable mitigation.","aliases":["CVE-2026-54673"],"modified":"2026-07-24T14:16:01.807017022Z","published":"2026-07-24T14:03:19Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-07-24T14:03:19Z","nvd_published_at":"2026-06-30T23:17:28Z","cwe_ids":["CWE-200"],"severity":"HIGH"},"references":[{"type":"WEB","url":"https://github.com/electron-userland/electron-builder/security/advisories/GHSA-p2f4-r6v6-j797"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54673"},{"type":"WEB","url":"https://github.com/electron-userland/electron-builder/pull/9834"},{"type":"WEB","url":"https://github.com/electron-userland/electron-builder/commit/22a7532bd01b9fb42cff7c58d599c7ad683569fe"},{"type":"PACKAGE","url":"https://github.com/electron-userland/electron-builder"},{"type":"WEB","url":"https://github.com/electron-userland/electron-builder/releases/tag/electron-builder@26.15.0"}],"affected":[{"package":{"name":"builder-util-runtime","ecosystem":"npm","purl":"pkg:npm/builder-util-runtime"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"9.7.0"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-p2f4-r6v6-j797/GHSA-p2f4-r6v6-j797.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N"}]}