Python 3.9 filelock with CVE-2025-68146 and CVE-2026-22701 security patches (symlink/TOCTOU attack prevention via O_NOFOLLOW)
Project description
filelock-lts (lts-py39) — 🛡️ PATCHED (Backport)
⚠️ Disclaimer: This project is not affiliated with, endorsed by, or associated with the official
filelockmaintainers. All patches and releases are independently maintained and provided on a best-effort basis to support legacy environments.
| Metric | Details |
|---|---|
| CVEs Fixed | CVE-2025-68146 (HIGH) · CVE-2026-22701 (MODERATE) |
| Version | 2026.22701.1 |
| Base | filelock 3.19.1 (upstream tag for Python 3.9) |
| Python | 3.9 only (>=3.9, <3.10) |
| License | Unlicense (Public Domain) |
🚨 The Problem
filelock >= 3.20.1 contains the official fix for CVE-2025-68146 and CVE-2026-22701, but upstream
requires Python >= 3.10. Python 3.9 users are permanently excluded from
official security patches.
This package backports the complete fix to Python 3.9.
🔒 Defense-in-Depth
This package uses three hardening layers to protect patched files from being silently overwritten by pip's flat install model:
-
Install-time prevention —
[conflicts]table inpyproject.tomlsignals incompatibility with upstreamfilelockto resolvers that support it. Enforced by pip ≥ 24.1 during dependency resolution; older pip versions will ignore this field silently. -
Runtime integrity check —
_check_clobber()runs at import time using two detection layers:- RECORD-based SHA256 verification (primary): reads pip's own
dist-info/RECORDand verifies_unix.py,_windows.py, and_soft.pyon disk still match the hashes recorded at install time. Catches silent overwrites even when no upstream dist-info is present. - Co-install detection (secondary): scans installed distributions for a
bare
filelockdist alongside this package. Detection is non-fatal — failures in the check will not interrupt imports.
- RECORD-based SHA256 verification (primary): reads pip's own
-
Documentation — install-order warnings, safe workflow guidance, and verification steps below.
Note: These layers reduce risk but cannot fully prevent file overwrites within pip's install model. Always verify patch integrity after installing new packages in the same environment. See Verifying Patch Integrity below.
📦 Installation
Fresh install (no existing filelock)
pip install filelock-lts-py39==2026.22701.1
If filelock is already installed — do this in order
# 1. Remove upstream filelock first
pip uninstall filelock -y
# 2. Install the patched version
pip install filelock-lts-py39==2026.22701.1
# 3. Verify patch integrity (see section below)
Why the order matters: Both packages install into the
filelock/namespace insite-packages. Whichever is installed last owns the files. If upstreamfilelockis installed after this package, it silently overwrites the patched_unix.py,_windows.py, and_soft.py, reintroducing the CVEs with no error or warning from pip.
⚠️ Staying Protected After Initial Install
The clobber risk
Any tool that declares Requires: filelock (without a version pin) may cause pip
to install upstream filelock when that tool is installed, overwriting your
patched files. This happens silently — pip does not warn when one package's
files are overwritten by another.
Safe workflow
# After installing ANY new package into this environment, verify protection:
python -c "import filelock"
# If patched files were overwritten, you will see a RuntimeWarning.
# Or check which dist owns the filelock namespace:
pip show filelock
# Should show NO result, or only show filelock-lts-py39
# If upstream filelock crept back in:
pip uninstall filelock -y
pip install --force-reinstall filelock-lts-py39
Pinning in requirements files
# requirements.txt — pin explicitly to block pip from pulling upstream
filelock-lts-py39==2026.22701.1
# Do NOT also list 'filelock' — that will pull in the unpatched upstream
Pinning in pyproject.toml
[project]
dependencies = [
"filelock-lts-py39==2026.22701.1",
# Do NOT add 'filelock' here — it will clobber the patched version
]
✅ Verifying Patch Integrity
Quick check — dist ownership
python -c "
import importlib.metadata as m
for d in m.distributions():
name = d.metadata.get('Name', '')
if 'filelock' in name.lower():
print(name, d.metadata.get('Version', ''))
"
# Expected: only 'filelock-lts-py39 2026.22701.1'
# If you see bare 'filelock 3.x.x' — upstream has been installed and may
# have overwritten the patched files.
Full integrity check — SHA256 against RECORD
This verifies the actual patched files on disk match the hashes pip recorded
at install time. This is the same check _check_clobber() performs at import.
python -c "
import importlib.metadata as m, hashlib, base64, pathlib
LTS_NAMES = {
'filelock_lts_py39', 'filelock_lts',
'filelock-lts-py39', 'filelock-lts',
}
PATCHED = {'_unix.py', '_windows.py', '_soft.py'}
our_dist = None
for d in m.distributions():
norm = (d.metadata.get('Name', '') or '').lower().replace('-', '_')
if norm in LTS_NAMES:
our_dist = d
break
if our_dist is None:
print('ERROR: filelock-lts-py39 dist-info not found')
raise SystemExit(1)
import filelock as fl
pkg_dir = pathlib.Path(fl.__file__).parent
record = our_dist.read_text('RECORD')
ok, failed = [], []
for line in record.splitlines():
parts = line.split(',')
if len(parts) < 2:
continue
filename = pathlib.Path(parts[0].strip()).name
recorded = parts[1].strip()
if filename not in PATCHED or not recorded.startswith('sha256:'):
continue
actual = pkg_dir / filename
if not actual.exists():
failed.append(f'{filename}: MISSING')
continue
digest = base64.urlsafe_b64encode(
hashlib.sha256(actual.read_bytes()).digest()
).rstrip(b'=').decode()
if digest == recorded[7:]:
ok.append(filename)
else:
failed.append(f'{filename}: HASH MISMATCH')
for f in ok:
print(f' ✅ {f}')
for f in failed:
print(f' ❌ {f}')
if failed:
print('\nPatch integrity FAILED — reinstall filelock-lts-py39')
raise SystemExit(1)
else:
print('\nAll patched files verified against RECORD.')
"
⚙️ What Was Patched
CVE-2025-68146 — _unix.py, _windows.py
- Unix:
os.O_NOFOLLOWflag enforced during lock file creation, blocking symlink traversal at the kernel level. - Windows: Explicit reparse point detection via
kernel32.GetFileAttributesW(ctypes), refusing lock acquisition if the target is a symlink or directory junction.
CVE-2026-22701 — _soft.py
O_NOFOLLOWguard applied toSoftFileLockviagetattrfallback for cross-platform safety.
Patch files and upstream diff analysis:
security/patches/— the exact diffs appliedsecurity/analysis/— justification for each change
🔗 Links
| Resource | URL |
|---|---|
| Source (this branch) | https://github.com/1minds3t/filelock-lts/tree/lts-py39 |
| Patch files | https://github.com/1minds3t/filelock-lts/tree/lts-py39/security/patches |
| CVE-2025-68146 | https://nvd.nist.gov/vuln/detail/CVE-2025-68146 |
| CVE-2026-22701 | https://www.cve.org/CVERecord?id=CVE-2026-22701 |
| Upstream filelock | https://github.com/tox-dev/py-filelock |
Note for package maintainers: If your package targets Python 3.9 and currently lists
filelockas a dependency, consider switching tofilelock-lts-py39>=2026.22701.1to ensure your users receive the patched version. The import API is 100% compatible — no code changes required.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file filelock_lts_py39-2026.22701.tar.gz.
File metadata
- Download URL: filelock_lts_py39-2026.22701.tar.gz
- Upload date:
- Size: 26.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6494983194bcf2c8c482f9498b1b3008b93fc26eaafb8ad7ce5bbe78b7743b4c
|
|
| MD5 |
3f33a7c6185230e1a3a8fd1e6b0defcb
|
|
| BLAKE2b-256 |
42d0aa5dce0a55de398727a14c47b182eacb35529137eb314b95d24109b606e0
|
Provenance
The following attestation bundles were made for filelock_lts_py39-2026.22701.tar.gz:
Publisher:
publish.yml on 1minds3t/filelock-lts
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
filelock_lts_py39-2026.22701.tar.gz -
Subject digest:
6494983194bcf2c8c482f9498b1b3008b93fc26eaafb8ad7ce5bbe78b7743b4c - Sigstore transparency entry: 1402792469
- Sigstore integration time:
-
Permalink:
1minds3t/filelock-lts@eb001d3191e8afd306326937d6cbc38361867c4e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/1minds3t
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@eb001d3191e8afd306326937d6cbc38361867c4e -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file filelock_lts_py39-2026.22701-py3-none-any.whl.
File metadata
- Download URL: filelock_lts_py39-2026.22701-py3-none-any.whl
- Upload date:
- Size: 21.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
830a9e4081d153e9e22fefb1abaff0f24e222378e6d91b6cae9c23b82e8fea9b
|
|
| MD5 |
5b78ae474b4074a9e693f3f8c668e8d2
|
|
| BLAKE2b-256 |
f2d9bf705e713ba6513f68c73872eee91bb146f548edf16f8ca4df19ce7800a4
|
Provenance
The following attestation bundles were made for filelock_lts_py39-2026.22701-py3-none-any.whl:
Publisher:
publish.yml on 1minds3t/filelock-lts
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
filelock_lts_py39-2026.22701-py3-none-any.whl -
Subject digest:
830a9e4081d153e9e22fefb1abaff0f24e222378e6d91b6cae9c23b82e8fea9b - Sigstore transparency entry: 1402792674
- Sigstore integration time:
-
Permalink:
1minds3t/filelock-lts@eb001d3191e8afd306326937d6cbc38361867c4e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/1minds3t
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@eb001d3191e8afd306326937d6cbc38361867c4e -
Trigger Event:
workflow_dispatch
-
Statement type: