GitHub Security Advisory: GHSA-mf9w-mj56-hr94
Package: python-dotenv (PIP)
Severity: MODERATE (CVSS 6.6)
CWE: CWE-59: Improper Link Resolution Before File Access ('Link Following'), CWE-61: UNIX Symbolic Link (Symlink) Following
Vulnerable versions: < 1.2.2
Patched in: 1.2.2

Summary: python-dotenv: Symlink following in set_key allows arbitrary file overwrite via cross-device rename fallback

Details:
### 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:

1. `shutil.move()` first attempts `os.rename()`, which fails with an `OSError` because atomic renames cannot cross device boundaries.
2. On failure, `shutil.move()` falls back to `shutil.copy2()` followed by `os.unlink()`.
3. `shutil.copy2()` calls `shutil.copyfile()` with `follow_symlinks=True` by default.
4. 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).

```python
import os
import sys
import tempfile
from dotenv import set_key

# Pre-condition: /tmp must be on a different device than the target directory.
tmp_dev = os.stat("/tmp").st_dev
home_dev = os.stat(os.path.expanduser("~")).st_dev
assert tmp_dev != home_dev, "Skipped: /tmp and ~ are on the same device (no cross-device move)"

with tempfile.Temporar