Repository navigation
chore(deps): update dependency filelock to v4.2.6 [security] - #1294
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
Contributor
Author
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
renovate
Bot
force-pushed
the
renovate/pypi-filelock-vulnerability
branch
3 times, most recently
from
November 13, 2026 10:52
b6b535e to
51e0e6f
Compare
renovate
Bot
force-pushed
the
renovate/pypi-filelock-vulnerability
branch
from
November 13, 2026 12:11
51e0e6f to
be7509d
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This PR body was truncated due to platform limits.
This PR contains the following updates:
4.2.5→4.2.64.2.4→4.2.53.32.4→4.2.1filelock: ReadWriteLock follows a symlink at the lock path into SQLite
CVE-2026-112936 / GHSA-j8f7-rjxc-mr56
More information
Details
Summary
ReadWriteLockandAsyncReadWriteLockresolve the lock path withpathlib.Path.resolve()and pass it tosqlite3.connect()without refusing a symlink. A local user who can create names in the lock's directory, such as/tmpor 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__storespathlib.Path(lock_file).resolve(), which follows a final symlink, and every connection opens that path throughsqlite3.connect()with noO_NOFOLLOWor regular-file check. SQLite's own VFS also resolves the final symlink in user space, sofs.protected_symlinksand the sticky bit do not help: the kernel never sees a symlink to refuse. The other lock classes do not follow a final symlink:FileLockopens withO_NOFOLLOW, andSoftReadWriteLockcoordinates through<path>.rw/.Proof of concept
On Linux, with the victim and attacker as different users and
/sharedmode 1777 withfs.protected_symlinks=1:ln -s /home/victim/private/pwned.flag /shared/app.rwlock.db.ReadWriteLock("/shared/app.rwlock.db"). A plainos.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./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
ReadWriteLockpaths in directories that only trusted users can write.Severity
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
filelock: On Windows an oplock on a soft lock's marker holds SoftFileLock.acquire() past its timeout and blocks the stale break
CVE-2026-112907 / GHSA-567q-5qvw-pf92
More information
Details
On Windows the soft locks (
SoftFileLock,MarkerSoftFileLock,SoftFileLease, their async forms, and aFileLockthat fell back to a soft lock) read their marker by path. The marker is the lock file whose presence says a process holds the lock. Eachacquire()attempt that finds a marker opens it withos.opento read the holder's process id, and the lock renames and deletes a marker it judges stale, by path. On Windows each of these calls waits for an oplock. An oplock (opportunistic lock) is a request by a process that holds a handle on a file to be told before another process opens, renames or deletes that file; the other process waits until the holder answers the break or closes its handle.Another local account that can open the marker for reading can take a Level 1 oplock on it while no process holds it open, and leave the break unanswered. Every
acquire()by the user running the lock (below: the lock user) then waits inside the open, past itstimeout,blocking=Falseandcancel_check, and a stale marker under that oplock is not broken. Mutual exclusion holds and the lock touches no other file. Affected versions are filelock 3.22.0 through 4.2.0 on Windows. The maintainers found this while reviewing the fix for the report Anthropic's OSS Scanner sent on 15 October 2026 (review round 4, finding F4); it is not among the scanner's findings.Severity
CVSS 3.1
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:Lscores 2.5 (Low).AV:L(local): the other account needs a logon on the same host.AC:H(high attack complexity): Windows grants a Level 1 or batch oplock only to the sole open handle on a file, so the marker must have no holder handle when the other account asks. That account cannot bring this about; it waits for a holder that died, closed its descriptor, or left a marker past itslifetime.PR:L(low privileges): an ordinary account with read access to the marker through the directory's inherited ACL (access control list).UI:N(no user interaction).S:U(scope unchanged).C:NandI:N: that account can read the marker's content (process id and host name) anyway, and mutual exclusion holds.A:L(partial availability impact): one thread, or the event loop withrun_in_executor=False, waits until the holder answers; 10 s per attempt in the two-account runs, no crash. Metrics in doubt take the lower reading.Classification from the review: security, conditional on an unheld marker that another account can open.
Scope and preconditions
SoftFileLock,MarkerSoftFileLock,SoftFileLease,AsyncSoftFileLockand the other async soft locks, andFileLockafter itsENOSYSfallback to a soft lock.WindowsFileLockopens its lock file on a separate code path that this advisory does not cover.os.open, which on Windows applies no more ofmodethan the read-only attribute (GHSA-8mx5-7m5m-r77g). The marker carries the lock directory's inheritable entries. Another account that those entries grant read access to new files can open the marker with a read handle that shares read, write and delete.lifetimehas passed. While the holder keeps its handle, the request fails.DeviceIoControland never acknowledges the break. Windows cancels a thread's pending request when the thread exits, so the holder thread stays alive. A same-account process that answers slowly, such as an indexer, has the same effect for its answer time.SoftFileLock.acquire(timeout=1)returnedTimeoutafter 10.02 s with the second account's handle. In the same-account probeos.openwithO_RDONLYwas still running after 15 s and returned once the holder closed. The retry loop consultstimeout,blocking=Falseandcancel_checkbetween attempts only. WithAsyncSoftFileLock(run_in_executor=False)the event loop waits.break_lock_filerenames and unlinks by path. Both calls wait in the same way, so a stale marker under such an oplock stays, and no process acquires the lock until the holder closes.Affected code
Scanned commit
b831496b0273: theos.openin_read_lock_file(function), called from_try_break_stale_lockon every attempt that finds a marker, and from_unprobeable_holder,pidandis_lock_held_by_us.break_lock_filerenames at L116 and unlinks at L118 and L122, by path._poll_until_acquiredand_check_give_uptest the deadline,blockingandcancel_checkonly between attempts.Introduced
e35c3af "feat(soft): detect and break stale locks" (#476, 2026-02-13), first tag 3.22.0 (verified with
git tag --contains). Before it a soft lock created its marker withO_CREAT | O_EXCLand never opened an existing one. #476 read the marker on Windows throughCreateFileWand broke a stale one withPath.renameandPath.unlink. These wait for an oplock asos.opendoes, because none of them passesFILE_COMPLETE_IF_OPLOCKED, anNtCreateFileoption that returns instead of waiting. The read became theos.openin_read_lock_filewith bfbfa76 (#548, 3.29.1) and the break moved intobreak_lock_filewith d244c6d (#551, 3.29.2); the by-path calls stayed.Mechanism
Each attempt of
_acquirefirst callsos.open(path, O_WRONLY | O_CREAT | O_EXCL). On an existing marker that fails withEEXIST(orEACCESon Windows) at once; the same-account probe shows the failed exclusive create returning at once under an oplock too._acquirethen calls_try_break_stale_lock, which calls_read_lock_file:os.lstat, thenos.open(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK). On WindowsO_NOFOLLOWandO_NONBLOCKdo not exist (the code reads them withgetattr(os, ..., 0)), andos.opencallsCreateFileW. When another handle holds a Level 1 or batch oplock on the file, that call sends the holder a break request and returns only when the holder acknowledges it or closes its handle._check_give_uptests the deadline after the attempt returns, so the wait overrunstimeout,blocking=Falseandcancel_checkby its length. Once the open returns, the attempt ends and_check_give_upraisesTimeout, because the deadline has passed.A marker whose holder is gone takes the second path.
break_lock_filerenames it to a break name and unlinks it, by path. On Windows the rename and the delete open the file with delete access, which breaks a Level 1 or batch oplock the same way; the same-account probe showsos.renameandos.unlinkstill running after 15 s and returning once the holder closed. Thelifetimebreak at L123 runs the same function. A stale marker under such an oplock is therefore not removed, and every attempt by every process waits inside the read. The other account can request the oplock again as soon as the lock user's handle closes, between two attempts or twoacquire()calls, as in the Linux lease case of GHSA-7hmp-gf99-57hx. Nothing in the retry loop bounds this wait.Reproduction
The review's reproducers, quoted as received.
oplock_vm.pysection[2]is this finding; sections[1a]and[1b]belong to therelease()advisory.oplock_semantics_vm.pyis the same-account probe of which path operations wait. They ran on a Windows 11 VM (NTFS, CPython 3.14.8) against the head of PR #835 at3e4c5ef, where_read_lock_fileandbreak_lock_fileare the same code as atb831496b0273apart from import changes. The second accountfltesteris a member ofUsers; the directory grants it(OI)(CI)(M).oplock_vm.pyRecorded output of section
[2]:oplock_semantics_vm.pyThe review kept this probe's result as a sentence rather than its printed lines: under a same-account Level 1 or batch oplock,
os.open(path, os.O_RDONLY),os.renameandos.unlinkwere still running after 15 s and returned only once the holder closed its handle;os.utimeby path and a failedO_CREAT | O_EXCLcreate returned at once.Failure sequence
sequenceDiagram participant A as other local account participant M as soft.lock marker, no handle open participant L as lock user: SoftFileLock(path).acquire(timeout=1) participant W as Windows (NTFS) A->>W: CreateFileW(soft.lock, GENERIC_READ, share read/write/delete, FILE_FLAG_OVERLAPPED) A->>W: DeviceIoControl(FSCTL_REQUEST_OPLOCK_LEVEL_1), granted, break never acknowledged L->>W: os.open(soft.lock, O_CREAT|O_EXCL) fails with EEXIST at once L->>W: os.lstat(soft.lock) regular file rect rgb(255, 214, 214) L->>W: os.open(soft.lock, O_RDONLY) inside _read_lock_file Note over L,W: CreateFileW asks A to break the oplock and waits#59; the 1 s deadline, blocking=False and cancel_check are not consulted W-->>L: handle after 10.02 s (two-account run), or when A closes (same-account probe) end L->>L: _check_give_up: past the deadline, raise Timeout after 10.02 s Note over A,M: a stale marker takes the same wait in Path.rename and Path.unlink, so it is not brokenEvidence
Section
[2]ofoplock_vm.pyacquiredsoft.lockas the test account, closed the holder's descriptor so the marker stayed with a live process id and no open handle, took a Level 1 oplock through the second account's read handle (oplock granted: True), and ranSoftFileLock(marker, is_singleton=False).acquire(timeout=1)on a thread. The call returnedTimeoutafter 10.02 s, before the holder closed. The review did not establish what ends the wait at 10 s in the two-account runs; section[1a]of the same script, with the second account's handle, shows the same 10 s. In the same-account probe (oplock_semantics_vm.py) the open returned only when the holder closed, still running at 15 s. The probe also shows that the failed exclusive create andos.utimeby path return at once, so the wait is in the read and the break, not in the create attempt.Impact
Each
acquire()of a soft lock that finds a marker another account holds an oplock on runs past itstimeout,blocking=Falseandcancel_checkuntil the holder answers or closes, 10 s per attempt in the recorded two-account runs. A stale marker under that oplock is not broken, so no process acquires the lock while the holder keeps its handle. WithAsyncSoftFileLock(run_in_executor=False)the wait is on the event loop thread and stops every task in the process; with the default executor one worker thread waits. The documentation names a soft lock as the option for network filesystems, after verification on the mount, andFileLockis a soft lock on builds withoutfcntland after itsENOSYSfallback. Mutual exclusion is not affected and nothing other than the marker is read, written or deleted.Workarounds
SYSTEMandAdministratorsgranted (icacls <dir> /inheritance:r /grant:r "<owner>:(OI)(CI)F" "SYSTEM:(OI)(CI)F" "Administrators:(OI)(CI)F").modedoes not do this on Windows.AsyncSoftFileLockwith the default executor rather thanrun_in_executor=False, so that only a worker thread waits.timeout,blockingorcancel_checkvalue bounds the wait.Patched version
4.2.1.
Fix: https://github.com/tox-dev/filelock/pull/835
Credits
Found by the maintainers while reviewing the fix for the report Anthropic's OSS Scanner sent on 15 October 2026 (review round 4, finding F4).
Severity
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
filelock: StrictSoftFileLock creates its claim directories without the lock mode, so another local user with a write bit on them can end a live hold
CVE-2026-112908 / GHSA-6627-m3xw-93mp
More information
Details
StrictSoftFileLockandAsyncStrictSoftFileLockapply the caller'smodeto 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>.filelockand<path>.filelock/claims, with a baremkdir(). That gives0o777filtered 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
0600claims. Three settings do this: umask000in a sticky directory such as/tmp, umask002with 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 reportingis_locked == Trueand later releases with no error. Affected versions: filelock >= 3.30.0 and <= 4.2.0 (findingANT-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 restrictivemodewhile 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 umask022and no default ACL the directories are0755, 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): the0600claim 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 inclaims, and an empty file namedheld-v1-<32 zeros>.claimmakes the nextacquire()raiseSoftFileLockProtocolError("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
modesuch as0o600. Without one, the umask decides the permissions by documented design (the "File permissions and mode" section ofdocs/concepts.rstdefinesmodeonly for the lock file). Umask000with 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=0o600and the other user a member of groupteam:<path>.filelockandclaims/1777, like/tmp0000777root:team,3775(setgid, so new entries inherit the group, and sticky)0022775teamroot:team,1770, default ACLgroup:team:rwx(setfacl -d -m g:team:rwx)0770770teamroot:team,3775, no ACL0222755The default-ACL row is the strongest. A holder with a private umask and
mode=0o600still gets group-writable claim directories. The lock file in that row is0600, because its mode reachesos.openand 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>.filelockfrom an earlier run stays writable by other users after an upgrade, because filelock does not remove these directories. Upgraders shouldchmodor remove existing<path>.filelocktrees (see Workarounds).The other lock classes do not have this gap in the same setting. With umask
000andmode=0o600,FileLockandSoftFileLockcreate one0600file and nothing else, andSoftReadWriteLockcreates its directories0700under 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. That is the case when each ancestor belongs to the lock's 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 bare
mkdir()sits in_ensure_protocol_directory.StrictSoftFileLock._acquirecalls it for both levels without a mode. It then passesself._open_mode()to the sentinel and, through_attempt_doorway, to both claim files. The doorway (the acquire step that scans existing claims and publishes the caller's own) proceeds when the claim scan is empty._unlink_owner_path_resultcounts a missing claim name as removed on release.Introduced
The bare
mkdir()arrived with the claim directories in 650c17c79c54 ("feat(strict): replace shared markers with owner claims", #659, 2026-07-14), first released in 3.30.0. We checked this against the git tags. Every release since 3.30.0 has it.Mechanism
A process with umask
000that takesStrictSoftFileLock("/tmp/<dir>/x.lock", mode=0o600)leaves this on disk while it holds the lock, every entry owned by the holder:Removing a name needs write permission on the directory, and these directories are not sticky. Another local user who cannot read either claim can therefore unlink both. Renaming
claims/away inside the0777<path>.filelockhas the same effect. For this lock class the directory permissions decide who can end a hold. The sentinel at<path>is permanent and says nothing about who holds. A contender starts its doorway by listing the claim directory and proceeds when it is empty. Once the two names are gone, the next contender publishes its own intent and held claims and enters. The original holder does not notice.is_lockedis its own counter, and_releaseremoves its claims by name through_unlink_owner_path_result, which takesFileNotFoundErroras "already removed". The first process therefore releases with exit status 0. Noforce_break(), lifetime or legacy client takes part.The other user's action is a plain
unlink. The right to do it comes from filelock'smkdir, not from anything the application asked for. In the same setting the other user can neither read nor remove the0600sentinel in the sticky parent. With the common umask022and no default ACL the directories are0755. Other users can then list the claim names, whichmode=0o600does not suggest either, but cannot remove them, and the second process times out.Reproduction
The scanner's reproducer for finding
ANT-2026-AD6SUOON, as received, ran against commit b831496b0273ecd11bf02b8d080ca8b673a538c1. Thereproscript drives the files underrepro.d/.reprorepro.d/attacker_remove_claims.pyrepro.d/victim_service.pyAttack sequence
sequenceDiagram participant H as holder (umask 000, mode=0o600) participant D as x.lock.filelock/claims participant X as other local user participant C as contender (same account as the holder) H->>D: mkdir() both directories: 0777, claims 0600 H->>D: publish intent and held claims, enter X->>D: read claim: EACCES (0600) rect rgb(255, 214, 214) X->>D: unlink both claims (directory is 0777, not sticky) C->>D: acquire(): scan finds no claims, publishes, enters Note over H,C: both inside at once H->>D: release(): claims missing, counted as removed, exit status 0 end Note over H,D: control under umask 022: directories 0755, unlink denied, contender raises TimeoutEvidence
The scanner's reproducer runs the holder's
StrictSoftFileLock(path, mode=0o600)withtimeout=2as accountvictim(uid 1000) and the unlink loop as accountattacker(uid 1001). Both run in a fresh root-owned1777directory under/tmp, at commit b831496 on Linux 6.1 (overlayfs), CPython 3.14.8. Two rounds differ only in the umask the holder's processes start with.Under
CONTROL umask 022, the directories are0755, the kernel denies the unlinks, both claims remain, and the second process printsTimeout. UnderOBSERVED umask 000, every file is still0600but<path>.filelockandclaims/are0777. The other account printsunlinked held-v1-<token>.claimandunlinked intent-v1-<token>.claimfollowed byclaims left: 0. The second process printsentered. The first process printsleaving, is_locked=True, exit status 0when asked afterwards. The verdict lines readCONTROL two holders at once: FalseandOBSERVED two holders at once: True; the overall verdict isBUG. The scanner ran the umask002and default-ACL rows of the table by hand at the same commit with the same result.Our review ran the reproducer against the 4.2.0 release image and the scanned commit (
BUGon both) and against the head of #839 (OK). The review's regression test covers the owner, group-read and group-write modes.Impact
Another local user who gets a write bit on the claim directories ends a live
StrictSoftFileLockhold. A second honest process of the holder's account then enters the critical section beside the first. Neither holder receives any signal. This is the overlap the strict lock's fail-closed contract exists to rule out. The same write access lets the other user deny the lock by planting a malformed claim name. This does not disclose the claims' contents (the0600claim files stay unreadable), and it does not affect holders whose directories came out0755or tighter.Workarounds
Run processes that take strict locks with umask
022or077. Do not put a default ACL that grants other accounts write on the lock's parent directory; where you intend group sharing, trust every member of that group with the lock. Keep strict locks in a directory other users cannot write. For paths already in use,chmod 700(or remove, while no process holds the lock) every existing<path>.filelocktree. Its mode comes from creation time and nothing changes it afterwards:find <dir> -maxdepth 1 -type d -name '*.filelock' -exec chmod -R go-w {} +.Patched version
4.2.1.
Fix: https://github.com/tox-dev/filelock/pull/839
Credits
Reported by Anthropic's OSS Scanner (automated analysis), 15 October 2026; reviewed by the maintainers.
ANT-2026-AD6SUOON, scanned commit b831496b0273)Severity
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:LReferences