BREW-SNAKEMAKE-CVE-2026-87818 (PYSEC-2026-3983)

Vulnerability from osv_homebrew – Published: 2026-09-18 18:39 – Updated: 2026-10-01 11:43 – Source website
VLAI
Summary
GitPython: --no-index bypasses diff unsafe-option protections and enables a blind local-file content oracle
Details

Summary

GitPython 3.1.59 blocks a previously available local-file read path through unsafe git diff options such as -O/--orderfile.

However, the high-level diff API still permits --no-index with the default allow_unsafe_options=False.

--no-index changes the semantics of the paths arguments: instead of repository-relative pathspecs, Git interprets them as arbitrary filesystem paths.

When combined with the still-allowed -I/--ignore-matching-lines option, this creates a content-dependent Boolean oracle over a caller-selected local file.

This was reproduced against the published GitPython 3.1.59 wheel.

The original unsafe-option path is blocked in 3.1.59, while this alternate path remains reachable without setting allow_unsafe_options=True.

Details

Confirmed API surface:

repo.index.diff( None, no_index=True, I=pattern, paths=[baseline_path, target_path], create_patch=True, )

The relevant behavior is:

  1. --no-index makes the two values supplied via paths filesystem operands rather than repository pathspecs.
  2. -I/--ignore-matching-lines makes Git's result depend on whether the supplied regular expression matches the relevant file content.
  3. GitPython exposes the resulting bit through distinguishable behavior:
  4. matching condition: normal return with an empty DiffIndex
  5. non-matching condition: GitCommandError with exit status 1

An application that forwards attacker-influenced diff options and paths and exposes the success/error distinction can therefore be queried repeatedly to recover a guessable local single-line secret.

The issue was confirmed with the default allow_unsafe_options=False.

The behavior does not require the caller to explicitly opt into GitPython's unsafe-option mode.

Verified intended security boundary:

  • GitPython 3.1.58 accepts the earlier -O/--orderfile local-file input path.
  • GitPython 3.1.59 rejects that same path with UnsafeOptionError.
  • GitPython 3.1.59 still permits the --no-index alternate path described above.

This appears to be an alternate route to the same local-file confidentiality property that the 3.1.59 diff option hardening is intended to protect.

PoC

A minimal reproducer, controlled extraction demonstrator, proof matrix, and proposed remediation are included in the attached package.

gitpython-3159-maintainer-evidence.zip

The minimal reproducer creates only temporary researcher-controlled files and demonstrates the following predicate:

correct prefix -> normal GitPython return incorrect prefix -> GitCommandError(status=1)

In the controlled extraction test, I generated three independent random single-line values and recovered all three exactly through repeated calls to the GitPython high-level API.

Result: 3/3 recovered.

The extraction harness also installs a Python audit hook that rejects direct Python open() access to the target file during the oracle phase. The content-dependent read is therefore performed by the child git process invoked through GitPython rather than by the reproduction script directly.

Controls were also tested:

  • normal repository-scoped diff: no secret disclosure
  • same outside paths without --no-index: no arbitrary-filesystem interpretation
  • deliberately incorrect predicate: status 1 as expected

Suggested remediation is to classify --no-index as unsafe for the high-level diff API unless the caller explicitly sets allow_unsafe_options=True.

Impact

Potential impact is disclosure of local files readable by the process running GitPython.

Exploitation requires an embedding application to allow an attacker to influence:

  1. the relevant diff options,
  2. both path operands, and
  3. repeated requests while exposing a distinguishable success/error result.

The demonstrated attack is a blind content oracle rather than a one-request in-band file read. It is particularly applicable to short or structured single-line secrets where the target path and approximate value format are known or guessable.

Confirmed affected release: GitPython 3.1.59.


