Update dependency python-dotenv to v1.2.2 [SECURITY] - autoclosed #9

Closed
renovate wants to merge 1 commit from renovate/pypi-python-dotenv-vulnerability into main
Member

This PR contains the following updates:

Package Change Age Confidence
python-dotenv 1.1.11.2.2 age confidence

CVE-2026-28684 / GHSA-mf9w-mj56-hr94 / PYSEC-2026-2270

More information

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

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.TemporaryDirectory(dir=os.path.expanduser("~")) as workdir:
    # File an attacker wants to overwrite
    target = os.path.join(workdir, "victim_config.txt")
    with open(target, "w") as f:
        f.write("DB_PASSWORD=supersecret\n")

    # Attacker pre-places a symlink at the path the application will use as .env
    env_symlink = os.path.join(workdir, ".env")
    os.symlink(target, env_symlink)

    before = open(target).read()

    # Application writes a new key -- triggers the cross-device fallback
    set_key(env_symlink, "INJECTED", "attacker_value")

    after = open(target).read()

    print("Before:", repr(before))
    print("After: ", repr(after))
    print("Symlink target overwritten:", target)

Expected output:

Before: 'DB_PASSWORD=supersecret\n'
After:  "DB_PASSWORD=supersecret\nINJECTED='attacker_value'\n"
Symlink target overwritten: /home/user/tmp806nut2g/victim_config.txt
Remediation

The fix changes the rewrite() context manager in the following ways:

  1. Symlinks are no longer followed by default. When the .env path 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.
  2. A follow_symlinks: bool = False parameter is added to set_key() and unset_key() for users who explicitly need the old behavior.
  3. Temp files are written in the same directory as the target .env file (instead of the system temp directory), eliminating the cross-device rename condition entirely.
  4. os.replace() is used instead of shutil.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 github.com/theskumar/python-dotenv@790c5c0299.patch

Severity

  • CVSS Score: 6.6 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


CVE-2026-28684 / GHSA-mf9w-mj56-hr94 / PYSEC-2026-2270

More information

Details

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.

Severity

  • CVSS Score: 6.6 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H

References

This data is provided by OSV and the PyPI Advisory Database (CC-BY 4.0).


Release Notes

theskumar/python-dotenv (python-dotenv)

v1.2.2

Compare Source

