---
id: CVE-2026-112936
aliases:
  - GHSA-j8f7-rjxc-mr56
title: 'filelock: ReadWriteLock follows a symlink at the lock path into SQLite'
summary: 'filelock: ReadWriteLock follows a symlink at the lock path into SQLite'
severity: medium
cvss: 6.1
cvssVector: 'CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L'
vendor: filelock
product: filelock
ecosystem: pip
affected:
  - 'filelock >= 3.20.4, < 4.0.9'
patched:
  - filelock 4.0.9
published: '2026-10-28'
updated: '2026-10-28'
sourceUpdated: '2026-10-28T21:15:04.169983430Z'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-j8f7-rjxc-mr56'
references:
  - url: >-
      https://github.com/tox-dev/filelock/security/advisories/GHSA-j8f7-rjxc-mr56
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-112936'
  - url: >-
      https://github.com/tox-dev/filelock/commit/4d45e83caf7e31f9a12fb44526bdc07f70114f04
  - url: 'https://github.com/tox-dev/filelock'
  - url: 'https://github.com/tox-dev/filelock/releases/tag/4.0.9'
  - url: 'https://github.com/advisories/GHSA-j8f7-rjxc-mr56'
tags:
  - osv
  - pip
  - ghsa
  - cve.org
  - exploit-available
epss: 0.00232
epssPercentile: 0.14116
exploitAvailable: true
ssvc:
  exploitation: poc
  automatable: 'no'
  technicalImpact: partial
  timestamp: '2026-10-20T16:47:15.788698Z'
cwe:
  - CWE-59
  - CWE-668
ingestedAt: '2026-10-20T15:09:24.121Z'
---

## Overview

### 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.

## Affected packages

- `filelock >= 3.20.4, < 4.0.9`

## Remediation

Upgrade to a patched release:

- `filelock 4.0.9`
