`SoftReadWriteLock` (and `AsyncSoftReadWriteLock`, which wraps one) checks once per lock object, by name, that `<path>.rw`, `<path>.rw/gen` and `<path>.rw/holders` are real directories and not symbolic links (files that redirect to another path). It then keeps only a boolean. Every later record operation resolves the full path again, and `O_NOFOLLOW` (a flag that makes an open fail when the final name is a symbolic link) protects the last component only. Another local user (the other user below) who can write the directory holding the lock, or who created `<path>.rw` 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), can put a tree under that name whose `gen` or `holders` is a symbolic link into a directory of the user running the lock (the lock user below).
A long-running process of the lock user that has already passed the check then creates, renames over and deletes files there with the lock user's privileges, including in `0700` directories the other user cannot open. The names and bytes are filelock's own lock records, not chosen by the other user. The scanner's reproducer replaces `.git/HEAD` in the lock user's private repository so that `git status` fails. There is no race. Affected versions: filelock >= 4.1.0 and <= 4.2.0. Anthropic's OSS Scanner reported it in private as `ANT-2026-WXXRI4M4` [high, CWE-367], titled "SoftReadWriteLock follows gen dir swapped for a symlink after first acquire".
## Severity
**CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N, 3.3 (Low).**
`AV:L` (local): the other user needs an account on the same host. `AC:L` (low attack complexity): the swap happens between two acquisitions, with no race to win. `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): the lock user's process replaces a file named `HEAD`, creates `epoch` and generation records, and deletes files whose names match the protocol's patterns, in any directory the lock user can write, with the lock user's privileges. The other user does not choose the bytes, but the files are gone and the chosen directory (a git repository) stops working. `A:N` (no availability impact): the lock itself keeps working and the lock user's process does not stall. The scanner rated it high; we scored the vector for a lock user with ordinary privileges.
Classification from the external review: conditional security. It is a cross-user replacement of a cached `.rw` tree that redirects later writes of the lock user through an intermediate symbolic link. It needs a planted tree in sticky `/tmp` or a writable parent without the sticky bit. Scoring note: we rate integrity low rather than high. The bytes written through the swapped tree are filelock's own records, not content the other user chose, so the lower reading of the CVSS integrity metric applies.
## Scope and preconditions
- Trust boundary: the other party is a different local account with write access to the lock's parent directory. Without the sticky bit (a `0777` scratch or spool directory, or a group-writable project or cache directory shared with that account's group) write access alone is enough. The other user renames the lock user's own `0700` `<path>.rw` aside and puts its tree in place. In a sticky parent such as `/tmp` the extra precondition is that it created `<path>.rw` (with real `gen` and `holders` directories, mode `0777`) before the lock user's first acquire. `prepare()` accepts an existing tree whoever owns it, and the other user later replaces its own `gen` with a symbolic link. `fs.protected_symlinks=1` (a Linux setting that refuses to follow another user's symbolic link in a world-writable sticky directory) does not intervene, because the link sits inside the other user's own non-sticky `<path>.rw`, not in the sticky parent (the scanner ran with that setting).
- Lock user: any process that keeps one `SoftReadWriteLock` or `AsyncSoftReadWriteLock` object (a singleton per path by default) and acquires it more than once, or is waiting for it when the swap happens. A fresh process that meets the planted tree gets `RuntimeError`. Between two acquisitions there is no timing to win.
- Targets: any directory the lock user can write, on any filesystem. The other user needs only to know its path (`/home/victim/project/.git` in the reproducer, under a `0700` home directory the other user cannot open).
- Platform: observed on Linux. The code path is the same on every POSIX platform and on Windows, where a junction (a Windows directory that points at another directory) at a directory component plays the symbolic link's part. The scanner ran Linux only.
- 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
`ReadWriteLock` and `AsyncReadWriteLock` (the SQLite-backed reader/writer lock) hand SQLite a name they do not control. On Linux the lock validates its database through a descriptor (the number the kernel gives a process for an open file). It then connects SQLite to `/proc/self/fd/<n>`, a Linux path that names the process's own open file `n`. SQLite turns that path back into a plain path and reopens it by name, following symlinks (a symlink is a file that points at another path). On every POSIX route SQLite also acts on `-journal`, `-wal` and `-shm` files it finds beside that name. Those are SQLite's side files: the rollback journal (pages saved to undo an interrupted write), the write-ahead log or WAL (changes kept outside the main file), and the shared-memory index for the WAL.
Another local user who can create or rename entries in the directory that holds the lock database can turn this against the user running the lock (below: the lock user). Such a 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). That other user can make the lock user's ordinary `read_lock()` or `write_lock()` write bytes of the other user's choosing into the lock user's own files. The same position lets the other user make that call create or unlink files of the lock user, admit a reader beside a writer, or kill the holder. All of this runs with the lock user's privileges. In a sticky directory such as `/tmp` a planted `-wal` file makes every acquire fail. Affected versions are filelock 3.20.4 through 4.2.0 (the `/proc/self/fd` redirect since 4.0.9, the side-file variants since 3.20.4).
Anthropic's OSS Scanner reported this in private as `ANT-2026-KMJ2575J` [high, CWE-363]: ReadWriteLock: SQLite still follows planted symlinks, overwriting files.
## Severity
**CVSS 3.1 `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H` scores 7.1 (High).** `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): the planted-journal variant needs no race, and the redirect won 77 of 77 attempts. `I:H` (high integrity impact): the other user writes bytes of their choosing into files of the lock user and unlinks files of their choosing. `A:H` (high availability impact): the other user makes every acquire fail or kills the lock holder. `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 effect that exists only when another user can change names in the lock database's directory. The review notes that the 77/77 inotify result (inotify is a Linux interface that reports file events) is stronger evidence than a theoretical TOCTOU (a time-of-check to time-of-use race). The scanner rated it high. The review accepts the mechanism and narrows it to that precondition.
## Scope and preconditions
- The other party is a different local account that can create or rename entries in the directory that holds the lock database. That is a `0777` directory or a group-writable one (including a setgid `2775` project directory shared with that account), in each case without the sticky bit. The lock user's lock must be able to open the database read-write, so a database the other user creates is `0666`. filelock itself creates the database `0600`.
- Sticky directories (`/tmp`, `/var/tmp`, any `1777` directory) narrow the finding rather than exclude it. The redirect and the journal replay do not reach a database the lock user owns there (tested with `fs.protected_regular=2`). What remains is a planted non-empty `<db>-wal` (any mode; a directory or FIFO works too) that the lock user cannot unlink. It makes every acquire raise `sqlite3.OperationalError: disk I/O error`. A hard link (a second name for the same file) to a file of the lock user at the lock path also remains, if `fs.protected_hardlinks=0`.
- Two kernel settings matter. `fs.protected_regular` (1 or 2 is the systemd default on most distributions; the kernel's own default is 0) keeps a user from opening a file another user owns in a sticky world-writable directory. Where it is 0, the other user can own the database in `/tmp`, and SQLite follows a planted symlink itself (not tested). `fs.protected_hardlinks=1` (most distributions; kernel default 0) blocks a hard link of a lock user's file onto the lock path. Neither setting affects a non-sticky shared directory.
- Platform and route. The `/proc/self/fd` redirect is Linux with `/proc` mounted, 4.0.9 and later. The side-file variants (`-journal`, `-wal`, `-shm`) exist on every POSIX route. On the hard-link route (macOS since 4.1.1 for every connection, Linux without `/proc`) they need the link-directory swap described under "Mechanism", so a non-sticky parent. N
`SoftFileLock`, `MarkerSoftFileLock`, `SoftFileLease`, their async variants and a `FileLock` that has fallen back to a soft lock on `ENOSYS` decide from the record inside the marker file whether its owner is gone. The marker is the file whose presence says a process holds the lock. They do not check who could have written that record.
A local user who can write the marker but cannot remove it rewrites the holder's record to name a dead PID (process id). That position arises with an explicit `mode` with group or other write bits, or the default mode under a lax umask (the process setting that strips permission bits from new files), in a sticky or otherwise non-removable directory. The sticky bit is a directory flag that lets only a file's owner remove or rename it. The next contender running as the holder's account, or root, judges the lock stale, removes the live marker on that user's behalf and acquires it. Two processes then run the critical section together and neither gets an error. Affected versions are filelock 3.22.0 through 4.2.0 (finding `ANT-2026-W1H9NXUH`).
## Severity
**CVSS 3.1 `CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N` scores 4.7 (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 marker must carry group or other write bits (an explicit permissive `mode`, or umask `002`/`000` with a shared group), and a contender of the holder's account must ask for the lock while the forged record is in place. `I:H` (high integrity impact): two holders then write whatever the lock protects, and the holder is not told. `C:N` and `A:N` (no confidentiality or availability impact). `S:U` (scope unchanged). Classification from the review: **security, conditional** on another user being able to write the live marker. The scanner rated it high (CWE-345). The default mode under umask `022` is not affected.
## Scope and preconditions
- The other user can write the marker. The application passes a `mode` with group or other write bits, which `fchmod` applies as given since 4.1.0. `0o666` is a common choice for a lock shared between accounts, although a soft-lock contender of another account waits as well on a `0o644` marker, since contenders only read and rename. Or the application uses the default mode (`0o666` through the umask) under umask `002` in a directory whose group the other user belongs to, or under umask `000`. The default mode under umask `022` is not affected.
- The other user cannot remove the marker itself: the directory is sticky, or the other user cannot write it at all (a `0755` directory owned by the lock's user gives the same overlap). In a non-sticky shared directory the other user can unlink the marker without filelock, and filelock adds nothing.
- A contender that may rename the marker asks for the lock: another process of the holder's account (several workers of one user) or root. A contender of another unprivileged account cannot rename the marker in a sticky directory and times out.
- The finding reaches every class that publishes a soft marker: `SoftFileLock`, `MarkerSoftFileLock`, `SoftFileLease`, their async variants, and `FileLock(mode=0o666)` on a filesystem where `flock` (a kernel lock on an open file) fails with `ENOSYS`.
These protections against another local user hold only when 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. 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.
## Affected code
At the scanned commit b831496: the create mode in [`SoftFileLock._acquire`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_soft.py#L67-L71), the explicit-mode `fchmod` in [`_apply_explicit_mode`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_soft.py#L100-L105), the trusting verdict in [`_try_break_stale_lock`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_soft.py#L107-L123) and the [`owner_is_stale`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_identity.py#L64-L79) it relies on; the same call in [`MarkerSoftFileLock`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_marker.py#L74-L81) and [`SoftFileLease`](https://github.com/tox-dev/filelock/blob/b831496b0273ecd11bf02b8d080ca8b673a538c1/src/filelock/_lease.py#L229-L256).
## Introduced
The stale verdict that trusts the record came in with e35c3afac473 ("feat(soft): detect and break stale locks", #476, 2026-02-13), first released in 3.22.0. From then on a marker left group- or other-writable by the umask is forgeable. The re
A vulnerability in filelock versions >=1.31.0_11 and <1.31.0_12 allows a local user with write access to a shared directory to swap a symbolic link in place of a lock directory between a check and subsequent operations. This can cause the lock user to create claim files in an attacker-chosen directory, potentially disrupting other locks by causing stranded claims that lead to denial of service until manually cleared. The issue requires a race condition and specific directory permissions, and has medium severity with a CVSS score of 5.8.
The filelock library versions 4.1.0 through 4.2.0 contain a vulnerability in the ReadWriteLock and AsyncReadWriteLock mechanisms where private .filelock-* directories are deleted recursively without verifying the directory's identity. This can be exploited by another local user who can rename directories in the shared parent directory (without sticky bit protection) to cause the lock user's process to delete arbitrary directories with the lock user's permissions. The issue affects macOS since version 4.1.1 and POSIX systems without a usable /proc/self/fd, such as chroot or containers without /proc mounted. The vulnerability has a CVSS 3.1 score of 6.1 (Medium) and results in high integrity impact due to unauthorized deletion of directory trees.
The ReadWriteLock and AsyncReadWriteLock classes in the affected software resolve lock file paths by following symlinks, which allows a local attacker with write access to the lock directory to manipulate the locking mechanism. This can cause the victim process to create or write files at unintended locations, break mutual exclusion between processes, or cause database locking conflicts. The issue affects versions from 1.31.0_9 up to but not including 1.31.0_11 and has been fixed in version 1.31.0_11.
A vulnerability in the filelock library versions 3.22.0 through 4.2.0 allows a local user with write access to a sticky shared directory (e.g., /tmp) on ext4 or xfs filesystems to cause two processes of the lock owner to simultaneously hold the same lock. This occurs due to inode reuse and a race condition in the stale-lock break logic of SoftFileLock and related classes, leading to integrity violations without confidentiality or availability impact.
CVE-2026-112934 describes a vulnerability in filelock versions >=1.31.0_11 and <1.31.0_12 where the SoftReadWriteLock holder refresh mechanism can be manipulated by a local user who owns the lock's .rw directory tree. This user can cause the lock user's process to hang indefinitely by placing a FIFO (named pipe) in the holders directory, blocking write operations and preventing heartbeat refreshes. Additionally, on systems that permit hard links to files not owned by the user (e.g., Linux with fs.protected_hardlinks=0), the attacker can cause the lock user to overwrite part of one of its own private files. The vulnerability has no confidentiality impact but can cause integrity degradation and high availability impact by allowing two processes to believe they hold the write lock simultaneously. A fix is available in version 1.31.0_12.
A vulnerability in the filelock library's ReadWriteLock and AsyncReadWriteLock components allows a local user with write access to the lock database file path to cause every acquire operation to stall or hang indefinitely, ignoring timeout and blocking settings. This is achieved by placing a crafted SQLite database file at the lock path, which causes the lock acquisition to consume excessive CPU or memory resources, resulting in denial of service. The issue affects filelock versions 3.20.4 through 4.2.0 and has no impact on confidentiality or integrity, only availability.
A vulnerability in filelock versions 4.1.0 through 4.2.0 allows a local unprivileged user who can write to a shared lock directory without the sticky bit to exploit the soft-lock stale break restore mechanism. This flaw enables the attacker to cause the lock process to create a hard link to one of the lock user's own files, exposing file metadata and allowing the attacker to rename the link. On version 4.2.0, this can lead to truncation of the targeted file by the lock user. The issue arises due to symbolic link following behavior in os.link on CPython 3.12+ on Linux and macOS. The vulnerability requires specific conditions including a writable non-sticky directory shared by users on the same filesystem. The CVSS 3.1 score is 5.3 (Medium).
A symlink swap vulnerability in filelock's SoftFileLock writability check allows a local user to leak one bit of information about whether a path exists and is writable by the lock user. This occurs due to a time-of-check to time-of-use (TOCTOU) race condition involving os.lstat and os.access calls that handle symlinks differently. The vulnerability affects filelock versions 3.32.6 through 4.2.0 and requires the attacker to have write access to the lock directory and the ability to observe the lock user's acquire outcome. Linux kernel setting fs.protected_symlinks mitigates this in typical sticky world-writable directories like /tmp, but not in others. The issue is rated low severity with a CVSS score of 2.5.
A vulnerability in filelock's StrictSoftFileLock and SoftReadWriteLock classes allows a local unprivileged user to pre-create a protocol directory in a shared sticky directory (e.g., /tmp) before the lock user acquires it. This causes the lock implementation to adopt a directory owned by the other user, enabling them to interfere with lock claims and potentially cause multiple processes to enter the same critical section simultaneously. The issue affects filelock versions 3.30.0 through 4.2.0 and is rated medium severity with CVSS 6.3. The vulnerability requires local access and a race condition where the attacker creates the directory first. It impacts integrity and availability but not confidentiality.
UnixFileLock, used by FileLock and AsyncFileLock on Unix, truncates and changes permissions of any regular file that another local user renames or hard-links onto the lock path before the lock user's next acquire. This can lead to unintended file truncation and permission changes, allowing the other user to read and modify the file contents thereafter. The vulnerability affects all filelock releases up to and including 4.2.0, with chmod behavior introduced since 3.10.2. Exploitation requires local access and the ability to rename or hard-link files in directories without the sticky bit or with specific kernel settings. The issue is fixed in version 1.31.0_12.
The filelock library versions >=1.31.0_11 and <1.31.0_12 have a vulnerability in StrictSoftFileLock and AsyncStrictSoftFileLock where claim directories are created without proper restrictive permissions. This allows another local user with write access to the directory to unlink claim files, causing multiple processes to believe they hold the lock simultaneously. This leads to integrity issues where two holders can write protected data concurrently without detection. The vulnerability requires specific conditions involving umask or default ACLs that leave the directories writable by other users. The issue is fixed in version 1.31.0_12.
A vulnerability in filelock versions 4.1.0 through 4.2.0 allows a local unprivileged user to cause a lock file to be evicted prematurely by exploiting inode reuse and coarse clock timestamp manipulation on ext4 or overlayfs filesystems. This can lead to two processes simultaneously entering a critical section protected by the lock, causing high integrity impact without affecting confidentiality or availability.
## 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)
CVE-2025-68146 is a Time-of-Check-Time-of-Use (TOCTOU) race condition in the filelock library affecting Unix, Linux, macOS, and Windows. It allows local attackers with filesystem access and symlink creation ability to exploit a race window during lock file creation, causing arbitrary user files to be truncated or corrupted via symlink attacks. This impacts dependent software such as virtualenv, PyTorch, poetry, and tox. The vulnerability is fixed in filelock version 3.20.1 by preventing symlink following during file open operations.