# Secret exfiltration via attacker-controlled URLs

Credentials or env vars expanded into or attached to outbound fetches of attacker-supplied URLs without origin checks

_Area: Software supply chain and CI/CD. Seen in 2 disclosed reports; latest 2026-07-29. Updated 2026-11-02._

## How it works

A trusted component takes a URL from an untrusted source (a PR, a spec `$ref`, a remote name) and either attaches a secret to the request or expands environment variables into the URL before fetching. The broken assumption is that the URL is benign because the caller is trusted. It is not: the attacker controls the destination, so the secret travels to them. The secret is usually a short-lived token, a bearer token, or a process env var, and the fetch happens automatically during normal operation (build, code generation, `git fetch`).

## Where it shows up

- Header builders that set `Authorization`/`authorizationToken` unconditionally on every fetch [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-h754-fxp7-88wx.md)
- `$ref`, import, remote-include, or schema-warming features that fetch external URLs [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-h754-fxp7-88wx.md)
- URL helpers that call `os.path.expandvars()` or similar before protocol checks [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-94p4-4cq8-9g67.md)
- Shared helpers with a safe-mode flag whose default is the dangerous value [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-94p4-4cq8-9g67.md)
- Cloud build configs (`buildspec.yml`, `cloudbuild.yaml`, `azure-pipelines.yml`) that run on fork PRs with an attached IAM/service role [E1](https://bugbountylibrary.com/r/blog%3Adivyanshu-mehta-gitbook-io-researchs-hijacking-cloud-ci-cd-systems-for-fun-and-profit.md)
- Build containers that can reach `169.254.169.254` / `169.254.170.2` [E1](https://bugbountylibrary.com/r/blog%3Adivyanshu-mehta-gitbook-io-researchs-hijacking-cloud-ci-cd-systems-for-fun-and-profit.md)

## How to review

Find every outbound fetch and ask: is the destination attacker-influenced, and does the request carry a secret? The invariant: a credential must only be sent to a host on an explicit allowlist matching the trusted origin, never to a URL derived from untrusted input. For URL expansion, the invariant is that no env var is expanded into a URL unless the caller explicitly opts in, and the safe default must be the non-expanding one. For CI, the invariant is that untrusted PR code never runs with a role attached and IMDS reachable. Check the default value of any safe-mode parameter, then enumerate every caller of the shared helper, not just the reported sink [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-94p4-4cq8-9g67.md).

## How finders spotted it

- Noticed cloud build systems attach IAM roles and that IMDS is reachable from build containers; recon for build config files in public repos plus old PRs to confirm no first-time-contributor approval gate [E1](https://bugbountylibrary.com/r/blog%3Adivyanshu-mehta-gitbook-io-researchs-hijacking-cloud-ci-cd-systems-for-fun-and-profit.md)
- Read the header-builder in `src/resolved-swagger-schema.ts` and saw the headers object was unconditional, then traced `getRemoteRequestHeaders` through the fetch path to confirm it applied to every external `$ref` [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-h754-fxp7-88wx.md)
- Reviewed an incomplete fix, saw `expand_vars=False` applied only at `Repo._clone()`, and audited other callers of the shared URL helper to find the dangerous default still in use [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-94p4-4cq8-9g67.md)

## Variants seen

- Cloud build role credentials exfiltrated via IMDS from fork-PR-triggered builds [E1](https://bugbountylibrary.com/r/blog%3Adivyanshu-mehta-gitbook-io-researchs-hijacking-cloud-ci-cd-systems-for-fun-and-profit.md)
- Bearer token forwarded to attacker `$ref` hosts during code generation [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-h754-fxp7-88wx.md)
- Env vars expanded into remote URLs, leaking into `.git/config` and to the attacker host on fetch [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-94p4-4cq8-9g67.md)
- Same expansion writing secrets into a tracked `.gitmodules` via `Submodule.add()` [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-94p4-4cq8-9g67.md)

## Why fixes fail

E3 is an incomplete fix of an earlier advisory: the safe-mode flag was added to one sink only, leaving sibling public APIs on the dangerous default. Patching the reported call site without auditing all callers of the shared helper leaves the primitive live.

## Impact and severity

High to critical. E1 reaches cloud account compromise and, with permissive build roles, registry push and supply chain impact on all customers. E2 blast radius depends on token scope: repo write, API impersonation, or pipeline access. E3 leaks process secrets and persists them in committable files. Severity rises with token privilege and how automatically the fetch runs.

## Seen in real software
Open-source packages and projects, and products, where this pattern was found. If the target runs one, compare the version it runs with the fix.
- **swagger-typescript-api (npm)** <= 13.12.1, fixed in 13.12.2: swagger-typescript-api vulnerable to authorization-token exfiltration via spec `$ref` (CVE-2026-54660). Report: https://bugbountylibrary.com/r/ghsa%3AGHSA-h754-fxp7-88wx.md. Fix: https://github.com/acacode/swagger-typescript-api/commit/306d59acb8ffbb00f953f807b97234b21f51d9de
- **GitPython (pip)** < 3.1.55, fixed in 3.1.55: GitPython: Environment-variable exfiltration via Repo.create_remote() / Remote.add() URL (incomplete fix of GHSA-rwj8-pg. Report: https://bugbountylibrary.com/r/ghsa%3AGHSA-94p4-4cq8-9g67.md. Fix: https://github.com/gitpython-developers/GitPython/commit/863417457a0633db7ea5aed4fd01e0b291a41162

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Adivyanshu-mehta-gitbook-io-researchs-hijacking-cloud-ci-cd-systems-for-fun-and-profit.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-h754-fxp7-88wx.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-94p4-4cq8-9g67.md
