Live › vulnerability › CVE-2026-112937
Go to dashboardfilelock's ReadWriteLock and AsyncReadWriteLock recursively delete a remembered .filelock-* directory without checking it is still the one they created. A local user with write access to a shared, non-sticky parent directory can rename a victim's directory onto that name, so the lock user's process deletes the victim's tree with the lock user's permissions.
Summary written from the sources below. Check the originals before acting.
First seen by InfraStack 2026-10-23 21:57 UTC. All times on this page are UTC.
Teams that run ReadWriteLock or AsyncReadWriteLock from filelock 4.1.0 through 4.2.0 on macOS or on POSIX processes without a usable /proc/self/fd, such as chroots and containers without /proc. The risk needs a shared parent directory writable by another local account and without the sticky bit; stock Linux with /proc mounted is not affected per the sources.
| Product | Ecosystem | Affected | Fixed in |
|---|---|---|---|
| tox-dev filelock | vendor | >= 4.1.0, < 4.2.1 | 4.2.1 |
| filelock | pypi | >= 4.1.0, <= 4.2.0 | 4.2.1 |
python -c "import filelock; print(filelock.__version__)" and check whether the version is between 4.1.0 and 4.2.0 inclusive.0777 or group-writable without the sticky bit, for example ls -ld <lock-dir>./proc/self/fd is missing in the process (chroot or container without /proc).4.2.1 (fixed in 4.2.1 per the sources).chmod +t <dir>).Each row is the first time that source carried this signal. The analyst’s rows are its own findings.
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-*.
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.
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./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.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 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 branch at line 984 and . The reproducer runs the same Python code on Linux with hidden, which takes root and is test scaffolding, not a step the other user performs. Windows does not take this route.At the scanned commit b831496b0273: _ForkSafeConnection.close runs shutil.rmtree on the remembered mkdtemp path. The path comes from retain_until_close, set on the link route in _connect_through_descriptor, where descriptor_usable is False on macOS. Three more rmtree sites sit in _link_beside (lines 1023 and 1026) and the connect's finally (line 1011). The fifth is in _sweep_links_of_the_dead, with the name parser and .
The link directory moved beside the database in 015d27c ("fix(read-write): reject WAL databases and link beside the db (#805)"), and the dead-owner sweep came with df06bbd (#815). Both were first released in 4.1.0 (verified: git tag --contains gives 4.1.0 for both; 4.0.11 and 4.0.12 made the link in TMPDIR). macOS takes the link route for every connection since 5fd0c8f (#820), first released in 4.1.1 (verified).
When the lock connects through its private hard link, it disposes of .filelock-* directories with shutil.rmtree on a path. Nothing ties that path to the directory filelock created. mkdtemp makes the name unpredictable and the directory 0700. The name is in the parent's listing as soon as it exists (ls -a), and renaming it needs write permission on the parent only. 0700 keeps the other user out of the directory; it does not stop a rename of the directory. There are two ways in, with the same preconditions.
On release. A connection lives for one acquisition: _open_for_acquisition opens it and release() reaches close through _release and _finish_connection. The code trusts the path from the acquire to the release, for as long as the application keeps the lock. While the lock user holds write_lock() on /tmp/shared-cache/cache.db, the directory lists .filelock-<pid>--<host digest>-<random>/ (0700, owned by the lock user, containing lock.db). The other user runs two renames: that directory to moved-aside, t
This page as .md · .json · in your agent: get_signal("CVE-2026-112937")
NVD data is provided by NIST and is public domain · GitHub Advisory Database, CC-BY 4.0. Posts quoted belong to their authors.
.filelock-*descriptor_usabledocs/how-to.rst/proc0700 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.