{"id":"CVE-2026-54673","aliases":["GHSA-p2f4-r6v6-j797"],"title":"electron-updater: Cross-origin redirect leaks `PRIVATE-TOKEN` and mixed-case `Authorization` credentials in `builder-util-runtime`","summary":"electron-updater: Cross-origin redirect leaks `PRIVATE-TOKEN` and mixed-case `Authorization` credentials in `builder-util-runtime`","severity":"high","cwe":["CWE-200"],"vendor":"builder-util-runtime","product":"builder-util-runtime","ecosystem":"npm","affected":["builder-util-runtime < 9.7.0"],"patched":["builder-util-runtime 9.7.0"],"published":"2026-07-24","updated":"2026-07-24","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-p2f4-r6v6-j797","references":[{"url":"https://github.com/electron-userland/electron-builder/security/advisories/GHSA-p2f4-r6v6-j797"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54673"},{"url":"https://github.com/electron-userland/electron-builder/pull/9834"},{"url":"https://github.com/electron-userland/electron-builder/commit/22a7532bd01b9fb42cff7c58d599c7ad683569fe"},{"url":"https://github.com/electron-userland/electron-builder/releases/tag/electron-builder@26.15.0"},{"url":"https://github.com/advisories/GHSA-p2f4-r6v6-j797"}],"tags":["ghsa","npm"],"epss":0.0024,"epssPercentile":0.15427,"ingestedAt":"2026-07-24T14:29:29.435Z","slug":"CVE-2026-54673","body":"## Overview\n\n## 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 <9.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: <token>` 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: <token>` 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 =>\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<string, unknown>)[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 `>= 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 `< 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.\n\n## Affected packages\n\n- `builder-util-runtime < 9.7.0`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `builder-util-runtime 9.7.0`","depth":"twilight","depthScore":41,"depthScoreParts":{"impact":41.3,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}