{
  "affected": [
    {
      "ecosystem_specific": {
        "fix": "bump",
        "range_state": "fixed",
        "resource": "gitpython",
        "resource_purl": "pkg:pypi/gitpython@3.1.62",
        "upstream_fixed_in": "3.1.60"
      },
      "package": {
        "ecosystem": "Homebrew",
        "name": "snakemake",
        "purl": "pkg:brew/snakemake"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "9.25.2"
            },
            {
              "fixed": "9.26.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "database_specific": {
    "confidence": "high",
    "source": "matched",
    "strategy": "registry",
    "upstream_evidence": [
      {
        "ecosystem": "PyPI",
        "key": "pkg:pypi/gitpython@3.1.62",
        "name": "gitpython",
        "resource": "gitpython",
        "strategy": "registry",
        "subject_version": "3.1.62"
      }
    ]
  },
  "details": "### Summary\n\nGitPython 3.1.59 blocks a previously available local-file read path through unsafe git diff options such as -O/--orderfile.\n\nHowever, the high-level diff API still permits --no-index with the default allow_unsafe_options=False.\n\n--no-index changes the semantics of the paths arguments: instead of repository-relative pathspecs, Git interprets them as arbitrary filesystem paths.\n\nWhen combined with the still-allowed -I/--ignore-matching-lines option, this creates a content-dependent Boolean oracle over a caller-selected local file.\n\nThis was reproduced against the published GitPython 3.1.59 wheel.\n\nThe original unsafe-option path is blocked in 3.1.59, while this alternate path remains reachable without setting allow_unsafe_options=True.\n\n### Details\n\nConfirmed API surface:\n\nrepo.index.diff(\n    None,\n    no_index=True,\n    I=pattern,\n    paths=[baseline_path, target_path],\n    create_patch=True,\n)\n\nThe relevant behavior is:\n\n1. --no-index makes the two values supplied via paths filesystem operands rather than repository pathspecs.\n2. -I/--ignore-matching-lines makes Git\u0027s result depend on whether the supplied regular expression matches the relevant file content.\n3. GitPython exposes the resulting bit through distinguishable behavior:\n   - matching condition: normal return with an empty DiffIndex\n   - non-matching condition: GitCommandError with exit status 1\n\nAn application that forwards attacker-influenced diff options and paths and exposes the success/error distinction can therefore be queried repeatedly to recover a guessable local single-line secret.\n\nThe issue was confirmed with the default allow_unsafe_options=False.\n\nThe behavior does not require the caller to explicitly opt into GitPython\u0027s unsafe-option mode.\n\nVerified intended security boundary:\n\n- GitPython 3.1.58 accepts the earlier -O/--orderfile local-file input path.\n- GitPython 3.1.59 rejects that same path with UnsafeOptionError.\n- GitPython 3.1.59 still permits the --no-index alternate path described above.\n\nThis appears to be an alternate route to the same local-file confidentiality property that the 3.1.59 diff option hardening is intended to protect.\n\n### PoC\n\nA minimal reproducer, controlled extraction demonstrator, proof matrix, and proposed remediation are included in the attached package.\n\n[gitpython-3159-maintainer-evidence.zip](https://github.com/user-attachments/files/31123571/gitpython-3159-maintainer-evidence.zip)\n\n\nThe minimal reproducer creates only temporary researcher-controlled files and demonstrates the following predicate:\n\ncorrect prefix   -\u003e normal GitPython return\nincorrect prefix -\u003e GitCommandError(status=1)\n\nIn the controlled extraction test, I generated three independent random single-line values and recovered all three exactly through repeated calls to the GitPython high-level API.\n\nResult: 3/3 recovered.\n\nThe extraction harness also installs a Python audit hook that rejects direct Python open() access to the target file during the oracle phase. The content-dependent read is therefore performed by the child git process invoked through GitPython rather than by the reproduction script directly.\n\nControls were also tested:\n\n- normal repository-scoped diff: no secret disclosure\n- same outside paths without --no-index: no arbitrary-filesystem interpretation\n- deliberately incorrect predicate: status 1 as expected\n\nSuggested remediation is to classify --no-index as unsafe for the high-level diff API unless the caller explicitly sets allow_unsafe_options=True.\n\n### Impact\n\nPotential impact is disclosure of local files readable by the process running GitPython.\n\nExploitation requires an embedding application to allow an attacker to influence:\n\n1. the relevant diff options,\n2. both path operands, and\n3. repeated requests while exposing a distinguishable success/error result.\n\nThe demonstrated attack is a blind content oracle rather than a one-request in-band file read. It is particularly applicable to short or structured single-line secrets where the target path and approximate value format are known or guessable.\n\nConfirmed affected release: GitPython 3.1.59.",
  "id": "BREW-snakemake-CVE-2026-87818",
  "modified": "2026-10-01T11:43:20Z",
  "published": "2026-09-18T18:39:20Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-whh4-5q6c-9v3x"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/pull/2217"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/commit/09f2cf383a992d1af697364569e9f384748c1bd8"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/gitpython-developers/GitPython"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.60"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/gitpython/PYSEC-2026-3983.yaml"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/gitpython-3.1.59-local-file-content-oracle-via-no-index"
    }
  ],
  "schema_version": "1.7.3",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "GitPython: --no-index bypasses diff unsafe-option protections and enables a blind local-file content oracle",
  "upstream": [
    "PYSEC-2026-3983",
    "CVE-2026-87818",
    "GHSA-whh4-5q6c-9v3x"
  ]
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…