Описание
python-dotenv: Symlink following in set_key allows arbitrary file overwrite via cross-device rename fallback
Summary
set_key() and unset_key() in python-dotenv follow symbolic links when rewriting .env files, allowing a local attacker to overwrite arbitrary files via a crafted symlink when a cross-device rename fallback is triggered.
Details
The rewrite() context manager in dotenv/main.py is used by both set_key() and unset_key() to safely modify .env files. It works by writing to a temporary file (created in the system's default temp directory, typically /tmp) and then using shutil.move() to replace the original file.
When the .env path is a symbolic link and the temp directory resides on a different filesystem than the target (a common configuration on Linux systems using tmpfs for /tmp), the following sequence occurs:
shutil.move()first attemptsos.rename(), which fails with anOSErrorbecause atomic renames cannot cross device boundaries.- On failure,
shutil.move()falls back toshutil.copy2()followed byos.unlink(). shutil.copy2()callsshutil.copyfile()withfollow_symlinks=Trueby default.- This causes the content to be written to the symlink target rather than replacing the symlink itself.
An attacker who has write access to the directory containing a .env file can pre-place a symlink pointing to any file that the application process has write access to. When the application (or a privileged process such as a deploy script, Docker entrypoint, or CI pipeline) calls set_key() or unset_key(), the symlink target is overwritten with the new .env content.
This vulnerability does not require a race condition and is fully deterministic once the preconditions are met.
Impact
The primary impacts are to integrity and availability:
- File overwrite / destruction (DoS): An attacker can cause an application or privileged process to corrupt or destroy configuration files, database configs, or other sensitive files it would not normally have access to modify.
- Integrity violation: The target file's original content is replaced with
.env-formatted content controlled by the attacker. - Potential privilege escalation: In scenarios where a privileged process (running as root or a service account) calls
set_key(), the attacker can leverage this to write to files beyond their own access level.
The scope of impact depends on the application using python-dotenv and the privileges under which it runs.
Proof of Concept
The following script demonstrates the vulnerability. It requires /tmp and the user's home directory to reside on different devices (common on systemd-based Linux systems with tmpfs).
Expected output:
Remediation
The fix changes the rewrite() context manager in the following ways:
- Symlinks are no longer followed by default. When the
.envpath is a symlink,rewrite()now resolves it to the real path before proceeding, or (by default) operates on the symlink entry itself rather than the target. - A
follow_symlinks: bool = Falseparameter is added toset_key()andunset_key()for users who explicitly need the old behavior. - Temp files are written in the same directory as the target
.envfile (instead of the system temp directory), eliminating the cross-device rename condition entirely. os.replace()is used instead ofshutil.move(), providing atomic replacement without symlink-following fallback behavior.
Users are advised to upgrade to the patched version as soon as it is available on PyPI.
Timeline
| Date | Event |
|---|---|
| 2026-01-09 | Initial report received from Giorgos Tsigourakos regarding a separate, unrelated issue also located in rewrite() |
| 2026-01-10 | Co-maintainer acknowledged report, requested clarification |
| 2026-01-11 | Initial report assessed as not exploitable and closed |
| 2026-02-24 | Reporter identified new, distinct cross-device symlink attack vector with deterministic exploitation |
| 2026-02-26 | Co-maintainer confirmed vulnerability and shared draft patch |
| 2026-02-26 | Reporter validated fix with monkeypatched PoC, proposed CVSS |
| 2026-03-01 | Patch merged to main |
| 2026-03-01 | Patched version released to PyPI |
| 2026-04-20 | Advisory published |
Patches
Upgrade to v.1.2.2 or use the patch from https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311.patch
Ссылки
- https://github.com/theskumar/python-dotenv/security/advisories/GHSA-mf9w-mj56-hr94
- https://nvd.nist.gov/vuln/detail/CVE-2026-28684
- https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311
- https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311.patch
- https://github.com/theskumar/python-dotenv/releases/tag/v1.2.2
Пакеты
python-dotenv
< 1.2.2
1.2.2
Связанные уязвимости
python-dotenv reads key-value pairs from a .env file and can set them as environment variables. Prior to version 1.2.2, `set_key()` and `unset_key()` in python-dotenv follow symbolic links when rewriting `.env` files, allowing a local attacker to overwrite arbitrary files via a crafted symlink when a cross-device rename fallback is triggered. Users should upgrade to v.1.2.2 or, as a workaround, apply the patch manually.
python-dotenv reads key-value pairs from a .env file and can set them as environment variables. Prior to version 1.2.2, `set_key()` and `unset_key()` in python-dotenv follow symbolic links when rewriting `.env` files, allowing a local attacker to overwrite arbitrary files via a crafted symlink when a cross-device rename fallback is triggered. Users should upgrade to v.1.2.2 or, as a workaround, apply the patch manually.
python-dotenv reads key-value pairs from a .env file and can set them as environment variables. Prior to version 1.2.2, `set_key()` and `unset_key()` in python-dotenv follow symbolic links when rewriting `.env` files, allowing a local attacker to overwrite arbitrary files via a crafted symlink when a cross-device rename fallback is triggered. Users should upgrade to v.1.2.2 or, as a workaround, apply the patch manually.
python-dotenv reads key-value pairs from a .env file and can set them ...