Added
  • Support for Python 3.14, including the free-threaded (3.14t) build. (#​588)
Changed
  • The dotenv run command now forwards flags directly to the specified command by [@​bbc2] in [#​607]
  • Improved documentation clarity regarding override behavior and the reference page.
  • Updated PyPy support to version 3.11.
  • Documentation for FIFO file support.
  • Dropped Support for Python 3.9.
Fixed
  • Improved set_key and unset_key behavior when interacting with symlinks by [@​bbc2] in [790c5c0]
  • Corrected the license specifier and added missing Python 3.14 classifiers in package metadata by [@​JYOuyang] in [#​590]
Breaking Changes
  • dotenv.set_key and dotenv.unset_key used to follow symlinks in some
    situations. This is no longer the case. For that behavior to be restored in
    all cases, follow_symlinks=True should be used.

  • In the CLI, set and unset used to follow symlinks in some situations. This
    is no longer the case.

  • dotenv.set_key, dotenv.unset_key and the CLI commands set and unset
    used to reset the file mode of the modified .env file to 0o600 in some
    situations. This is no longer the case: The original mode of the file is now
    preserved. Is the file needed to be created or wasn't a regular file, mode
    0o600 is used.

v1.2.1

Compare Source

  • Move more config to pyproject.toml, removed setup.cfg
  • Add support for reading .env from FIFOs (Unix) by [@​sidharth-sudhir] in [#​586]

v1.2.0

Compare Source


Configuration

📅 Schedule: (in timezone Europe/Paris)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate CLI.

This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [python-dotenv](https://github.com/theskumar/python-dotenv) | `1.1.1` → `1.2.2` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/python-dotenv/1.2.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/python-dotenv/1.1.1/1.2.2?slim=true) | --- ### python-dotenv: Symlink following in set_key allows arbitrary file overwrite via cross-device rename fallback [CVE-2026-28684](https://nvd.nist.gov/vuln/detail/CVE-2026-28684) / [GHSA-mf9w-mj56-hr94](https://github.com/advisories/GHSA-mf9w-mj56-hr94) / PYSEC-2026-2270 <details> <summary>More information</summary> #### 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.TemporaryDirectory(dir=os.path.expanduser("~")) as workdir: # File an attacker wants to overwrite target = os.path.join(workdir, "victim_config.txt") with open(target, "w") as f: f.write("DB_PASSWORD=supersecret\n") # Attacker pre-places a symlink at the path the application will use as .env env_symlink = os.path.join(workdir, ".env") os.symlink(target, env_symlink) before = open(target).read() # Application writes a new key -- triggers the cross-device fallback set_key(env_symlink, "INJECTED", "attacker_value") after = open(target).read() print("Before:", repr(before)) print("After: ", repr(after)) print("Symlink target overwritten:", target) ``` **Expected output:** ``` Before: 'DB_PASSWORD=supersecret\n' After: "DB_PASSWORD=supersecret\nINJECTED='attacker_value'\n" Symlink target overwritten: /home/user/tmp806nut2g/victim_config.txt ``` ##### Remediation The fix changes the `rewrite()` context manager in the following ways: 1. **Symlinks are no longer followed by default.** When the `.env` path 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. 2. **A `follow_symlinks: bool = False` parameter** is added to `set_key()` and `unset_key()` for users who explicitly need the old behavior. 3. **Temp files are written in the same directory** as the target `.env` file (instead of the system temp directory), eliminating the cross-device rename condition entirely. 4. **`os.replace()` is used instead of `shutil.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 #### Severity - CVSS Score: 6.6 / 10 (Medium) - Vector String: `CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H` #### References - [https://github.com/theskumar/python-dotenv/security/advisories/GHSA-mf9w-mj56-hr94](https://github.com/theskumar/python-dotenv/security/advisories/GHSA-mf9w-mj56-hr94) - [https://nvd.nist.gov/vuln/detail/CVE-2026-28684](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) - [https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311.patch](https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311.patch) - [https://github.com/theskumar/python-dotenv](https://github.com/theskumar/python-dotenv) - [https://github.com/theskumar/python-dotenv/releases/tag/v1.2.2](https://github.com/theskumar/python-dotenv/releases/tag/v1.2.2) This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-mf9w-mj56-hr94) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)). </details> --- ### [CVE-2026-28684](https://nvd.nist.gov/vuln/detail/CVE-2026-28684) / [GHSA-mf9w-mj56-hr94](https://github.com/advisories/GHSA-mf9w-mj56-hr94) / PYSEC-2026-2270 <details> <summary>More information</summary> #### Details 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. #### Severity - CVSS Score: 6.6 / 10 (Medium) - Vector String: `CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H` #### References - [https://github.com/theskumar/python-dotenv/releases/tag/v1.2.2](https://github.com/theskumar/python-dotenv/releases/tag/v1.2.2) - [https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311](https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311) - [https://github.com/theskumar/python-dotenv/security/advisories/GHSA-mf9w-mj56-hr94](https://github.com/theskumar/python-dotenv/security/advisories/GHSA-mf9w-mj56-hr94) This data is provided by [OSV](https://osv.dev/vulnerability/PYSEC-2026-2270) and the [PyPI Advisory Database](https://github.com/pypa/advisory-database) ([CC-BY 4.0](https://github.com/pypa/advisory-database/blob/main/LICENSE)). </details> --- ### Release Notes <details> <summary>theskumar/python-dotenv (python-dotenv)</summary> ### [`v1.2.2`](https://github.com/theskumar/python-dotenv/blob/HEAD/CHANGELOG.md#122---2026-03-01) [Compare Source](https://github.com/theskumar/python-dotenv/compare/v1.2.1...v1.2.2) ##### Added - Support for Python 3.14, including the free-threaded (3.14t) build. ([#&#8203;588](https://github.com/theskumar/python-dotenv/issues/588)) ##### Changed - The `dotenv run` command now forwards flags directly to the specified command by \[[@&#8203;bbc2](https://github.com/bbc2)] in \[[#&#8203;607](https://github.com/theskumar/python-dotenv/issues/607)] - Improved documentation clarity regarding override behavior and the reference page. - Updated PyPy support to version 3.11. - Documentation for FIFO file support. - Dropped Support for Python 3.9. ##### Fixed - Improved `set_key` and `unset_key` behavior when interacting with symlinks by \[[@&#8203;bbc2](https://github.com/bbc2)] in \[[`790c5c0`](https://github.com/theskumar/python-dotenv/commit/790c5c0)] - Corrected the license specifier and added missing Python 3.14 classifiers in package metadata by \[[@&#8203;JYOuyang](https://github.com/JYOuyang)] in \[[#&#8203;590](https://github.com/theskumar/python-dotenv/issues/590)] ##### Breaking Changes - `dotenv.set_key` and `dotenv.unset_key` used to follow symlinks in some situations. This is no longer the case. For that behavior to be restored in all cases, `follow_symlinks=True` should be used. - In the CLI, `set` and `unset` used to follow symlinks in some situations. This is no longer the case. - `dotenv.set_key`, `dotenv.unset_key` and the CLI commands `set` and `unset` used to reset the file mode of the modified .env file to `0o600` in some situations. This is no longer the case: The original mode of the file is now preserved. Is the file needed to be created or wasn't a regular file, mode `0o600` is used. ### [`v1.2.1`](https://github.com/theskumar/python-dotenv/blob/HEAD/CHANGELOG.md#121---2025-10-26) [Compare Source](https://github.com/theskumar/python-dotenv/compare/v1.2.0...v1.2.1) - Move more config to `pyproject.toml`, removed `setup.cfg` - Add support for reading `.env` from FIFOs (Unix) by \[[@&#8203;sidharth-sudhir](https://github.com/sidharth-sudhir)] in \[[#&#8203;586](https://github.com/theskumar/python-dotenv/issues/586)] ### [`v1.2.0`](https://github.com/theskumar/python-dotenv/blob/HEAD/CHANGELOG.md#120---2025-10-26) [Compare Source](https://github.com/theskumar/python-dotenv/compare/v1.1.1...v1.2.0) - Upgrade build system to use PEP 517 & PEP 518 to use `build` and `pyproject.toml` by \[[@&#8203;EpicWink](https://github.com/EpicWink)] in \[[#&#8203;583](https://github.com/theskumar/python-dotenv/issues/583)] - Add support for Python 3.14 by \[[@&#8203;23f3001135](https://github.com/23f3001135)] in \[[#&#8203;579](https://github.com/theskumar/python-dotenv/issues/579)] - Add support for disabling of `load_dotenv()` using `PYTHON_DOTENV_DISABLED` env var. by \[[@&#8203;matthewfranglen](https://github.com/matthewfranglen)] in \[[#&#8203;569](https://github.com/theskumar/python-dotenv/issues/569)] </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Paris) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC40LjYiLCJ1cGRhdGVkSW5WZXIiOiI0NC40LjYiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->
Update dependency python-dotenv to v1.2.2 [SECURITY]
All checks were successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/push/lint Pipeline was successful
bfa0290458
renovate changed title from Update dependency python-dotenv to v1.2.2 [SECURITY] to Update dependency python-dotenv to v1.2.2 [SECURITY] - autoclosed 2026-08-02 06:08:32 +02:00
renovate closed this pull request 2026-08-02 06:08:32 +02:00
All checks were successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/push/lint Pipeline was successful

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
yaal/testemails!9
No description provided.