Threats affecting pkg:generic/osquery
192,059 threats
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| Medium | 6.1 | EPSS 0.2% | CVE-2026-112937virtualenv · filelock: ReadWriteLock on the hard-link route deletes any directory another user renames onto its .filelock-* link Medium6.1EPSS 0.2%GCVE DatabaseCVE-2026-112937 `ReadWriteLock` and `AsyncReadWriteLock` (the SQLite-backed reader/writer lock) remove their private `.filelock-*` link directories with `shutil.rmtree` (a recursive delete) on a remembered path. They do not check that the path still names the directory they created. This happens on the hard-link route (a hard link is a second name for the same file). That route is every connection on macOS since 4.1.1 and any POSIX process without a usable `/proc/self/fd` (a Linux path that names the process's own open files), such as a chroot or container that does not mount `/proc`. Another local user who can rename entries in the directory that holds the lock database can rename one of the directories of the user running the lock (below: the lock user) onto the link directory's name, or onto a forged dead-holder name. Such a lock directory is a `0777` or group-writable cache, build or project directory without the sticky bit (a directory flag that lets only a file's owner remove or rename it). The lock user's process then deletes that tree and everything in it with the lock user's permissions, and reports no error. Stock Linux with `/proc` mounted is not affected. Affected versions are filelock 4.1.0 through 4.2.0. Anthropic's OSS Scanner reported this in private as `ANT-2026-LSSZIUEO` [high, CWE-367]: ReadWriteLock deletes any directory another user renames to .filelock-*. ## Severity **CVSS 3.1 `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L` scores 6.1 (Medium).** `AV:L` (local): the other user needs an account on the same host. `PR:L` (low privileges): an ordinary account is enough. `UI:N` (no user interaction). `AC:L` (low attack complexity): two `rename` calls and no race against filelock's own code. `I:H` (high integrity impact): the other user destroys a directory tree owned by the lock user, and the lock user's program reports no error. `A:L` (partial availability loss): the Python 3.11 `RecursionError` variant makes the lock user's first lock construction fail. `C:N` (no confidentiality impact): the other user learns nothing. `S:U` (scope unchanged): the effect stays within the lock user's own privileges. Classification from the external review: **conditional security**, a cross-user rename in a non-sticky shared parent that turns private-link cleanup into recursive deletion of the lock user's tree. The scanner rated it high. The review accepts the mechanism, notes that the macOS route rests on a Linux run with `/proc` hidden rather than on a run on macOS, and narrows the finding to that precondition and route. ## Scope and preconditions - The other party is a different local account with write permission on the directory that holds the lock database, where that directory has no sticky bit. That is a `0777` directory, or a group-writable one (in a setgid `2775` directory a group member can rename the lock user's sub-directory the same way). Renaming an entry needs write permission on the parent only, not on the entry. Anyone who can write the parent can rename a `0700` directory of the lock user. - Sticky directories (`/tmp` itself, any `1777` directory) stop the release variant: the rename of an entry owned by the lock user fails with `EPERM`. The dead-owner sweep still reaches a lock user's tree that sits inside a directory the other account owns there. That account may rename its own directory to a forged dead-owner name, and the lock user's sweep deletes everything inside it. The Python 3.11 `RecursionError` variant also works in a sticky directory. - Platform and route. The lock user must connect through the private hard link. That is every `ReadWriteLock` or `AsyncReadWriteLock` on macOS (since 4.1.1, #820), or a POSIX process whose `/proc/self/fd/<n>` (`/dev/fd/<n>` elsewhere) does not exist, such as a chroot or container without `/proc`. Linux with `/proc` mounted connects through the descriptor and creates no `.filelock-*` directory (checked: none appears, also with two holders in one process), so stock Linux is not affected at 4.2.0. The macOS statement rests on the `descriptor_usable` branch at line 984 and `docs/how-to.rst`. The reproducer runs the same Python code on Linux with `/proc` hidden, which takes root and is test scaffolding, not a step the other user performs. Windows does not take this route. - Target set. Only directories that sit in the shared parent itself, or that the other account can already write, are reachable. The kernel refuses to move a `0700` directory of the lock user in from another parent. A tree the other account can already write (a group-writable project tree created with umask `002`) is no gain, since that account can delete it without filelock. A lock user running as root deletes a tree of any owner. - Ancestor boundary. Protection against another local user is possible only while no other user can replace the lock's directory or any of its ancestors. That holds when each ancestor belongs to the user or root and is either not writable by others or sticky. ## Af | GCVE Database | ||
| Medium | 6.1 | EPSS 0.2% | Medium6.1EPSS 0.2%GCVE DatabaseCVE-2026-112936 ### Summary `ReadWriteLock` and `AsyncReadWriteLock` resolve the lock path with `pathlib.Path.resolve()` and pass it to `sqlite3.connect()` without refusing a symlink. A local user who can create names in the lock's directory, such as `/tmp` or a shared cache, can plant a symlink at the lock path and make the victim's process create, lock, or write the file it points to, or break mutual exclusion between the victim's own processes. ### Details `ReadWriteLock.__init__` stores `pathlib.Path(lock_file).resolve()`, which follows a final symlink, and every connection opens that path through `sqlite3.connect()` with no `O_NOFOLLOW` or regular-file check. SQLite's own VFS also resolves the final symlink in user space, so `fs.protected_symlinks` and the sticky bit do not help: the kernel never sees a symlink to refuse. The other lock classes do not follow a final symlink: `FileLock` opens with `O_NOFOLLOW`, and `SoftReadWriteLock` coordinates through `<path>.rw/`. ### Proof of concept On Linux, with the victim and attacker as different users and `/shared` mode 1777 with `fs.protected_symlinks=1`: 1. The attacker runs `ln -s /home/victim/private/pwned.flag /shared/app.rwlock.db`. 2. The victim constructs `ReadWriteLock("/shared/app.rwlock.db")`. A plain `os.open(..., O_NOFOLLOW)` on the same path fails, but the lock creates `/home/victim/private/pwned.flag`, owned by the victim, inside a directory the attacker cannot write. 3. With `/shared/cache.rwlock.db -> /shared/a.db`, one victim process holds the write lock. The attacker repoints the link to `/shared/b.db`, and a second victim process acquires the write lock while the first still holds it. The same behavior reproduces on macOS, on Windows 11, and through `AsyncReadWriteLock`. ### Impact A local attacker with write access to the lock directory can make the victim create empty files it owns at paths the attacker cannot write, make the victim hold a SQLite lock on another application's database so that application gets `database is locked`, and give two victim processes the write lock at once, letting them corrupt the resource the lock protects. ### Workarounds Keep `ReadWriteLock` paths in directories that only trusted users can write. | GCVE Database | ||
| Medium | 4.7 | EPSS 0.1% | CVE-2026-112935virtualenv · filelock: SoftFileLock stale-lock break removes a live lock file on a reused inode number and admits two holders Medium4.7EPSS 0.1%GCVE DatabaseCVE-2026-112935 The stale-lock break shared by `SoftFileLock`, `MarkerSoftFileLock` and `SoftFileLease` (and their async variants, and a `FileLock` that has fallen back to a soft lock on `ENOSYS`, the "not implemented" error) recognises the marker it judged stale by two values. The marker is the lock file at `<path>`, whose presence says a process holds the lock. The values are its inode number (the kernel's identity for a file, independent of its name) and its modification time (mtime) not being newer. The check runs after the contender has already closed the marker, so nothing keeps the inode allocated. On a filesystem that hands a freed inode number straight to the next file (ext4, xfs, overlayfs on them) the next holder's marker can carry that number. A contender still between its read and its break then renames and unlinks the live marker. 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), forces this at will. It plants a marker with a stale owner record and a modification time in the future and unlinks it as soon as a contender has read it. Two processes of the user running the lock (the lock user below) then hold the same default-configured lock at once, and nothing raises or logs. Affected versions: filelock >= 3.22.0 and <= 4.2.0 (finding `ANT-2026-KIIS96IX`). ## 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, though one the other user starts, lengthens (by making the lock user's `/proc` read slow) and retries at every release. The scanner measured 10 to 26 overlaps per 20-second run. `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 on a sticky shared directory, ext4-style inode reuse and at least two contenders of the lock user. Our review verified it across UIDs (a UID is a user account's numeric id): the other user as uid 1001, the lock user's processes as uid 1000, BUG on 4.2.0 over ext4. The scanner rated it high (CWE-367). ## Scope and preconditions - The other user can create files in the lock's directory; a root-owned `1777` directory such as `/tmp` or `/var/tmp` is enough. It creates, `utime`s (sets the timestamps of) and unlinks only its own file. The sticky bit stops it removing the lock user's marker (checked: `EPERM`, operation not permitted), so the lock user's own stale break does the deletion. - The directory is on a filesystem that hands a freed inode number straight to the next file. ext4 does (six create/unlink rounds returned one number six times, in the image's overlayfs `/tmp` and on a loop-mounted ext4), and xfs does. tmpfs returned six increasing numbers and the same run there gave no overlap, and btrfs does not hand a freed number back. - The lock user has at least two processes contending for the lock, and the displaced holder is still inside when the next contender polls (the breaker itself comes back one `poll_interval`, 50 ms by default, later). - The lock is `SoftFileLock`, `MarkerSoftFileLock` or `SoftFileLease` (or async forms). The default `flock`-based `FileLock` joins them only after it has fallen back to `SoftFileLock` on `ENOSYS`. `StrictSoftFileLock` does not call `break_lock_file`, so it is not affected. - The same overlap also occurs with no other user involved, under heavy same-account contention, when a contender breaks a dead holder's marker and a successor creates its marker within the same timestamp tick (about once in 750 crashes with 64 workers on a 2-CPU machine, not on tmpfs). 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 re-check sits in [`break_lock_file`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_util.py#L113-L118). Its callers are [`SoftFileLock._try_break_stale_lock`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_soft.py#L107-L123), [`MarkerSoftFileLock._try_break_stale_lock`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_marker.py#L74-L81) and [`SoftFileLease._try_break_stale_l | GCVE Database | ||
| Medium | 6.1 | EPSS 0.2% | CVE-2026-112934virtualenv · filelock: SoftReadWriteLock holder refresh writes through a hard link and hangs on a named pipe that another local Medium6.1EPSS 0.2%GCVE DatabaseCVE-2026-112934 `SoftReadWriteLock` (and `AsyncSoftReadWriteLock`, which wraps one) refreshes its holder record (the file under `<path>.rw/holders/` that says a process holds or wants the lock) on every poll of a waiting acquire and on every heartbeat (the periodic refresh a holder runs to keep its record fresh). It does so by reopening `<path>.rw/holders/<token>` by name with a plain blocking `O_WRONLY | O_NOFOLLOW` open and writing at offset 0, without checking what it opened. `O_NOFOLLOW` makes the open fail only when the final name is a symbolic link (a file that redirects to another path). A different local user (the other user below) who owns the lock's `.rw` tree reads the token of the user running the lock (the lock user below) from the `holders/` listing and renames another node over the record. The other user owns the tree because it created it first in a sticky directory such as `/tmp` (the sticky bit is a directory flag that lets only a file's owner rename or remove it), or because it can write a lock directory without the sticky bit. A FIFO (a named pipe: a file whose open for writing blocks until some process opens it for reading) makes the lock user's `write_lock()` / `read_lock()` hang regardless of `timeout`. It also stops a lock holder's heartbeat thread without `on_compromise` (the callback that tells a holder it lost the lock) ever running, so the lock grants a second process while the first still believes it holds it. This works on a default Linux. Where the kernel lets a user hard-link a file it does not own (a hard link is a second name for the same file; Linux with `fs.protected_hardlinks=0`; macOS and the BSDs by their defaults, not executed), a hard link makes the lock user overwrite the first ~135 bytes of one of its own private files with its holder record. Affected versions: filelock >= 4.0.0 and <= 4.2.0. Anthropic's OSS Scanner reported it in private as `ANT-2026-AT33VEVC` [medium, CWE-62], titled "SoftReadWriteLock holder refresh writes through hard links, hangs on FIFOs". ## Severity **CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H, 6.1 (Medium).** `AV:L` (local): the other user needs an account on the same host. `AC:L` (low attack complexity): on a default Linux there is no race. `PR:L` (low privileges): a local user with ordinary privileges is enough. `UI:N` (no user interaction): the lock user's process acts on its own. `C:N` (no confidentiality impact): the other user learns nothing. `I:L` (low integrity impact): where the kernel permits hard links to files one does not own, the lock user also writes its holder record over the first ~135 bytes of one of its private files. The bytes are filelock's record, not chosen by the other user, so the change stays within that record. `A:H` (high availability impact): the other user makes a lock user's acquire outlast any `timeout` and leaves a holder's heartbeat thread asleep. The lock then evicts the holder without telling it, and two processes believe they hold the write lock. Classification from the external review: conditional security. The owner of a planted `.rw` tree can make the refresh open a FIFO and stall a lock user, including its heartbeat. Overwriting a file of the lock user through a hard link also needs a permissive hard-link policy. The scanner rated it medium. ## Scope and preconditions - Trust boundary: the other party is a different local account that owns the lock's `.rw` tree. Either it created `<path>.rw` (with `gen/` and `holders/`) before the lock's first use in a sticky directory such as `/tmp`, where `prepare()` adopts an existing tree whoever owns it and whatever its mode. Or the lock lives in a directory without the sticky bit that the account can write (the scanner checked a root-created `0777` directory). There being first is not needed, because the other user renames the lock user's own `0700` tree aside and puts its own in place; `prepare()` does not run again for a live lock object, and a waiting lock user recreates its record in the replacement. A tree the lock user already created in a sticky directory (mode `0700`) is out of reach. - FIFO variant: needs only that boundary. It works on a default Linux. `fs.protected_hardlinks` plays no part, and `fs.protected_fifos=1` (a Linux setting that refuses some opens of FIFOs in sticky world-writable directories) does not help, since it covers `O_CREAT` opens in sticky directories only, and the other user renames the FIFO into place inside its own `holders/`. - Hard-link variant: also needs a kernel that permits a hard link to a file the caller does not own. That is Linux with `fs.protected_hardlinks=0` (the kernel's built-in value; observed), while most distributions and the scanner's test image boot with `1`, under which the other user's `link()` fails with `EPERM` (operation not permitted) and the file stays untouched. macOS and the BSDs have no such restriction by default; that follows from their `link(2)` semantics, and the scanner did not execute it. The tar | GCVE Database | ||
| Medium | 5.5 | EPSS 0.2% | CVE-2026-112915virtualenv · filelock ReadWriteLock: a crafted lock database makes every acquire stall or hang regardless of timeout Medium5.5EPSS 0.2%GCVE DatabaseCVE-2026-112915 `ReadWriteLock` and `AsyncReadWriteLock` (the SQLite-backed reader/writer lock) run `PRAGMA journal_mode=MEMORY` on a fresh connection for every acquire. That statement makes SQLite load the whole schema (the `sqlite_master` table, which lists every table and index with its `CREATE` text) of whatever file sits at the lock path. The caller's `timeout` reaches SQLite only as `busy_timeout`, which bounds waits for another connection's lock, not schema loading. The only content checks reject non-regular files and WAL mode (write-ahead log, an SQLite mode that keeps changes in a side file). A local user who can place or overwrite the file at a `ReadWriteLock`'s database path can therefore make every `acquire_read` / `acquire_write` of every process using that lock ignore its `timeout` and `blocking=False`. That position needs a non-sticky shared directory, or a database owned by the user running the lock (below: the lock user) that other users may write. One 37 KB crafted file stalls each acquire for as long as its author chooses while the process allocates about 1 GB. A second 28 KB layout makes the acquire of a free lock run without end, spinning at 100% CPU and ignoring Ctrl-C. The effect is availability only: the other user reads nothing and corrupts nothing. Affected versions are filelock 3.20.4 through 4.2.0. Anthropic's OSS Scanner reported this in private as `ANT-2026-NFPRHMYJ` [medium, CWE-400]: ReadWriteLock: crafted lock database hangs acquire regardless of timeout. ## Severity **CVSS 3.1 `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H` scores 5.5 (Medium).** `AV:L` (local): the other user needs an account on the same host. `PR:L` (low privileges): an ordinary account is enough. `AC:L` (low attack complexity): the other user plants one file; no race. `UI:N` (no user interaction). `A:H` (high availability impact): every acquire of every process using the lock runs for as long as that user chose, or does not return, with `timeout` and `blocking=False` ignored and `SIGINT` not delivered, so only killing the process ends it. `C:N` and `I:N` (no confidentiality or integrity impact): the other user reads nothing and corrupts nothing. `S:U` (scope unchanged). Classification from the external review: **conditional security**, an availability concern only when an untrusted actor can supply the database file (it needs a writable or replaceable database path). The scanner rated it medium. The review agrees with the mechanism and the precondition. ## Scope and preconditions - The other party is a different local account that can create or replace the file at the lock path. One way: the lock directory is writable by that account and not sticky (the sticky bit is a directory flag that lets only a file's owner remove or rename it), for example a group-writable project or cache directory. There the account can be the first to create the database, or rename the lock user's database aside and put the crafted one in its place. The other way: the lock user's own database is writable by that account (`0666`), which is the setup a lock shared between accounts needs. In a `1777` directory the scanner wrote the crafted file over such a database in place, and the lock user's next acquire hung. The lock user must be able to open the file read-write, so a file the other user creates is `0666`. - Sticky directories and kernel settings. In a sticky world-writable directory such as `/tmp`, Linux with `fs.protected_regular=2` (a kernel setting) refuses the lock user's `O_CREAT` open of a file owned by the other account with `EACCES`. A database owned by the other user does not work there. That does not cover a `0666` database owned by the lock user. The scanner did not test systems with `fs.protected_regular=0` (the kernel's own default). - Platform. Every platform runs the same statements. On Windows the documentation already requires a lock directory that untrusted users cannot write, so the shared-directory setup is outside what the project supports there. - Ancestor boundary. Protection against another local user is possible only while no other user can replace the lock's directory or any of its ancestors. That holds when each ancestor belongs to the user or root and is either not writable by others or sticky. ## Affected code At the scanned commit `b831496b0273`: [`_configure_and_begin` runs `PRAGMA busy_timeout=...; PRAGMA journal_mode=MEMORY;` on a fresh connection](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_read_write.py#L814-L816), and for a read lock [`SELECT name FROM sqlite_master LIMIT 1` at line 832](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_read_write.py#L832). The code in [`_do_acquire_inner` closes the previous connection and opens a new one on every acquire](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_read_write.py#L745-L754). The only content checks | GCVE Database | ||
| Medium | 5.3 | EPSS 0.2% | CVE-2026-112914virtualenv · filelock: SoftFileLock stale-break restore follows a swapped-in symbolic link and hard-links a file of the user Medium5.3EPSS 0.2%GCVE DatabaseCVE-2026-112914 When a soft-lock stale break finds that the marker (the lock file at `<path>`) it renamed aside was a live one recreated by a successor, the restore branch of `break_lock_file()` checks the break name with `lstat` (a check that reads a name without following a symbolic link) and then calls `os.link(break_path, lock_file)` by name. On CPython 3.12 and later on Linux (and on macOS) that call follows a symbolic link (a file that redirects to another path). A local user (the other user below) who can write a shared lock directory without the sticky bit (a directory flag that lets only a file's owner rename or remove it) replaces the break name with a symbolic link between those two calls. The `SoftFileLock`, `MarkerSoftFileLock`, `SoftFileLease` or `lifetime` break of the user running the lock (the lock user below) then creates a hard link (a second name for the same file), in that directory, to any of the lock user's own files on the same filesystem. That includes files inside directories the other user cannot enter and links that `fs.protected_hardlinks` (a Linux setting that forbids hard links to files one does not own) refuses the other user when it asks itself. The link exposes the file's size, times, mode and owner, keeps the inode (the kernel's identity for a file, independent of its name) alive, and gives the other user a name it can move. On 4.2.0 a later `FileLock` of the lock user on that name truncated the file. Affected versions: filelock >= 4.1.0 and <= 4.2.0 (finding `ANT-2026-SB29HIAL`). ## Severity **CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:N, 5.3 (Medium).** `AV:L` (local): the other user needs an account on the same host. `AC:H` (high attack complexity): two short races against a few lines of Python, timed by inotify (a Linux facility that reports file events), and the lock user must take the lock again and again (the scanner won 10 runs out of 10 after 9 to 910 lock cycles). `PR:L` (low privileges): an unprivileged account is enough. `UI:N` (no user interaction): the lock user's process acts on its own. `C:L` (low confidentiality impact): the file's metadata through the new name, not its content. `I:H` (high integrity impact): the other user chooses the file, and whatever the lock user later does to names in that directory lands on it. On 4.2.0 the lock user's own `FileLock` truncated the 17-byte private file to 0 bytes and, with a permissive `mode`, would have changed its mode too. `A:N` (no availability impact): the lock keeps working. Classification from the review: security, conditional on a shared parent without the sticky bit, which crosses the protected-directory boundary. The scanner rated it high (CWE-363). This is an earlier-public-context finding. #786 added the restore branch and guarded a symbolic link already present when `lstat` runs, not one swapped in after it. ## Scope and preconditions - The other user can write the lock directory, and it has no sticky bit: a `0777` or group-writable cache or project directory. In a root-owned `1777` directory the unprivileged lock user cannot rename the other user's marker and only waits. In a `1777` directory the lock user owns, `fs.protected_symlinks=1` (a Linux setting that refuses to follow another user's symbolic link in a world-writable sticky directory) makes the `link` fail with `EACCES` (permission denied). Testing showed no effect in sticky directories. - The interpreter's `os.link` follows symbolic links. CPython 3.14 on Linux issues `linkat(..., AT_SYMLINK_FOLLOW)`. CPython 3.11 issues `link(2)`, which links the symbolic link itself, and is not affected. The scanner did not test 3.12 and 3.13. On macOS `link(2)` follows (confirmed in our review on macOS 27.0.1: a plain `os.link` of a symbolic link gave the target `st_nlink` 2, that is two names). - The target file and the lock directory are on the same filesystem (hard links cannot cross filesystems), and the lock user takes the lock more than once. - Every soft-lock path reaches the branch: `SoftFileLock`, `MarkerSoftFileLock`, `SoftFileLease` and the `lifetime` break. The native `FileLock` does not, but it is the consumer that turns the planted link into a truncation. 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, which no file-based lock can defend against. This finding is about what the lock itself adds inside such a directory, a hard link the other user cannot make on its own. ## Affected code At the scanned commit b831496, the restore branch of [`break_lock_file`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_util.py#L113-L122) runs for [`SoftFileLock._try_break_stale_lock`](https://github.com/tox-dev/filelock/bl | GCVE Database | ||
| Low | 2.5 | EPSS 0.2% | CVE-2026-112912virtualenv · filelock: Symlink swap during SoftFileLock's writability check leaks whether a path exists and is writable by the lock Low2.5EPSS 0.2%GCVE DatabaseCVE-2026-112912 `raise_on_not_writable_file()` is the check that `SoftFileLock` and the locks sharing its acquire path run before every attempt. It looks at the lock path with `os.lstat`, so that it judges a planted symlink (a file that points at another path) by itself. When the node has no owner-write bit, it then calls `os.access(filename, os.W_OK)`, which follows symlinks. A local user who can create entries in the lock's directory can swap the path between a mode-`0466` file and a symlink to a path of their choosing. The second call then reports on the symlink's target with the permissions of the user running the lock (below: the lock user). The lock user's `acquire()` ends in `PermissionError` within a fraction of a second when the target is missing or not writable by the lock user, and in the ordinary `Timeout` otherwise. That yields one bit per watched acquire. The bit says whether a path exists and is writable by the lock taker. It covers paths under directories the other user cannot search, or any path when root takes the lock. The lock does not open, create or modify anything through the symlink. On Linux, `fs.protected_symlinks` (a kernel setting that refuses to follow another user's symlink in a sticky world-writable directory) blocks it in a directory such as `/tmp`. It does not block it in a `0777` directory without the sticky bit (a directory flag that lets only a file's owner remove or rename it) or in a sticky group-writable one. This reopens the leak PR #567 closed. Affected versions are filelock 3.32.6 through 4.2.0. Anthropic's OSS Scanner reported this in private. Finding covered by this advisory: - `ANT-2026-RQ91AXTW` [medium, CWE-59]: Symlink TOCTOU in raise_on_not_writable_file leaks file existence. ## Severity **CVSS 3.1 `CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N` scores 2.5 (Low).** `AV:L` (local): the other user needs an account on the same host. `PR:L` (low privileges): an ordinary account is enough. `UI:N` (no user interaction). `AC:H` (high attack complexity): the swap has to land between two system calls a few bytecodes apart (6 to 12 % of back-to-back acquires with the other user on a second CPU, 1 in 8 000 on one CPU), and the other user has to observe how the lock user's acquire ended. `C:L` (partial confidentiality impact): one bit per acquire (existence or writability of a chosen path), no content. `I:N` and `A:N` (no integrity or availability impact). `S:U` (scope unchanged). Classification from the review: **security, conditional**, "a one-bit probe of a victim-only path" that "Linux protected symlinks block in the common sticky `/tmp` case, but not every shared directory". The scanner rated this medium. The maintainers score it low for the complexity and the one-bit yield. ## Scope and preconditions - The affected classes are `SoftFileLock` and everything that reuses its `_acquire`: `MarkerSoftFileLock`, `SoftFileLease`, the async soft locks, and `FileLock` after an `ENOSYS` fallback to a soft lock. `UnixFileLock` itself does not call the helper. `WindowsFileLock._acquire()` does, but on Windows `os.access` reads the link's own read-only attribute and does not follow it; that branch did not run. - The other user must be able to create entries in the lock's directory. With Linux defaults (`fs.protected_symlinks=1`) the kernel refuses the lock user's attempt to follow another user's symlink in a directory that is both sticky and world-writable. `/tmp` therefore gives `PermissionError` for every target and leaks nothing. A `0777` directory without the sticky bit, or a sticky group-writable one (`1770` with a shared group), has no such cover and leaks as described. macOS and the BSDs have no `fs.protected_symlinks`, and a symlink there has mode bits of its own, so one without the owner-write bit would reach `os.access` with no swap at all (not executed). - The swap is practical only with a second CPU for the other user. - The other user needs to see how the lock user's acquire ended, for example the exception in a shared log, an exit status, or when the job stopped (`PermissionError` ends a blocking acquire within about a second; the other case runs to its timeout). The scanner also observed a variant that needs no output from the lock user. Hold the soft lock, set the marker (the lock file whose presence says a process holds the lock) to mode `0o066`, swap the symlink in and out for a few seconds, release, and see whether a marker owned by the other uid appears. A contender still waiting takes the lock; one that raised is gone. - The `O_NOFOLLOW` open that follows the check is not affected. The lock does not read, create or modify anything through the symlink. ## Affected code Scanned commit `b831496b0273`: in `raise_on_not_writable_file`, the [`os.lstat` that states the invariant](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_util.py#L46-L50) and the [`os.access` call that follows symlinks](https://github.com/tox-dev/filel | GCVE Database | ||
| Medium | 6.3 | EPSS 0.1% | CVE-2026-112911virtualenv · filelock: StrictSoftFileLock and SoftReadWriteLock adopt a protocol directory another user created first, losing Medium6.3EPSS 0.1%GCVE DatabaseCVE-2026-112911 `StrictSoftFileLock`, `AsyncStrictSoftFileLock`, `SoftReadWriteLock` and `AsyncSoftReadWriteLock` keep their whole ownership state as files in a directory derived from the lock path (`<path>.filelock/claims`, `<path>.rw/{gen,holders}`). When that directory already exists they adopt it after checking only that it is a real directory, not who owns it. Another local user who can create names in the lock's directory can create the tree before the first acquire on that path by the user running the lock (below: the lock user). In `/tmp`, `/var/tmp` or another sticky shared directory any user can do that; the sticky bit is a directory flag that lets only a file's owner remove or rename it. The other user then owns the tree. They can unlink the lock user's claims while the lock user holds the lock, so a second process of the lock user, root included, enters the same critical section and neither process sees an error. Against `SoftReadWriteLock` the same position also admits two writers with `on_compromise` silent. It redirects the lock user's own creates and renames into a directory the other user picks (the scanner replaced a repository's `.git/HEAD` with a filelock record written with the lock user's privileges). It also keeps the lock user's acquire or release running past any `timeout`. Affected versions are filelock 3.30.0 through 4.2.0 (finding `ANT-2026-Q5VK5JEE`). ## Severity **CVSS 3.1 `CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H` scores 6.3 (Medium).** `AV:L` (local): the other user needs an account on the same host. `PR:L` (low privileges): an ordinary account is enough. `UI:N` (no user interaction). `AC:H` (high attack complexity): the other user must create the protocol directory before the lock user's first acquire on that path. That depends on the path being new, or on a reboot, a tmp cleaner or the documented `<path>.rw` recovery having removed its tree; once planted, the rest needs no race. `I:H` (high integrity impact): two holders write whatever the lock protects, and for `SoftReadWriteLock` the lock user's process creates, replaces or deletes files named `HEAD`, `epoch` and `0000000000000000000N` in any directory the lock user can write. `A:H` (high availability impact): the owner of `<path>.rw` can make the lock user's `read_lock(timeout=1)` spin at full CPU without end, cost 22 s per acquire, or pin 1 MiB of memory per planted file until a heartbeat thread dies of `MemoryError`. `C:N` (no confidentiality impact). `S:U` (scope unchanged). Classification from the review: **security, conditional** on the other user winning initial ownership of the protocol directory. The scanner rated it high (CWE-283). With `AC:L` the same impacts would score 7.1. ## Scope and preconditions - The other user can create names in the directory that holds the lock. In a sticky `1777` directory such as `/tmp` any local user can. The sticky bit stops that user from renaming or removing a directory the lock user created, but does nothing for one the other user created first. - The other user gets there first: `<path>.filelock` (strict) or `<path>.rw` (soft read-write) must not exist when the lock user's process first acquires. `_strict.py` does not remove the directory at any point, so a path the lock user has already used stays safe for as long as the tree exists. The exposure is a new lock path, or one whose tree a reboot, a tmp cleaner, a cache purge or the recovery described in `docs/how-to.rst` removed. - For the strict lock the planted directory must be writable by the lock user (`0777`); a planted `0755` directory makes the lock user's acquire fail with `PermissionError` instead. Planting only the outer `<path>.filelock` is enough. The lock user creates `claims` inside it, and the other user, as owner of the parent, renames that `claims` aside and creates an empty one. - The Linux `fs.protected_regular`, `fs.protected_symlinks`, `fs.protected_hardlinks` and `fs.protected_fifos` sysctls (kernel settings, all enabled in the scanner's image) do not interfere. They cover regular files, symlinks, hard links and FIFOs, not directories. The `SoftReadWriteLock` symlink redirect below also passes `fs.protected_symlinks=1`, because the symlink (a file that points at another path) sits in the other user's own non-sticky directory. - The affected classes are `StrictSoftFileLock` and `AsyncStrictSoftFileLock` (which inherits `_acquire`), and `SoftReadWriteLock` and `AsyncSoftReadWriteLock` with default options. `FileLock` gives way to the same position (when the other user creates the lock file first with mode `0666` and unlinks it during a hold, a second lock user process acquires), so the strict lock is not weaker than the native one here. This advisory reports it because `StrictSoftFileLock` is the lock documented as fail-closed and already raises `SoftFileLockProtocolError` for a symlink or a regular file at the same names, while it uses a directory that belongs to someone else without any signal. The `SoftReadWri | GCVE Database | ||
| Medium | 6.1 | EPSS 0.2% | CVE-2026-112909virtualenv · filelock: UnixFileLock truncates and chmods any regular file another user renames or hard-links onto the lock path Medium6.1EPSS 0.2%GCVE DatabaseCVE-2026-112909 `UnixFileLock`, the backend of `FileLock` and `AsyncFileLock` on Unix, empties whatever regular file it finds at the lock path once it holds the `flock` (a kernel lock on an open file). With an explicit `mode` it also chmods that file (sets its permission bits), whether or not this acquire created it. Another local user who can rename or hard-link one of the files of the user running the lock (below: the lock user) onto the lock path gets that file truncated by the lock user's next `acquire()`. A hard link is a second name for the same file. With a mode such as `0o666` the file, still owned by the lock user, also becomes readable and writable by that other user. The other user can then read and alter everything the lock user writes to it afterwards. `O_NOFOLLOW`, the defence added for the symlink variant (CVE-2025-68146), does not see a renamed file or a hard link. The rename route needs the file's directory to be writable without the sticky bit (a directory flag that lets only a file's owner remove or rename it). The hard-link route into a sticky directory such as `/tmp` needs `fs.protected_hardlinks=0`, a Linux kernel setting. Affected versions are every filelock release up to and including 4.2.0 (the truncation is as old as the project; the chmod since 3.10.2). Anthropic's OSS Scanner reported this in private. Finding covered by this advisory: - `ANT-2026-KO573NWI` [high, CWE-706]: UnixFileLock truncates and chmods victim files renamed onto the lock path. ## Severity **CVSS 3.1 `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N` scores 6.1 (Medium).** `AV:L` (local): the other user needs an account on the same host. `PR:L` (low privileges): an ordinary account is enough. `UI:N` (no user interaction). `AC:L` (low attack complexity): one rename or link, then wait for the lock user's next acquire; no race. `I:H` (high integrity impact): the lock user's own process truncates a file it did not mean to touch, including content behind a second name the other user could neither modify nor delete. With a permissive explicit `mode` the other user can then write to it. If that file is one the lock user later executes or trusts, that is code execution as the lock user. `C:L` (partial confidentiality impact): the old content is gone before the other user can read anything, but what the lock user writes afterwards through a descriptor (the number the kernel gives a process for an open file) it still holds is readable. `A:N` (no availability impact). `S:U` (scope unchanged). Classification from the review: **security, conditional** on another user controlling names in a non-sticky directory on the lock's filesystem (or on `fs.protected_hardlinks=0`). The scanner rated this high. The maintainers score it medium because the reachable targets depend on the directory layout and kernel settings described below. ## Scope and preconditions - Lock classes: `FileLock` and `AsyncFileLock` on Unix (`UnixFileLock`). `SoftFileLock` applies an explicit mode only to a file it created with `O_CREAT | O_EXCL` and does not truncate an existing file. `WindowsFileLock` has not truncated at any version. - The other user must put a regular file of the lock user at the lock path before the lock user's next acquire, while the lock file does not exist yet. That is the case on first use, after a reboot, after a `/tmp` cleaner, or after a release with the default `preserve_lock_file=False`, which unlinks the file. The scanner checked three ways at the scanned commit, with default kernel settings unless stated: - rename inside a directory without the sticky bit (a group-writable project or cache directory, a root-created `0777` directory); - rename from such a directory into a sticky one (`1777`) on the same filesystem. The sticky bit only protects entries that already exist, so the move succeeds while the lock path is free. Once the lock user's lock file exists there, the same move fails with `EPERM`; - `link(2)` into a sticky directory. This needs a system that lets a user hard-link a file it cannot write, which on Linux means `fs.protected_hardlinks=0`. Mainstream distributions set 1, which refuses the link. The scanner did not test macOS or the BSDs. - What the lock user loses: with the default `FileLock(path)` the lock truncates the file, which keeps its mode. With an explicit `mode` (a lock shared between users needs `0o666` or `0o660`, because `UnixFileLock` opens the file read-write), the lock also chmods the file to that mode. `_apply_explicit_mode` suppresses `PermissionError` only for files the lock user does not own. The lock user owns its own files, so the `fchmod` succeeds. - Trust boundary: the protection against another local user holds only when no other user can rename entries in the lock's directory or, for the rename-into-sticky route, in any directory on the same filesystem that holds a file of the lock user. A user who can rename entries in a non-sticky directory above the lock controls every name below it, which n | GCVE Database | ||
| Medium | 5.3 | CVE-2026-112908virtualenv · filelock: StrictSoftFileLock creates its claim directories without the lock mode, so another local user with a write Medium5.3GCVE DatabaseCVE-2026-112908 `StrictSoftFileLock` and `AsyncStrictSoftFileLock` apply the caller's `mode` to the sentinel (the lock file at `<path>`) and to both claim files (the files whose presence says a process wants or holds the lock). They create the two directories that hold the claims, `<path>.filelock` and `<path>.filelock/claims`, with a bare `mkdir()`. That gives `0o777` filtered through the umask (a per-process mask that removes permission bits from new files and directories) or, under a parent with a default ACL (an access control list on a directory that sets the permissions of entries created inside it), through that ACL. A hold consists of two claim names in that directory, which has no sticky bit (a directory flag that lets only a file's owner remove it). When the environment leaves another local user (the other user below) a write bit on the directory, that user can unlink the holder's `0600` claims. Three settings do this: umask `000` in a sticky directory such as `/tmp`, umask `002` with the other user in the directory's group, or a default ACL on the lock's parent that grants the group write (the kernel then ignores the umask). After the unlink, the next contender of the holder's account finds the directory empty and enters. The first holder keeps reporting `is_locked == True` and later releases with no error. Affected versions: filelock >= 3.30.0 and <= 4.2.0 (finding `ANT-2026-AD6SUOON`). ## Severity **CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:L, 5.3 (Medium).** `AV:L` (local): the other user needs an account on the same host. `AC:H` (high attack complexity): the holder must pass a restrictive `mode` while its umask or the parent's default ACL leaves the other user a write bit on new directories, and a contender of the holder's account must ask for the lock after the claims are gone. With the common umask `022` and no default ACL the directories are `0755`, and the kernel refuses the other user's unlink. `PR:L` (low privileges): an unprivileged account is enough. `UI:N` (no user interaction): the holder's processes act on their own. `C:N` (no confidentiality impact): the `0600` claim files stay unreadable. `I:H` (high integrity impact): two holders then write the data the lock protects, and neither learns of the other. `A:L` (low availability impact): the same write access lets the other user create names in `claims`, and an empty file named `held-v1-<32 zeros>.claim` makes the next `acquire()` raise `SoftFileLockProtocolError` ("malformed claim record") until someone removes it. Classification from the review: security, conditional on a lax umask or a permissive default ACL. The scanner rated it high (CWE-732). ## Scope and preconditions - The holder passes a restrictive `mode` such as `0o600`. Without one, the umask decides the permissions by documented design (the "File permissions and mode" section of `docs/concepts.rst` defines `mode` only for the lock file). Umask `000` with the default mode gives the same two-holder result but is outside this finding. - The holder's environment leaves the other user a write bit on new directories. The scanner measured three ways, with `mode=0o600` and the other user a member of group `team`: | lock's parent directory | holder's umask | `<path>.filelock` and `claims/` | who can unlink the claims | |---|---|---|---| | `1777`, like `/tmp` | `000` | `0777` | any other local user | | `root:team`, `3775` (setgid, so new entries inherit the group, and sticky) | `002` | `2775` | any member of `team` | | `root:team`, `1770`, default ACL `group:team:rwx` (`setfacl -d -m g:team:rwx`) | `077` | `0770` | any member of `team` | | `root:team`, `3775`, no ACL | `022` | `2755` | no one; the second process times out | The default-ACL row is the strongest. A holder with a private umask and `mode=0o600` still gets group-writable claim directories. The lock file in that row is `0600`, because its mode reaches `os.open` and masks the inherited ACL. - A contender that may enter asks for the lock: another process of the holder's account, or root. Nothing needs planting beforehand. The holder creates and owns the whole tree, so the existing-directory branch of `_ensure_protocol_directory` (the subject of GHSA-9c4x-gm8v-wgc4) plays no part. - Directories that an affected release already created keep their mode. A `0777` `<path>.filelock` from an earlier run stays writable by other users after an upgrade, because filelock does not remove these directories. Upgraders should `chmod` or remove existing `<path>.filelock` trees (see Workarounds). - The other lock classes do not have this gap in the same setting. With umask `000` and `mode=0o600`, `FileLock` and `SoftFileLock` create one `0600` file and nothing else, and `SoftReadWriteLock` creates its directories `0700` under any umask. Platform: POSIX permission semantics; demonstrated on Linux. filelock's checks against another local user hold only when no other user can replace the lock's directory or any of its ancestors. Tha | GCVE Database | |||
| Medium | 4.7 | EPSS 0.2% | CVE-2026-112906virtualenv · 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 | ||
| High | 7.5 | EPSS 0.3% | High7.5EPSS 0.3%GCVE DatabaseCVE-2026-114605 A vulnerability in dnspython's dns.name.from_wire_parser() function can cause excessive CPU usage when processing maliciously crafted DNS messages. This can degrade or deny service to applications that handle untrusted DNS wire format messages using dnspython. The issue is fixed in dnspython version 2.9.0. No workarounds are currently known. | GCVE Database | ||
| Medium | 6.5 | EPSS 0.3% | Medium6.5EPSS 0.3%GCVE DatabaseCVE-2026-116686 ### Summary In affected versions, when a connected MCP server declared an `outputSchema` for a tool, the SDK's client validated each `call_tool` result with `jsonschema`'s default resolver, which fetches any `$ref` it cannot resolve locally by opening the URL. A server could therefore put an `http://`, `https://` or `file://` reference in a tool's output schema and make the client: - request that URL, or read that local file, from the client's own network position on every call of the tool (the content was used only as a schema and never sent back to the server), and - stall: the fetch ran synchronously on the event loop with no timeout, so a target that accepted the connection and then went quiet froze every session in the process for as long as it held the connection. ### Am I affected? Yes, if your application uses the SDK as an MCP client (`ClientSession` over any transport, `ClientSessionGroup`, or the 2.x `Client`) and calls tools on servers you do not fully control. There was no setting that turned the validation off. Affected versions: 1.10.0 through 1.29.1, and 2.0.0 through 2.1.1. Not affected: applications that only build servers. If nothing in your code uses `ClientSession`, `ClientSessionGroup` or the 2.x `Client` class, you are not affected. ### Fix Upgrade to `mcp` 1.30.0 (1.x) or 2.2.0 (2.x) or later. The client now resolves references only within the schema document the server sent (plus the standard JSON Schema metaschemas), so result validation performs no network or file access. A tool whose output schema points outside its own document fails at call time with `RuntimeError` (`Invalid schema for tool ...`); in-document references such as `#/$defs/...` work as before. Until you can upgrade, call tools only on servers you trust. As a partial stopgap, skip tools whose `outputSchema` contains an `$id`, or a `$ref` / `$dynamicRef` that does not start with `#`. | GCVE Database | ||
| Medium | 5.9 | EPSS 0.4% | CVE-2026-116685snyk-agent-scan · Model Context Protocol Python SDK: HTTP client transports followed cross-origin redirects carrying custom headers and Medium5.9EPSS 0.4%GCVE 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 | 6.9 | EPSS 0.2% | CVE-2026-110860GitPython · Submodule.move() follows an attacker-committed intermediate symlink outside the worktree Medium6.9EPSS 0.2%GCVE 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 | EPSS 0.4% | CVE-2026-110859cruft · GitPython: arbitrary file read via attacker-controlled branch name in `Head.checkout()` (incomplete fix of High8.7EPSS 0.4%GCVE DatabaseCVE-2026-110859 GitPython versions up to 3.1.62 contain a vulnerability in the Head.checkout() method where an attacker-controlled branch name can be used to perform an arbitrary file read. This occurs because the branch name is passed as a positional argument to git checkout without proper sanitization or a separator, allowing injection of git options such as --pathspec-from-file. An attacker hosting a repository with a malicious default branch name can cause a consumer application that clones and checks out this branch to read arbitrary files accessible to the process, such as /etc/passwd. The issue is a partial regression of prior fixes and affects all published releases up to 3.1.62. The vulnerability has a CVSS 3.1 score of 7.5 (High). | GCVE Database | ||
| Critical | 9.2 | EPSS 0.5% | CVE-2026-110858Cruft: GitPython Remote.pull() refspec bypasses the --upload-pack unsafe-option guard, enabling arbitrary command Critical9.2EPSS 0.5%GCVE DatabaseCVE-2026-110858 GitPython's Remote.pull() method improperly handles the positional refspec argument, allowing an attacker to inject a --upload-pack option that bypasses the unsafe-option guard. This leads to arbitrary command execution with the privileges of the calling process when using local-path or SSH transports. The vulnerability affects versions from 2.12.0 up to but not including 2.16.0_17 and has a CVSS score of 8.1 (high severity). | GCVE Database | ||
| High | 7.7 | EPSS 0.4% | High7.7EPSS 0.4%GCVE DatabaseCVE-2026-110857 GitPython versions >=2.9.0 and <2.16.0_17 contain vulnerabilities where certain command wrapper methods omit checks for unsafe protocols and unsafe options. Specifically, the Git.ls_remote method does not check URLs for unsafe protocols, allowing potential remote code execution (RCE) if the victim's git transport policy permits the 'ext' protocol. The Repo.merge_base and IndexFile.move methods forward caller-controlled arguments to git commands without proper unsafe option checks, creating latent argument-injection surfaces. These issues were verified on the main branch post the latest release and are fixed in version 2.16.0_17. | GCVE Database | ||
| High | 8.7 | EPSS 0.3% | CVE-2026-110856Cruft: GitPython parse_actor_and_date() is vulnerable to quadratic backtracking on malformed identity lines High8.7EPSS 0.3%GCVE DatabaseCVE-2026-110856 Cruft's GitPython integration contains a regex vulnerability in the parse_actor_and_date() function that causes quadratic backtracking on malformed author, committer, or tagger identity lines missing or having malformed trailing epoch or timezone data. This results in significant CPU exhaustion when processing crafted commit or tag objects. The issue affects versions from 2.9.0 up to but not including 2.16.0_17 and is fixed in version 2.16.0_17. | GCVE Database | ||
| High | 7.1 | EPSS 0.3% | High7.1EPSS 0.3%GCVE 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 39 of 9,603