Threats affecting pkg:generic/hdf5
191,086 threats
Most recently published first
| Severity | CVSS | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|
| Low | 2.5 | CVE-2026-112907lexicon · filelock: On Windows an oplock on a soft lock's marker holds SoftFileLock.acquire() past its timeout and blocks the Low2.5GCVE DatabaseCVE-2026-112907 On Windows, certain soft lock implementations in filelock (versions 3.22.0 through 4.2.0) can be blocked indefinitely due to Windows opportunistic locks (oplocks) on the lock marker file. Another local user with read access can take a Level 1 oplock on a stale marker file and never release it, causing SoftFileLock.acquire() calls to hang past their timeout. This results in partial availability impact without compromising confidentiality or integrity. The issue is fixed in filelock version 3.25.2_5. | GCVE Database | ||
| Medium | 6.3 | CVE-2026-111750AIOHTTP · Pure-Python parser request smuggling Medium6.3GCVE DatabaseCVE-2026-111750 AIOHTTP's pure Python HTTP parser is vulnerable to an HTTP request smuggling attack when used instead of the default C parser. This affects applications using the Python parser, such as those with the AIOHTTP_NO_EXTENSIONS environment or non-standard wheel installations. The vulnerability is fixed in version 3.25.2_5. | GCVE Database | ||
| Low | 2.3 | Low2.3GCVE DatabaseCVE-2026-111749 A vulnerability in AIOHTTP allows a subdomain to remove the Host-Only flag from an existing cookie set for a parent domain. This could reduce cookie security protections for users accessing untrusted subdomains. The impact is limited because most use cases do not reflect these cookies back to the browser. | GCVE Database | ||
| Medium | 6.3 | Medium6.3GCVE DatabaseCVE-2026-111748 CVE-2026-111748 is a vulnerability in aiohttp where an attacker can spoof the Request.scheme attribute by sending an absolute-form HTTP request. This can mislead applications relying on this attribute for security decisions, such as determining whether a connection is encrypted. The issue affects versions >=3.25.2_3 and <3.25.2_5. A fix is available in version 3.25.2_5. A workaround is to use a WAF rule to block absolute-form requests, which are typically only legitimate for proxy usage. | GCVE Database | ||
| High | 8.2 | CVE-2026-111066AIOHTTP · File descriptor exhaustion in multipart requests High8.2GCVE DatabaseCVE-2026-111066 CVE-2026-111066 is a vulnerability in AIOHTTP where an attacker can exhaust file descriptors by sending specially crafted multipart requests. This affects handlers using the Request.post() method, potentially leading to resource exhaustion. The issue is fixed in version 3.25.2_5. | GCVE Database | ||
| Medium | 6.9 | CVE-2026-111065AIOHTTP · Connector pool exhaustion via Max-Age overflow Medium6.9GCVE DatabaseCVE-2026-111065 AIOHTTP versions >=3.25.2_3 and <3.25.2_5 contain a vulnerability where an overflow in Max-Age processing can cause connections to remain open, leading to temporary exhaustion of the connection pool until garbage collection intervenes. This flaw has a medium severity with a CVSS score of 6.9. A fix is available in version 3.25.2_5. | GCVE Database | ||
| High | 8.2 | High8.2GCVE DatabaseCVE-2026-111064 The aiohttp server component in Lexicon versions >=3.25.2_3 and <3.25.2_5 lacks an initial request/header timeout, allowing an attacker to keep an idle connection open indefinitely before sending any data. This issue has been fixed in version 3.25.2_5. The vulnerability has a high severity with a CVSS score of 8.2. A recommended workaround is to use a reverse proxy with sensible keepalive limits. | GCVE Database | ||
| Medium | 6.3 | CVE-2026-111063AIOHTTP · Secure attribute not enforced for shared cookie Medium6.3GCVE DatabaseCVE-2026-111063 A vulnerability in AIOHTTP allows the Secure attribute of a shared cookie to not be enforced, potentially causing the cookie to be sent over unencrypted requests. This flaw affects versions from 3.25.2_3 up to but not including 3.25.2_5 and has a medium severity rating. A fix is available in version 3.25.2_5. | GCVE Database | ||
| Medium | 6.5 | CVE-2026-116686pieces-cli · Model Context Protocol Python SDK: Client fetched server-chosen $ref URLs while validating tool results against output Medium6.5GCVE DatabaseCVE-2026-116686 The Model Context Protocol (MCP) Python SDK versions 1.10.0 through 1.29.1 and 2.0.0 through 2.1.1 have a vulnerability where the client fetches server-chosen external $ref URLs during tool output schema validation. This can cause the client to make network or local file requests synchronously without timeout, potentially freezing the client session. The issue is fixed in versions 1.30.0 and 2.2.0, which restrict reference resolution to within the schema document only. | GCVE Database | ||
| High | 7.4 | High7.4GCVE Database PyJWT versions up to 2.13.0 and current master before 2.26.0 contain a vulnerability where the HMAC verification accepts an empty key when using octet JWKs. This allows an attacker to forge tokens without knowing any secret if the application verifies tokens using a JWK/JWKS with an empty 'k' value. The issue arises because the empty key rejection implemented for string/bytes keys is not applied to keys loaded from JWKs. The vulnerability is fixed in PyJWT version 2.26.0. | GCVE Database | ||
| Medium | 5.9 | CVE-2026-116685mistral-vibe · Model Context Protocol Python SDK: HTTP client transports followed cross-origin redirects carrying custom headers and Medium5.9GCVE DatabaseCVE-2026-116685 ### Summary In affected versions, the SDK's Streamable HTTP and SSE client transports followed HTTP redirects to any host. If the configured MCP endpoint answered with a redirect to a different origin: - custom headers set on the client (for example an `X-API-Key` credential) were sent to that origin, and - on a `307` or `308` redirect the request body was re-sent there too. The SDK's OAuth client providers followed redirects on their own discovery, registration and token requests in the same way. A lone `Authorization` bearer header was not exposed; the HTTP library already drops it on cross-origin redirects. Anyone able to make an endpoint you trust redirect elsewhere (a stale redirect to a lapsed domain, a misrouted proxy rule, redirect logic an attacker can influence) could capture that material. A server that is itself malicious gains nothing this way; it already receives your requests directly. ### Am I affected? Yes, if your code uses the SDK as an MCP client over HTTP through any of: - `streamable_http_client` (on 2.x also `Client` given an `http://` or `https://` URL) - `streamablehttp_client` or `sse_client` - `ClientSessionGroup` with HTTP server parameters - the internal `create_mcp_http_client` helper This includes an HTTP client you passed in yourself with `follow_redirects=True`. Affected versions: 1.8.0 through 1.29.1, and 2.0.0 through 2.1.1. Not affected: - MCP servers - stdio clients - a client you supplied with `follow_redirects` left at its default of `False` ### Fix Upgrade to `mcp` 1.30.0 (1.x) or 2.2.0 (2.x) or later; no code changes are needed. The transports, and the OAuth providers' own requests, now follow a redirect only when it stays on the endpoint's origin (same scheme, host and port, or an `http` to `https` upgrade on the same host) and keeps the request method. Any other redirect is not followed and the request fails with an error naming the target (`MCPError` on 2.x, `httpx.HTTPStatusError` on 1.x), whichever HTTP client the transport uses. The internal `create_mcp_http_client` helper no longer turns redirect following on, so code calling it directly now receives the redirect response instead. If your client currently reaches its server through a redirect to another host, including a server that needs no credentials, it will stop connecting after the upgrade: set the endpoint URL to the redirect's target. Until you can upgrade, supply your own HTTP client with `follow_redirects` left at `False` (`http_client=` on `streamable_http_client`; `httpx_client_factory=` on `sse_client` and `streamablehttp_client`; `ClientSessionGroup` has no such parameter). If a client may already have followed a redirect to a host you do not control, rotate the credentials it carried. <details> <summary>If you genuinely need the old behaviour (unsupported)</summary> This restores what this advisory fixes: redirects to any host are followed again, carrying your custom headers and, on `307`/`308`, the request body, OAuth token requests included. Prefer setting the endpoint URL to the redirect's target. If you cannot, and you accept forwarding credentials to the redirect target, give the transport an HTTP client that follows redirects regardless of what the SDK asks for: ```python import httpx2 # on 1.x: import httpx and subclass httpx.AsyncClient instead class FollowRedirectsAnyway(httpx2.AsyncClient): """Follows redirects to ANY origin. Deliberately gives up the SDK's same-origin protection.""" async def send(self, request, *, follow_redirects=False, **kwargs): return await super().send(request, follow_redirects=True, **kwargs) http_client = FollowRedirectsAnyway(timeout=httpx2.Timeout(30, read=300)) # streamable_http_client(url, http_client=http_client) # sse_client / streamablehttp_client take a factory instead: def client_factory(headers=None, timeout=None, auth=None): return FollowRedirectsAnyway(headers=headers, auth=auth, timeout=timeout if timeout is not None else httpx2.Timeout(30, read=300)) # sse_client(url, httpx_client_factory=client_factory) ``` The redirect handling is then the HTTP library's own, as before the fix: a lone `Authorization` header is still dropped on cross-origin hops and `301`/`302`/`303` become `GET`. It is not something the SDK supports or tests. </details> --- | GCVE Database | ||
| Medium | 5.3 | Medium5.3GCVE DatabaseCVE-2026-114603 ## Summary `TraceState.from_header` splits the inbound W3C `tracestate` header with a regular expression that starts with an unbounded optional-whitespace run, and no length limit is applied first. A run of spaces or tabs that is not followed by a comma costs O(N²) CPU. A single unauthenticated request with a ~100 KB `tracestate` keeps the handling worker busy for ~6 s (spaces) to ~12 s (tabs); under an asyncio server it blocks every other connection for that time. ## Affected component / version - Package: `opentelemetry-api` (pip) — `opentelemetry/trace/span.py` (`TraceState.from_header`), reached from `opentelemetry/trace/propagation/tracecontext.py` (`TraceContextTextMapPropagator.extract`). - Confirmed in `main` (5e8083e8) and in the v1.44.0 and v1.45.0 tags; the same pattern is present back to v1.0.0 (introduced by #1599, March 2021). Not exhaustively verified against every release in between. - Reachable with the default propagators (`OTEL_PROPAGATORS` defaults to `tracecontext,baggage`) from any server instrumentation that calls `propagate.extract()`, for example `opentelemetry-instrumentation-asgi`. ## Details 1. The header reaches the parser without any length check: ```python # opentelemetry-api/src/opentelemetry/trace/propagation/tracecontext.py:57 tracestate_headers = getter.get(carrier, self._TRACESTATE_HEADER_NAME) # <-- attacker-controlled request header(s), any size if tracestate_headers is None: tracestate = None else: tracestate = TraceState.from_header(tracestate_headers) # <-- no length check before parsing (line 61) ``` 2. The split pattern and the parser: ```python # opentelemetry-api/src/opentelemetry/trace/span.py:45 _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS = 32 # <-- only enforced later, in TraceState.__init__, after the split _delimiter_pattern = re.compile(r"[ \t]*,[ \t]*") # <-- (line 46) starts with an unbounded [ \t]* # span.py:382 @classmethod def from_header(cls, header_list: list[str]) -> TraceState: pairs = {} # type: dict[str, str] for header in header_list: members: list[str] = re.split(_delimiter_pattern, header) # <-- (line 400) VULNERABLE: O(N^2) on "a=b" + " " * N + "x" for member in members: if not member: continue match = _member_pattern.fullmatch(member) if not match: _logger.warning( # <-- also logs the entire malformed member (up to the whole header) "Member doesn't match the w3c identifiers format %s", member, ) return cls() ... ``` `re.split` tries the pattern at every offset. At each offset inside a whitespace run, `[ \t]*` consumes the rest of the run, finds no `,`, backtracks and fails, then the engine moves one character forward and repeats. N characters × up to N steps each = O(N²). The 32-member limit never gets a chance to run because `split` has to finish first. 3. The sibling propagator already has the missing guard: ```python # baggage/propagation/__init__.py:68 _MAX_HEADER_LENGTH = 8192 ... if len(header.encode()) > self._MAX_HEADER_LENGTH: # <-- (line 93) rejected before any regex runs; tracestate has no equivalent _logger.warning(...) return context ``` A 16 KiB header costs ≈ 0.25 s and an 8 KiB header ≈ 0.07 s. Measured cost of `extract()` on one core (valid `traceparent`, `tracestate: "a=b" + N spaces + "x"`): the time quadruples each time N doubles (see the proof of concept). Roughly 0.06 s at 10,000 characters, 1 s at 40,000, 4 s at 80,000, 9 s at 100,000 (tabs are about 1.6× slower). Whether a server lets such a header through (header-size limits, tested): gunicorn rejects ≥ 8190 bytes (response code 431); `uvicorn --http h11` rejects above 16 KiB (400); waitress accepts up to 256 KiB (200 KB accepted, 300 KB rejected); `uvicorn --http httptools` — the default parser with `uvicorn[standard]` — accepted 300 KB, 1 MB, 5 MB and 20 MB `tracestate` headers. ## Proof of concept ### A. Show the quadratic cost (no server needed) 1. Install the API: `pip install opentelemetry-api` (any version from 1.0 up to the current main is affected). 2. Save as `poc_tracestate.py`: ```python import logging import time from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator # The library logs the whole malformed member at WARNING; silence it so the timings stay readable. logging.disable(logging.CRITICAL) TRACEPARENT = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" propagator = TraceContextTextMapPropagator() previous = None for n in (10_000, 20_000, 40_000, 80_000): # N spaces after a valid member, no comma anywhere in the header carrier = {"traceparent": TRACEPARENT, "tracestate": "a=b" + " " * n + "x"} start = time.perf_counter() propagator.extract(carrier) elapsed = time.perf_counter() - start growth = f"x{elapsed / previous | GCVE Database | ||
| High | 7.5 | CVE-2026-114145Mistral vibe · GitPython validates a submodule's ".git" path only after cloning into it (RCE) High7.5GCVE DatabaseCVE-2026-114145 ### Summary `create_submodule(name, path, url)` clones a submodule into the repo's own `.git` directory before it rejects the path. A `path` of ".git/hooks" is accepted by the pre-clone check, so the submodule is cloned straight into the live hooks directory, and a file named `post-checkout` in the submodule becomes an executable `.git/hooks/post-checkout`. On 3.2.0+ the later index check does raise `ValueError`, but only after the clone already wrote to disk, and it does not remove the planted hook; on 3.1.x there is no such check and the call succeeds silently. Either way the hook runs on the next git operation (e.g. checkout), giving code execution. ### Details The checkout path is validated too late, after the clone has already mutated disk. This breaks the "validate before mutation" invariant that commit 43a43cd8 ("Validate submodule checkout and metadata paths before mutation") set out to enforce. Submodule.add() calls the permissive path check before cloning, and the strict check only reaches the ".git" alias at index time, after _clone_repo has written to disk: git/objects/submodule/base.py (Submodule.add) 599: path = cls._to_relative_path(repo, path) # accepts ".git/hooks" 673: mrepo = cls._clone_repo(repo, url, path, name, ...) # clones into <worktree>/.git/hooks 714: index.add([sm], write=True) # strict check only triggers here git/index/base.py (IndexFile._preprocess_add_items) 732: _validate_repo_path(entry.path) # rejects ".git", too late _to_relative_path only blocks `..`/absolute/out-of-tree paths; `.git` aliases are rejected only by _validate_repo_path, which was added in 3.2.0 (commit a75aedf3) and runs at the index boundary. The ValueError it raises at line 732 does not roll back what _clone_repo already wrote. Version-dependent behavior: - 3.2.0 .. tip bda18e08: _validate_repo_path raises ValueError at index.add, but the hook is already on disk and is not removed (incorrect order of validation vs. clone). - 3.1.x (e.g. 3.1.59): _validate_repo_path does not exist and _to_relative_path does not reject `.git`; create_submodule(path=".git/hooks", ...) returns with no error at all and the hook is planted silently. ### Proof of concept Setup: evilsub/ : a git repo whose ROOT contains an executable file named post-checkout (the payload) victim/ : a git repo with at least 1 commit and a second branch, whose .git/hooks has NO sample hooks (the state of a minimal/CI checkout, or `git init` with an empty template dir, or GIT_TEMPLATE_DIR/init.templateDir empty) import git, os.path as osp try: git.Repo("victim").create_submodule(name="x", path=".git/hooks", url=osp.abspath("evilsub")) except ValueError: pass # 3.2.0+: ".git" is rejected, but only AFTER the clone, so the plant already happened. # 3.1.x: no exception is raised at all; the call succeeds and still plants the hook. #### victim/.git/hooks/post-checkout now exists, mode 0755, attacker-controlled, on every #### affected version. Any later checkout runs it: #### git -C victim checkout <other-branch> -> the hook runs -> code execution Verified live: - tip bda18e08 (3.2.0 + 3 commits): create_submodule raised ValueError from git/index/base.py:732, yet .git/hooks/post-checkout was present and executable, and `git checkout` executed it. - 3.1.59: create_submodule returned with no error; .git/hooks/post-checkout was planted and executed by `git checkout`. ### Impact Arbitrary code execution. A library/app that creates a submodule from an attacker-influenced `path` ends up with an attacker-controlled executable in `.git/hooks`, which git runs on ordinary later operations. This affects any service that derives a submodule checkout path from untrusted data, e.g. one that ingests a submodule spec (name/path/url) from a user-supplied manifest or API request and feeds `path` into create_submodule. On 3.2.0+ the library's own validation is the thing that fails: it correctly rejects the `.git` path, but only after the submodule has already been cloned into place, so an app that traps the ValueError reasonably believes the unsafe path was blocked. Preconditions (hence AC:H): (1) the app passes an attacker-influenced submodule `path`; (2) the target hook name is free in `.git/hooks`, true for minimal/CI/container checkouts and whenever init.templateDir/GIT_TEMPLATE_DIR is empty. With the default sample hooks present, `git clone` refuses the non-empty destination and no plant occurs. Affected: all tested versions from 3.1.x (3.1.59) through the current tip bda18e08 (3.2.0 + 3 commits). 3.1.x plants silently; 3.2.0+ plants despite raising ValueError. | GCVE Database | ||
| Medium | 6.9 | CVE-2026-110860GitPython · Submodule.move() follows an attacker-committed intermediate symlink outside the worktree Medium6.9GCVE DatabaseCVE-2026-110860 ## Summary GitPython's public `Submodule.move()` API verifies that its destination is lexically inside the parent repository, but it does not reject symbolic links in the destination's intermediate components before filesystem operations. On systems that check out Git symlinks, an untrusted repository can therefore redirect a fixed, application-selected move destination outside the parent worktree. The reporter reproduced this on the latest release, GitPython 3.1.62, and current `main`. In the strict scenario, the repository commits `vendor -> ../outside`, while the consuming application independently passes the fixed literal `vendor/module`. `Submodule.move()` follows `vendor`, moves the initialized submodule checkout to `outside/module`, rewrites `.gitmodules` and the index to the symlinked path, and leaves the index unable to produce a tree. The same result occurs when the committed symlink contains an absolute path to an external directory. No attacker-controlled API argument, local symlink planting, race, Git hook, unsafe-option flag, or network access during the proof is required. ## Trust boundary and preconditions The attacker controls: - an untrusted Git repository that the application clones; - a mode-`120000` tracked symlink named `vendor`; - the initialized submodule's content. The consuming application controls the call and uses a fixed layout rule: ```python submodule.move("vendor/module") ``` The trigger requires the application to initialize the submodule and later call `Submodule.move()` with a path below the attacker-committed symlink. Ordinary clone or submodule update does not automatically make this call. Windows was not tested. The primitive applies where Git materializes the tracked entry as a real symlink; on Windows that generally requires `core.symlinks=true` with Developer Mode or sufficient privilege. Configurations that materialize it as an ordinary file do not take this path. ## Product and directly verified revisions - Package: `GitPython` (`pip` ecosystem) - Latest release: 3.1.62, tag commit `db47516dac750c5968b8f6d388b18712c355df50` - Current `main`: `b62e91be897f687a622e40cd1e59c33dce45f124` - Relevant `git/objects/submodule/base.py` SHA-256 in both revisions: `a4355d49e0b75c7c7e74d33ac70ff9550f940c977848c99da40eb41e034b189c` - Upstream state rechecked: 2026-09-10 KST Only these two revisions were directly tested. ## Severity and weakness The demonstrated impact is an integrity boundary violation: repository content selects an existing directory outside the worktree, and GitPython moves an attacker-influenced checkout into a missing or empty child path there with the victim process's permissions. It also removes the original checkout and corrupts the parent repository's index/configuration state. For a downstream service that remotely accepts repositories and performs this exact layout operation with user involvement retained, `AV:N` gives a conditional application-level score of 7.1. A fully automatic service may warrant a different `UI` assessment. The library report uses `AV:L` because the vulnerable API call is a separate application action and is not part of GitPython's ordinary clone/update flow. No confidentiality impact, code execution, overwrite of an existing file, or overwrite of a non-empty directory was demonstrated. ## Reproducer The following script creates only temporary local repositories and a self-generated canary. It passes `protocol.file.allow=always` separately with `-c` on each fixture Git invocation; that permission is process-scoped and is not persisted to global or repository configuration. Run it from a fresh source checkout: ```bash git clone --depth 1 --branch 3.1.62 https://github.com/gitpython-developers/GitPython.git python3 poc.py /path/to/GitPython python3 poc.py --absolute-link /path/to/GitPython ``` `--absolute-link` commits the temporary external directory's absolute path instead of `../outside`. ```python #!/usr/bin/env python3 """Local-only reproducer for GitPython Submodule.move() symlink traversal.""" from __future__ import annotations import argparse import json import subprocess import sys import tempfile from pathlib import Path from typing import Optional def run_git( *args: str, cwd: Optional[Path] = None, check: bool = True ) -> subprocess.CompletedProcess: command = [ "git", "-c", "user.name=GitPython security fixture", "-c", "user.email=fixture@example.invalid", "-c", "protocol.file.allow=always", *args, ] return subprocess.run(command, cwd=cwd, check=check, text=True, capture_output=True) def read_core_worktree(parent) -> str: module_config = Path(parent.git_dir) / "modules" / "logical-name" / "config" return parent.git.config("--file", str(module_config), "--get", "core.worktree") def read_gitmodules_path(parent) -> str: modules_file = Path(parent.working_tree_dir) / ".gitmodules" return parent.git. | GCVE Database | ||
| High | 8.7 | CVE-2026-110859mistral-vibe · GitPython: arbitrary file read via attacker-controlled branch name in `Head.checkout()` (incomplete fix of High8.7GCVE DatabaseCVE-2026-110859 > **A note on the referenced files.** This report was submitted through GitHub's private > vulnerability reporting API, which carries no attachments. Every transcript this report leans > on is reproduced **inline above**, verbatim. The file names it mentions (`poc.sh`, `poc.log`, > per-case transcripts, `evidence.md`, `negative-controls.md`, blob-sweep tables) are the local > evidence bundle those inline excerpts were extracted from, with a checksum manifest. I am happy > to send the bundle by any channel you prefer, or to open a private fork — just say which. The > PoC scripts are self-contained and reproduce the result from a clean environment. --- **Affected:** `<= 3.1.62` — all published releases; also `main` @ `4905b4af` **Verified at runtime:** GitPython `3.1.62` with git `2.55.0` **Date:** 2026-09-10 **Reporter:** MAXI8594 --- ## Threat model | | | |---|---| | **Attacker** | anyone who can get the application to clone a URL they control. No account, no credential, no privilege in the consuming application. | | **Pre-need** | a repository they host, with `HEAD` pointing at a branch whose name is the payload. A `git push` to a plain git server is enough — no filesystem access to a server is required (`raw/06`). | | **Gets** | the contents of any file the application's process can read, returned in band through the exception. `/etc/passwd` recovered 34 of 34 lines. | | **Real-world likelihood** | **High.** The vulnerable consumer is six lines of ordinary code with no unusual configuration: `Repo.clone_from(user_url)` then `repo.active_branch.checkout()`. `allow_unsafe_options=False` is the default. Cloning user-supplied URLs is the library's central use case — CI systems, repository importers, scanners and bots. | **On severity relative to the sibling advisories.** GitHub, as CNA, scored the three prior advisories in this class at 6.5 / `PR:L` (CVE-2026-76217, CVE-2026-78678, CVE-2026-78679). This report asks for 7.5 / `PR:N`, and the difference is one measured fact: in those three, the application had to pass an attacker-influenced value into the API. Here it passes nothing — the clone materialises the hostile branch on its own and `repo.active_branch` returns it (`raw/02`). If that reading is not accepted, the `PR:L` path via `Repo.create_head()` is also real and lands at 6.5, matching the siblings. ## Summary `Head.checkout()` guards its `**kwargs` against unsafe Git options but never inspects its own positional argument — the branch name — which reaches the `git checkout` argv with no `--` separator. A repository whose default branch is named `--pathspec-from-file=/etc/passwd` makes a consumer that merely clones it and checks out the default branch return the entire contents of that file to the caller. This is the exact combination of two advisories the project already fixed: - **CVE-2026-76217** (`GHSA-hh9p-6wh2-4mfc`) — *"Arbitrary file read via `--pathspec-from-file` in `IndexFile.remove()` **and `Head.checkout()`**"*. Fixed in 3.1.58 by commit `f2550b65`, which added the guard to this exact method — but only for `**kwargs`. - **CVE-2026-78679** (`GHSA-3wxw-xv34-2frg`) — *"`TagReference.create` positional reference bypasses kwargs-only `--file` guard, enabling arbitrary file read (incomplete fix of 3af0c251)"*. Fixed in 3.1.59 by commit `1b0d2d9b`, which applied the positional check to `git/refs/tag.py`, `git/diff.py`, `git/index/base.py` and `git/repo/base.py` — but not to `git/refs/head.py:286`. `f2550b65` states in its own commit message that the audit found the positional variant and applied it to `HEAD.reset` — 185 lines above `Head.checkout`, in the same file, in the same commit: > "The audit also found that **reset's positional commit could carry the option before its argument > separator**. […] **Check reset's positional commit as well as keyword options**" `HEAD.reset` got the fix. `Head.checkout` did not, and `1b0d2d9b` did not come back for it. ## Affected Asset | | | |---|---| | Package | `GitPython` (PyPI) | | Affected versions | **`<= 3.1.62`** — all published releases | | Latest release tested | `3.1.62` | | Also present on | `main` @ `4905b4af` | | Vulnerable file | `git/refs/head.py` | | Guard | line 286 — `Git._option_candidates([], kwargs)` | | Sink | line 293 — `self.repo.git.checkout(self, **kwargs)` | | CWE | CWE-88 (Argument Injection), CWE-73 (External Control of File Name or Path), CWE-200 (Exposure of Sensitive Information) | | CVSS 3.1 | `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N` → **7.5 High** | The version range was established by **blob hash against every tag**, not by dates: the sink line `self.repo.git.checkout(self, **kwargs)` — with no `--` separator — is present in `git/refs/head.py` in every tag from `0.3.1-beta2` through `3.1.62`. The `3.1.62` blob is byte-identical to `main`: `7f563374ef9e018b3d5f95054498e8591e112ce2`. Full sweep in `raw/07-blob-version-sweep.txt`. The kwargs-only guard first appears in **3.1.58**, so `3.1.58`–`3.1.62` i | GCVE Database | ||
| Critical | 9.2 | CVE-2026-110858Mistral vibe: GitPython Remote.pull() refspec bypasses the --upload-pack unsafe-option guard, enabling arbitrary Critical9.2GCVE DatabaseCVE-2026-110858 ## Summary `Remote.pull()` in GitPython forwards its positional `refspec` argument directly to the `git pull` child process while its unsafe-option guard inspects only keyword arguments. An application that passes attacker-influenced data as the `refspec` (for example a user-supplied branch or refspec parameter in a web or CI service) can supply an option-shaped value such as `--upload-pack=<command>`. The value bypasses the `unsafe_git_pull_options` check, is placed after the `--` separator on the `git pull` command line, and is executed as the upload-pack program for local-path or SSH transports. This yields arbitrary command execution with the privileges of the calling process using the documented default arguments (`allow_unsafe_options=False`). ## Root Cause `Remote.pull()` is documented to reject dangerous options unless `allow_unsafe_options=True`, and `--upload-pack` is explicitly listed as such an option. The guard, however, only screens keyword arguments and never the positional `refspec`. The check builds its candidate list with `Git._option_candidates([], kwargs)`, passing an empty list in place of the positionals, so the `refspec` value is never compared against `unsafe_git_pull_options = ["--upload-pack"]`. The normalized `refspec` is then forwarded verbatim to the `git pull` invocation. The leading `--` separator that neutralizes an option-shaped refspec for `git fetch` and `git push` does not protect `git pull`, because `git pull` re-forwards its trailing positionals to an internal `git fetch` without re-inserting a `--` separator, so the positional becomes an option to that internal fetch. ```text Affected function: git.remote.Remote.pull Source: git/remote.py (guard at the check_unsafe_options call using _option_candidates([], kwargs); sink at the self.repo.git.pull("--", self, refspec, ...) invocation) ``` ## Impact An attacker who controls the `refspec` value passed to `Remote.pull()` achieves arbitrary operating-system command execution as the process invoking GitPython, with no additional privileges and no user interaction, on the default code path. Exploitation requires that the pulled remote uses a transport that runs upload-pack on the local side, namely a local filesystem path or SSH; over HTTPS the `--upload-pack` value is inert because the server ignores a client-supplied upload-pack program. This transport requirement narrows the population of vulnerable deployments but does not add work for the attacker once a vulnerable target is reached. Working checkouts commonly use local-path or SSH origins, so the exposed population is substantial. ## Proof of Concept ```text Environment: GitPython from the affected source tree; system git 2.43.0. The pulled origin uses a local filesystem transport (protocol.file.allow=always for git >= 2.38). ``` Reproduction steps: 1. Create an origin repository reachable over a local-path transport. 2. In a consumer repository, register that origin and call `Remote.pull()` with an attacker-controlled refspec. 3. Observe that the injected command executes. ```python import git repo = git.Repo("consumer") # has an origin using a local-path or ssh transport origin = repo.remote("origin") # Attacker-controlled refspec value; default arguments (allow_unsafe_options=False): origin.pull(refspec="--upload-pack=touch /tmp/pwned; git-upload-pack") ``` ```text cmdline: git pull -v -- origin --upload-pack=touch /tmp/pwned; git-upload-pack [attack] marker created (RCE) : True [isolate] upload_pack passed as KWARG blocked : True (only the positional refspec escapes) [sibling] same value via git fetch : safe (pull-specific behavior) [control] benign refspec : safe ``` ## Attack Chain 1. **Exposure:** An application calls `Remote.pull(refspec=<value>)` on the default path, where `<value>` is derived from attacker-influenced input such as a branch or refspec request parameter. 2. **Control:** The attacker supplies the string `--upload-pack=<command>; git-upload-pack`. The value passes through `Git._unpack_args`, which only flattens and stringifies it, preserving the option shape. 3. **Path:** The protocol loop calls `Git.check_unsafe_protocols` on the value, which only matches remote-helper transport URLs (a `<transport>::` form) and does not flag an `--upload-pack=` token. 4. **Guard:** The unsafe-option guard calls `check_unsafe_options(options=Git._option_candidates([], kwargs), ...)`. Because positionals are passed as an empty list, the positional `refspec` is never inspected against `unsafe_git_pull_options`. 5. **Primitive:** The value is forwarded to `git pull -v -- <remote> --upload-pack=<command>; git-upload-pack`; `git pull` re-forwards the positional to an internal `git fetch` without a `--` separator, so it is honored as the upload-pack program. 6. **Result:** For a local-path or SSH transport, git executes `<command>` as the upload-pack program, giving arbitrary command execution as the calling p | GCVE Database | ||
| High | 7.7 | High7.7GCVE DatabaseCVE-2026-110857 ### Summary GitPython has spent 2022-2026 rolling out two reusable guards method-by-method: `Git.check_unsafe_protocols(url)` (raises `UnsafeProtocolError` for `<transport>::` remote-helper URLs - CVE-2022-24439) and `Git.check_unsafe_options(...)` (raises `UnsafeOptionError` for denylisted option spellings - CVE-2023-40267, CVE-2026-42215, CVE-2026-67323). Three wrapper methods that build a `git` argv from caller-controlled values still have **neither**: 1. **`Git.ls_remote`** (git/cmd.py:1128-1143) - checks options (:1139-1141) but forwards the URL positional unchecked; no `allow_unsafe_protocols` parameter exists. The same URL through `clone_from` raises `UnsafeProtocolError`. 2. **`Repo.merge_base`** (git/repo/base.py:895-936) - forwards `**kwargs`/`*rev` to `self.git.merge_base` with no guard; guarded sibling `iter_commits` sits 12 lines up. Its exception handler additionally swallows every non-128 `GitCommandError` into `[]`, masking the injection. 3. **`IndexFile.move`** (git/index/base.py:1092-1168) - forwards `**kwargs` to `git mv` twice (:1147, :1167); guarded sibling `remove()` is 23 lines above. Verified unfixed on `main @ a54d159` (2026-09-21), post-dating latest release 3.1.62 (2026-09-07) - all three sites re-verified today. ### Impact **Site 1 (`ls_remote`)** - RCE conditional on the victim git transport policy. The common 'probe first, then clone' pattern (`Git(...).ls_remote(attacker_url)`) with an `ext::sh -c ...` URL executes arbitrary commands when the victim git permits the ext transport: dynamically verified with `protocol.ext.allow=always` (marker executed). git >= 2.38.1 stock config refuses the transport (`fatal: transport 'ext' not allowed`, rc 128) - GitPython adds no defense-in-depth and surfaces a bare `GitCommandError`. Environments on git < 2.38.1 are exposed with stock config - the exact population CVE-2022-24439 was scored against. **Sites 2/3** - latent argument-injection surface: caller kwargs flow 1:1 into the `git` argv before operands and `--` (cmd.py:1850 `opt_args + ext_args`), and `execute_kwargs` (incl. `shell=True`/`env` - the CVE-2023-40267 Windows mechanism) passes through unguarded. Today `git merge-base`/`git mv` expose no exec option, so we claim the missing-guard class (vendor-settled by CVE-2026-67323, which bundled archive/ls_remote/iter_commits/blame in one advisory), not direct RCE. ### Dynamic verification (today; GitPython main @ a54d159, git 2.55.0, Linux) | # | Call | Result | |---|---|---| | T1 | `repo.git.ls_remote('ext::/tmp/gp_touch.sh')`, stock config | git refuses (rc 128); **no `UnsafeProtocolError` from GitPython** | | T1b | same + victim config `protocol.ext.allow=always` | **marker created - arbitrary command executed** | | T2 | `repo.git.ls_remote('.', upload_pack=...)` | blocked by cmd.py:1141 (options-side fixed - protocol-side is the residual gap) | | T3 | `repo.merge_base('main','side', upload_pack=...)` | **not blocked**; argv `git merge-base --upload-pack=... main side`; rc 129 silently converted to `[]` | | T4 | `repo.index.move(['f1','f2'], output=...)` | **not blocked**; option reaches `git mv` (rc 129) | ### Coverage matrix (main @ a54d159) `check_unsafe_protocols`: 7 call sites (`_clone`, `archive`, `set_url`, `create`, `fetch`, `pull`, `push`) - none on `ls_remote`. `check_unsafe_options`: 24 call sites incl. `ls_remote`, `iter_commits`, `IndexFile.remove` - absent on `merge_base` and `move`. ### Precedents (same repo, same guard family) - **CVE-2022-24439 / GHSA-hcpj-qp55-gfph** (critical): `check_unsafe_protocols` on the clone path - Site 1 is the same `ext::` primitive on a sibling API. - **CVE-2023-40267 / GHSA-pr76-5cm5-w9cj** (critical, 'incomplete fix for CVE-2022-24439'): `check_unsafe_options` - unguarded kwargs to argv treated as RCE (`shell=True` on Windows). - **CVE-2026-42215 / GHSA-rpm5-65cw-6hj4** (high, fixed 3.1.47): underscored-spelling bypass - rollout still active in 2026. - **CVE-2026-67323 / GHSA-956x-8gvw-wg5v** (high, 8.4, fixed 3.1.51): added `check_unsafe_options` to `archive`/`ls_remote`/`iter_commits`/`blame`; its PoC is literally `repo.git.ls_remote('.', upload_pack=...)`. Establishes that a wrapper missing the check is itself a vulnerability. This report is the residual gap of that rollout: `ls_remote` got options but not protocols; `merge_base`/`move` got neither. ### Recommended fix (existing helpers, 1-3 lines per site) 1. `Git.ls_remote`: `if not allow_unsafe_protocols: self.check_unsafe_protocols(args[0])` + new `allow_unsafe_protocols: bool = False` (the `_clone` pattern, base.py:1536-1538). 2. `Repo.merge_base`: `check_unsafe_options(Git._option_candidates(list(rev), kwargs), unsafe_options=self.unsafe_git_revision_options)` (the `iter_commits` pattern); stop masking non-128 statuses. 3. `IndexFile.move`: the `remove()` pattern with `unsafe_git_pathspec_from_file_options`. One advisory, three affected methods, severity anchored on Site 1 - mirroring CVE-2026-67323's bundling. | GCVE Database | ||
| High | 8.7 | CVE-2026-110856Mistral vibe: GitPython parse_actor_and_date() is vulnerable to quadratic backtracking on malformed identity lines High8.7GCVE DatabaseCVE-2026-110856 ### Summary This is a residual of GHSA-g5vv-9gxw-82hx / CVE-2026-87819. The 3.1.60 fix made `Actor._from_string` regex-free, but `parse_actor_and_date` still runs `_re_actor_epoch` on the same author/committer/tagger line one step earlier. That pattern backtracks quadratically when the trailing epoch or timezone is missing or malformed, so the same class of slow parse is still reachable in 3.1.62. ### Details `git/objects/util.py:322`: ```python _re_actor_epoch = re.compile(r"^.+? (.*) (\d+) ([+-]\d+).*$") ``` `.+?` and `(.*)` can both absorb the same run of spaces, and the required ` (\d+) ([+-]\d+)` tail is only checked after them. On a long line with no valid tail, the search retries every split point. It is called from `parse_actor_and_date` (`git/objects/util.py:335`), which runs on the author and committer lines in `Commit._deserialize` (`git/objects/commit.py:917`, `:931`) and on the tagger line in `git/objects/tag.py:129`. The published payload (letters plus an unterminated `<`) is fast on 3.1.62. Replacing the letters with spaces is not: | author line (after `author `) | 2 KB | 8 KB | 32 KB | |---|---|---|---| | `'a'*n + ' <'` (advisory payload) | 0.0 ms | 0.1 ms | 0.4 ms | | `' '*n` | 28 ms | 453 ms | 7,700 ms | | valid line, long name (control) | 0.0 ms | 0.0 ms | 0.0 ms | Cost grows about 16x for each 4x increase in length. ### PoC ```python import time from git.objects.util import parse_actor_and_date line = "author " + " " * 32000 t = time.perf_counter() parse_actor_and_date(line) print(f"{time.perf_counter() - t:.1f} s") # ~7 s on 3.1.62 ``` End to end, reading `Commit.author` on a commit object whose author line is 32 KB of spaces followed by any malformed tail (no epoch, no timezone, non-numeric epoch, or bad timezone) takes about 7.2 s per access. The same line with a valid `1191999972 -0700` tail takes 1.3 ms. Note for reproducing: `git hash-object -t commit -w` refuses such an object (`missingEmail`), so the test object was written with `--literally`. A remote can still serve one, because `fetch.fsckObjects` and `transfer.fsckObjects` default to false. This is the same delivery model as CVE-2026-87819 ("craft a commit object with a malformed author field"). ### Impact CPU exhaustion (CWE-1333) in any application that uses GitPython to read commits or tags from an untrusted repository: CI tooling, repository scanners, code-hosting integrations. Each access to `Commit.author`, `Commit.committer` or `TagObject.tagger` on a crafted object costs seconds, and a repository can contain many such objects. ### Suggested fix Match the leading field name with `.[^ ]*` instead of `.+?`. Both stop at the first space after the first character, but `.[^ ]*` cannot overlap the following `(.*)`, so the backtracking disappears: ```python _re_actor_epoch = re.compile(r"^.[^ ]* (.*) (\d+) ([+-]\d+).*$") _re_only_actor = re.compile(r"^.[^ ]* (.*)$") ``` Verification on current main (73a72c9): - Differential over 368 actor lines (real-world shapes plus edge cases): 0 differences in match groups for either regex. - `' '*n` at 64 KB: 32,170 ms before, 0.98 ms after. Valid lines are unchanged (0.01 ms). - The existing test suite gives the same pass/fail set with and without the change - The new regression test, `test_parse_actor_and_date_malformed_line_is_linear`, fails on the old pattern (8.4 s) and passes with the fix (0.5 s). The full patch (fix plus regression test) is attached below. ```diff diff --git a/git/objects/util.py b/git/objects/util.py index a68d701f..d8c0594d 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -319,8 +319,11 @@ def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]: # Precompiled regexes -_re_actor_epoch = re.compile(r"^.+? (.*) (\d+) ([+-]\d+).*$") -_re_only_actor = re.compile(r"^.+? (.*)$") +# The leading field name ("author", "committer", "tagger") is matched with ``.[^ ]*`` rather than +# ``.+?``: both stop at the first space after the first character, but ``.[^ ]*`` cannot overlap the +# following ``(.*)``, so a line with no valid trailing epoch/offset no longer backtracks quadratically. +_re_actor_epoch = re.compile(r"^.[^ ]* (.*) (\d+) ([+-]\d+).*$") +_re_only_actor = re.compile(r"^.[^ ]* (.*)$") def parse_actor_and_date(line: str) -> Tuple[Actor, int, int]: diff --git a/test/test_util.py b/test/test_util.py index cf4299d5..e1631420 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -22,6 +22,7 @@ from git.cmd import dashify from git.objects.util import ( altz_to_utctz_str, from_timestamp, + parse_actor_and_date, parse_date, tzoffset, utctz_to_altz, @@ -571,6 +572,30 @@ class TestUtils(TestBase): Actor("name last another", "some-very-long-email@example.com"), ) + def test_parse_actor_and_date_malformed_line_is_linear(self): + # A long author line whose trailing epoch/offset is missing or malformed used to make + # _re_actor_epoch backtrack | GCVE Database | ||
| High | 7.1 | High7.1GCVE DatabaseCVE-2026-110855 ### Summary `Repo._clone()`'s handling of `separate_git_dir` never disables `Git.polish_url()`'s environment-variable expansion, unlike every other caller of that helper that handles attacker-influenced data. Since `separate_git_dir` can be derived from an attacker-controlled `.gitmodules` submodule name, a completely ordinary `clone` + `submodule_update(init=True, recursive=True)` on a malicious repository expands an environment variable of the attacker's choosing and exposes its value both as a persistent on-disk directory name and, whenever DEBUG-level logging is enabled (a common CI/verbose-logging configuration), in a `Popen(...)` log line that is not redacted the way credential-bearing arguments are. ### Root cause / Details `git/repo/base.py`'s `_clone()` has: ```python sep_dir = kwargs.get("separate_git_dir") if sep_dir: kwargs["separate_git_dir"] = Git.polish_url(sep_dir) ``` This is the only remaining `Git.polish_url()` call site handling attacker-influenced data that does not pass `expand_vars=False` (there is one other unguarded call site, `clone_path` at line 1537, but its input is the caller's own local destination path, not attacker-controlled). The sibling fixes for `GHSA-94p4-4cq8-9g67`/`GHSA-rwj8-pgh3-r573` added `expand_vars=False` to `clone_url` (5 lines below this one, in the same function), `git/repo/base.py:1599`, `remote.py:814`, and `submodule/base.py:686` — this one did not receive it. (`submodule/root.py` also currently sets `expand_vars=False` at a related call site, but that was added incidentally by a later, unrelated commit, not as part of these security fixes.) `Git.polish_url()`'s docstring itself says: "Disable this for values obtained from an untrusted source, such as remote URLs." Its non-Cygwin branch does plain `os.path.expandvars()` on the entire string, including embedded variables anywhere in an absolute path. `separate_git_dir` reaches `_clone()` from `Submodule._clone_repo()` (`git/objects/submodule/base.py`) as `module_abspath = osp.join(parent_repo.git_dir, "modules", name)`, where `name` is the raw `.gitmodules` submodule section name — fully attacker-controlled content of a cloned repository. `_validated_name()` (added by the fix for `GHSA-hmq2-w58f-27jc`) only rejects names that are absolute, contain a drive letter, or contain `..` path components; it does not reject `$`. `_need_gitfile_submodules()` is true for git ≥ 1.7.5, i.e. the default path for virtually every real-world installation. ### Reproduction (live, against real, unmodified GitPython code, no monkeypatching) Built a hostile repository with a plain, ordinary git command: `git submodule add --name '$GPY_SECRET_MARKER' ../evil_submodule sub`. Set an environment variable in the "victim" process (standing in for a real CI secret such as `AWS_SECRET_ACCESS_KEY`): `GPY_SECRET_MARKER=AKIA_TOTALLY_FAKE_BUT_SECRET_LOOKING_VALUE_12345`. Then ran the exact ordinary workflow the `GHSA-hmq2-w58f-27jc` advisory's own PoC also uses: ```python repo = Repo.clone_from(evil_parent_url, victim_clone_dir) repo.submodule_update(init=True, recursive=True) ``` Observed result (`ls .git/modules/`): ``` .git/modules/AKIA_TOTALLY_FAKE_BUT_SECRET_LOOKING_VALUE_12345/ (a full git repo: HEAD, config, objects/, refs/, hooks/) ``` The literal `$GPY_SECRET_MARKER`-named directory was never created — only the expanded one exists, proving expansion happened before the value reached the `git clone --separate-git-dir=...` subprocess argument. The persistent, expanded-name directory under `.git/modules/` is itself the primary disclosure channel — it survives on disk for as long as the clone does. A second, independent channel: with the `git.cmd`/`git.repo.base` loggers at DEBUG level (a common CI/verbose-logging setting, not requiring any special flag), GitPython's own `Popen(...)` debug log line prints the fully expanded `--separate-git-dir=<value>` subprocess argument verbatim — `remove_password_if_present()` only redacts URL passwords/Authorization headers, not path-style arguments like this one, so the expanded value is not scrubbed. (The call also fails downstream with an `OSError`/`FileNotFoundError` referencing the original, unexpanded name, since `_write_git_file_and_module_config()` looks for a directory using the pre-expansion name — but that error message itself does not contain the expanded/secret value.) **Control**: the same call with an ordinary (no `$`) submodule name succeeds normally and creates `.git/modules/<name>` as expected — isolating the gap to submodule names containing environment-variable syntax specifically. ### Already-known check Checked all 35 published `gitpython-developers/GitPython` security advisories in full text. `GHSA-hmq2-w58f-27jc` covers the same `name` → `.git/modules/<name>` path but for path-traversal (`../../..`), not variable expansion. `GHSA-59cr-6r3x-644w` covers the sibling `path` field reaching unguarded `os.makedirs()`. `GHSA-94p4-4cq8-9g67`/`GHSA-rwj8-pgh3-r573` co | GCVE Database |
Page 38 of 9,555 · 1 duplicate CVE record merged