{"id":"CVE-2026-28684","aliases":["DEBIAN-CVE-2026-28684","EUVD-2026-23901","GHSA-mf9w-mj56-hr94","OESA-2026-2198","OESA-2026-2199","OESA-2026-2324","PYSEC-2026-2270","ROOT-APP-PYPI-CVE-2026-28684","SUSE-SU-2026:22118-1","SUSE-SU-2026:2724-1","UBUNTU-CVE-2026-28684","openSUSE-SU-2026:20952-1"],"status":"published","title":"python-dotenv: Symlink following in set_key allows arbitrary file overwrite via cross-device rename fallback","description":"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":{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00252,"percentile":0.16836,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-59","CWE-61"],"published":"2026-04-20T16:25:12Z","modified":"2026-10-28T02:30:40Z","fixes":[{"ecosystem":"PyPI","package":"python-dotenv","fixed_in":["1.2.2"],"sources":["ghsa","osv"]},{"ecosystem":"openEuler:24.03-LTS","package":"python-dotenv","fixed_in":["1.2.2-1.oe2403"],"sources":["osv"]},{"ecosystem":"openEuler:24.03-LTS-SP1","package":"python-dotenv","fixed_in":["1.2.2-1.oe2403sp1"],"sources":["osv"]},{"ecosystem":"openEuler:24.03-LTS-SP3","package":"python-dotenv","fixed_in":["1.2.2-1.oe2403sp3"],"sources":["osv"]},{"ecosystem":"Root:PyPI","package":"python-dotenv","fixed_in":["1.2.1+aikido.2","1.1.1+aikido.2","0.21.1+aikido.1","1.0.1+aikido.1","1.1.1+aikido.3","1.1.1+aikido.4","1.2.1+aikido.3"],"sources":["osv"]},{"ecosystem":"SUSE:Linux Enterprise Server 16.0","package":"python-python-dotenv","fixed_in":["1.1.0-160000.3.1"],"sources":["osv"]},{"ecosystem":"SUSE:Linux Enterprise Server for SAP applications 16.0","package":"python-python-dotenv","fixed_in":["1.1.0-160000.3.1"],"sources":["osv"]},{"ecosystem":"SUSE:Linux Enterprise Module for Python 3 15 SP7","package":"python-python-dotenv","fixed_in":["1.0.0-150400.9.6.1"],"sources":["osv"]},{"ecosystem":"openSUSE:Leap 16.0","package":"python-python-dotenv","fixed_in":["1.1.0-160000.3.1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.2.2"}]}]},{"source":"osv","ecosystem":"Debian:12","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.21.0-1","1.0.0-1","1.0.1-1","1.2.1-1","1.2.1-2"]},{"source":"osv","ecosystem":"Debian:13","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.1-1","1.2.1-1","1.2.1-2"]},{"source":"osv","ecosystem":"Debian:14","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.1-1","1.2.1-1","1.2.1-2"]},{"source":"osv","ecosystem":"PyPI","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.2.2"}]}],"versions":["0.1.0","0.1.2","0.1.3","0.1.5","0.10.0","0.10.1","0.10.2","0.10.3","0.10.4","0.10.5","0.11.0","0.12.0","0.13.0","0.14.0","0.15.0","0.16.0","0.17.0","0.17.1","0.18.0","0.19.0","0.19.1","0.19.2","0.2.0","0.20.0","0.21.0","0.21.1","0.3.0","0.4.0","0.5.0","0.5.1","0.6.0","0.6.1","0.6.2","0.6.3","0.6.4","0.6.5","0.7.0","0.7.1","0.8.0","0.8.1","0.8.2","0.9.0","0.9.1","1.0.0","1.0.1","1.1.0","1.1.1","1.2.0","1.2.1"]},{"source":"osv","ecosystem":"openEuler:24.03-LTS","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.2.2-1.oe2403"}]}]},{"source":"osv","ecosystem":"openEuler:24.03-LTS-SP1","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.2.2-1.oe2403sp1"}]}]},{"source":"osv","ecosystem":"openEuler:24.03-LTS-SP3","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.2.2-1.oe2403sp3"}]}]},{"source":"osv","ecosystem":"PyPI","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.2.2"}]}],"versions":["0.1.0","0.1.2","0.1.3","0.1.5","0.10.0","0.10.1","0.10.2","0.10.3","0.10.4","0.10.5","0.11.0","0.12.0","0.13.0","0.14.0","0.15.0","0.16.0","0.17.0","0.17.1","0.18.0","0.19.0","0.19.1","0.19.2","0.2.0","0.20.0","0.21.0","0.21.1","0.3.0","0.4.0","0.5.0","0.5.1","0.6.0","0.6.1","0.6.2","0.6.3","0.6.4","0.6.5","0.7.0","0.7.1","0.8.0","0.8.1","0.8.2","0.9.0","0.9.1","1.0.0","1.0.1","1.1.0","1.1.1","1.2.0","1.2.1"]},{"source":"osv","ecosystem":"Root:PyPI","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.2.1+aikido.2"},{"fixed":"1.1.1+aikido.2"},{"fixed":"0.21.1+aikido.1"},{"fixed":"1.0.1+aikido.1"},{"fixed":"1.1.1+aikido.3"},{"fixed":"1.1.1+aikido.4"},{"fixed":"1.2.1+aikido.3"}]}]},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 16.0","package":"python-python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.1.0-160000.3.1"}]}]},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server for SAP applications 16.0","package":"python-python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.1.0-160000.3.1"}]}]},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Module for Python 3 15 SP7","package":"python-python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.0.0-150400.9.6.1"}]}]},{"source":"osv","ecosystem":"Ubuntu:18.04:LTS","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.7.1-1.1"]},{"source":"osv","ecosystem":"Ubuntu:20.04:LTS","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.9.1-1"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.15.0-1","0.19.0-1","0.19.2-1"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.0-1","1.0.1-1"]},{"source":"osv","ecosystem":"Ubuntu:25.10","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.1-1"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.1-1build1","1.2.1-2"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.1-1","1.0.1-1build1"]},{"source":"osv","ecosystem":"openSUSE:Leap 16.0","package":"python-python-dotenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.1.0-160000.3.1"}]}]}],"products":[{"vendor":"saurabh-kumar","product":"python-dotenv"},{"vendor":"theskumar","product":"python-dotenv"}],"references":[{"url":"https://github.com/theskumar/python-dotenv/security/advisories/GHSA-mf9w-mj56-hr94","tags":["x_refsource_CONFIRM","exploit","Exploit","Patch","Vendor Advisory","advisory","web","fix","report"]},{"url":"https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/theskumar/python-dotenv/releases/tag/v1.2.2","tags":["x_refsource_MISC","Release Notes","web","advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-28684","tags":["advisory"]},{"url":"https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311.patch","tags":["web"]},{"url":"https://github.com/advisories/GHSA-mf9w-mj56-hr94"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/28xxx/CVE-2026-28684.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-28684","tags":["advisory"]},{"url":"https://github.com/theskumar/python-dotenv","tags":["package"]},{"url":"https://www.openeuler.org/zh/security/security-bulletins/detail/?id=openEuler-SA-2026-2198","tags":["advisory"]},{"url":"https://www.openeuler.org/zh/security/security-bulletins/detail/?id=openEuler-SA-2026-2199","tags":["advisory"]},{"url":"https://www.openeuler.org/zh/security/security-bulletins/detail/?id=openEuler-SA-2026-2324","tags":["advisory"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-202622118-1/","tags":["advisory"]},{"url":"https://bugzilla.suse.com/1262423","tags":["report"]},{"url":"https://www.suse.com/security/cve/CVE-2026-28684","tags":["web"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-20262724-1/","tags":["advisory"]},{"url":"https://ubuntu.com/security/CVE-2026-28684","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-28684","tags":["report"]},{"url":"","tags":["advisory"]}],"sources":{"cve":[{"id":"CVE-2026-28684","title":"python-dotenv: Symlink following in set_key allows arbitrary file overwrite via cross-device rename fallback","description":"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.","status":"published","published":"2026-04-20T16:25:12.302Z","modified":"2026-04-20T17:43:09.477Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium","assessor":"cna"}],"cwes":["CWE-59","CWE-61"],"updated_at":"2026-11-02T14:20:01.969981Z"}],"euvd":[{"id":"EUVD-2026-23901","description":"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.","status":"published","published":"2026-04-20T16:25:12Z","modified":"2026-04-20T17:43:09Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"updated_at":"2026-11-02T19:24:22.012341Z"}],"ghsa":[{"id":"GHSA-mf9w-mj56-hr94","title":"python-dotenv: Symlink following in set_key allows arbitrary file overwrite via cross-device rename fallback","description":"### Summary\n\n`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.\n\n\n### Details\n\nThe `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.\n\nWhen 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:\n\n1. `shutil.move()` first attempts `os.rename()`, which fails with an `OSError` because atomic renames cannot cross device boundaries.\n2. On failure, `shutil.move()` falls back to `shutil.copy2()` followed by `os.unlink()`.\n3. `shutil.copy2()` calls `shutil.copyfile()` with `follow_symlinks=True` by default.\n4. This causes the content to be written to the **symlink target** rather than replacing the symlink itself.\n\nAn 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.\n\nThis vulnerability does not require a race condition and is fully deterministic once the preconditions are met.\n\n### Impact\nThe primary impacts are to **integrity** and **availability**:\n\n- **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.\n- **Integrity violation:** The target file's original content is replaced with `.env`-formatted content controlled by the attacker.\n- **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.\n\nThe scope of impact depends on the application using python-dotenv and the privileges under which it runs.\n\n\n### Proof of Concept\n\nThe 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).\n\n```python\nimport os\nimport sys\nimport tempfile\nfrom dotenv import set_key\n\n# Pre-condition: /tmp must be on a different device than the target directory.\ntmp_dev = os.stat(\"/tmp\").st_dev\nhome_dev = os.stat(os.path.expanduser(\"~\")).st_dev\nassert tmp_dev != home_dev, \"Skipped: /tmp and ~ are on the same device (no cross-device move)\"\n\nwith tempfile.TemporaryDirectory(dir=os.path.expanduser(\"~\")) as workdir:\n    # File an attacker wants to overwrite\n    target = os.path.join(workdir, \"victim_config.txt\")\n    with open(target, \"w\") as f:\n        f.write(\"DB_PASSWORD=supersecret\\n\")\n\n    # Attacker pre-places a symlink at the path the application will use as .env\n    env_symlink = os.path.join(workdir, \".env\")\n    os.symlink(target, env_symlink)\n\n    before = open(target).read()\n\n    # Application writes a new key -- triggers the cross-device fallback\n    set_key(env_symlink, \"INJECTED\", \"attacker_value\")\n\n    after = open(target).read()\n\n    print(\"Before:\", repr(before))\n    print(\"After: \", repr(after))\n    print(\"Symlink target overwritten:\", target)\n```\n\n**Expected output:**\n```\nBefore: 'DB_PASSWORD=supersecret\\n'\nAfter:  \"DB_PASSWORD=supersecret\\nINJECTED='attacker_value'\\n\"\nSymlink target overwritten: /home/user/tmp806nut2g/victim_config.txt\n```\n\n### Remediation\n\nThe fix changes the `rewrite()` context manager in the following ways:\n\n1. **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.\n2. **A `follow_symlinks: bool = False` parameter** is added to `set_key()` and `unset_key()` for users who explicitly need the old behavior.\n3. **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.\n4. **`os.replace()` is used instead of `shutil.move()`**, providing atomic replacement without symlink-following fallback behavior.\n\nUsers are advised to upgrade to the patched version as soon as it is available on PyPI.\n\n### Timeline\n\n| Date             | Event                                                                                                                                                                 |\n| ------------ | ---------------------------------------------------------------------------------------------------- |\n| 2026-01-09  | Initial report received from Giorgos Tsigourakos regarding a separate, unrelated issue also located in `rewrite()` |\n| 2026-01-10   | Co-maintainer acknowledged report, requested clarification                                                         |\n| 2026-01-11    | Initial report assessed as not exploitable and closed                                                              |\n| 2026-02-24  | Reporter identified new, distinct cross-device symlink attack vector with deterministic exploitation               |\n| 2026-02-26  | Co-maintainer confirmed vulnerability and shared draft patch                                                       |\n| 2026-02-26  | Reporter validated fix with monkeypatched PoC, proposed CVSS                                                       |\n| 2026-03-01   | Patch merged to main                                                                                               |\n| 2026-03-01   | Patched version released to PyPI                                                                                   |\n| 2026-04-20   | Advisory published                                                                                                 |\n\n### Patches\n\nUpgrade to v.1.2.2 or use the patch from https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311.patch","status":"published","published":"2026-04-21T14:38:57Z","modified":"2026-04-21T14:38:59Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"cwes":["CWE-59","CWE-61"],"updated_at":"2026-11-02T14:39:55.105822Z"}],"nvd":[{"id":"CVE-2026-28684","description":"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.","status":"published","published":"2026-04-20T17:16:33.087Z","modified":"2026-10-26T21:10:00.17Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium","assessor":"secondary"}],"cwes":["CWE-59","CWE-61"],"updated_at":"2026-11-02T14:12:15.120927Z"}],"osv":[{"id":"CVE-2026-28684","title":"python-dotenv: Symlink following in set_key allows arbitrary file overwrite via cross-device rename fallback","description":"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.","status":"published","published":"2026-04-20T16:25:12.302Z","modified":"2026-10-28T02:30:40.924321186Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"cwes":["CWE-59","CWE-61"],"updated_at":"2026-11-02T15:45:00.530889Z"},{"id":"DEBIAN-CVE-2026-28684","description":"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.","status":"published","published":"2026-04-20T17:16:33.087Z","modified":"2026-10-27T09:47:43.401298023Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"updated_at":"2026-11-02T15:45:53.877573Z"},{"id":"GHSA-mf9w-mj56-hr94","title":"python-dotenv: Symlink following in set_key allows arbitrary file overwrite via cross-device rename fallback","description":"### Summary\n\n`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.\n\n\n### Details\n\nThe `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.\n\nWhen 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:\n\n1. `shutil.move()` first attempts `os.rename()`, which fails with an `OSError` because atomic renames cannot cross device boundaries.\n2. On failure, `shutil.move()` falls back to `shutil.copy2()` followed by `os.unlink()`.\n3. `shutil.copy2()` calls `shutil.copyfile()` with `follow_symlinks=True` by default.\n4. This causes the content to be written to the **symlink target** rather than replacing the symlink itself.\n\nAn 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.\n\nThis vulnerability does not require a race condition and is fully deterministic once the preconditions are met.\n\n### Impact\nThe primary impacts are to **integrity** and **availability**:\n\n- **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.\n- **Integrity violation:** The target file's original content is replaced with `.env`-formatted content controlled by the attacker.\n- **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.\n\nThe scope of impact depends on the application using python-dotenv and the privileges under which it runs.\n\n\n### Proof of Concept\n\nThe 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).\n\n```python\nimport os\nimport sys\nimport tempfile\nfrom dotenv import set_key\n\n# Pre-condition: /tmp must be on a different device than the target directory.\ntmp_dev = os.stat(\"/tmp\").st_dev\nhome_dev = os.stat(os.path.expanduser(\"~\")).st_dev\nassert tmp_dev != home_dev, \"Skipped: /tmp and ~ are on the same device (no cross-device move)\"\n\nwith tempfile.TemporaryDirectory(dir=os.path.expanduser(\"~\")) as workdir:\n    # File an attacker wants to overwrite\n    target = os.path.join(workdir, \"victim_config.txt\")\n    with open(target, \"w\") as f:\n        f.write(\"DB_PASSWORD=supersecret\\n\")\n\n    # Attacker pre-places a symlink at the path the application will use as .env\n    env_symlink = os.path.join(workdir, \".env\")\n    os.symlink(target, env_symlink)\n\n    before = open(target).read()\n\n    # Application writes a new key -- triggers the cross-device fallback\n    set_key(env_symlink, \"INJECTED\", \"attacker_value\")\n\n    after = open(target).read()\n\n    print(\"Before:\", repr(before))\n    print(\"After: \", repr(after))\n    print(\"Symlink target overwritten:\", target)\n```\n\n**Expected output:**\n```\nBefore: 'DB_PASSWORD=supersecret\\n'\nAfter:  \"DB_PASSWORD=supersecret\\nINJECTED='attacker_value'\\n\"\nSymlink target overwritten: /home/user/tmp806nut2g/victim_config.txt\n```\n\n### Remediation\n\nThe fix changes the `rewrite()` context manager in the following ways:\n\n1. **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.\n2. **A `follow_symlinks: bool = False` parameter** is added to `set_key()` and `unset_key()` for users who explicitly need the old behavior.\n3. **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.\n4. **`os.replace()` is used instead of `shutil.move()`**, providing atomic replacement without symlink-following fallback behavior.\n\nUsers are advised to upgrade to the patched version as soon as it is available on PyPI.\n\n### Timeline\n\n| Date             | Event                                                                                                                                                                 |\n| ------------ | ---------------------------------------------------------------------------------------------------- |\n| 2026-01-09  | Initial report received from Giorgos Tsigourakos regarding a separate, unrelated issue also located in `rewrite()` |\n| 2026-01-10   | Co-maintainer acknowledged report, requested clarification                                                         |\n| 2026-01-11    | Initial report assessed as not exploitable and closed                                                              |\n| 2026-02-24  | Reporter identified new, distinct cross-device symlink attack vector with deterministic exploitation               |\n| 2026-02-26  | Co-maintainer confirmed vulnerability and shared draft patch                                                       |\n| 2026-02-26  | Reporter validated fix with monkeypatched PoC, proposed CVSS                                                       |\n| 2026-03-01   | Patch merged to main                                                                                               |\n| 2026-03-01   | Patched version released to PyPI                                                                                   |\n| 2026-04-20   | Advisory published                                                                                                 |\n\n### Patches\n\nUpgrade to v.1.2.2 or use the patch from https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311.patch","status":"published","published":"2026-04-21T14:38:57Z","modified":"2026-09-10T03:51:02.363430883Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"cwes":["CWE-59","CWE-61"],"updated_at":"2026-11-02T15:46:51.811331Z"},{"id":"OESA-2026-2198","title":"python-dotenv security update","description":"Python-dotenv reads key-value pairs from a .env file and can set them as environment variables. It helps in the development of applications following the 12-factor principles.\r\n\r\nSecurity Fix(es):\n\npython-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.(CVE-2026-28684)","status":"published","published":"2026-05-09T11:11:10Z","modified":"2026-08-18T01:19:51.363180013Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"updated_at":"2026-11-02T15:52:46.087701Z"},{"id":"OESA-2026-2199","title":"python-dotenv security update","description":"Python-dotenv reads key-value pairs from a .env file and can set them as environment variables. It helps in the development of applications following the 12-factor principles.\r\n\r\nSecurity Fix(es):\n\npython-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.(CVE-2026-28684)","status":"published","published":"2026-05-09T11:11:10Z","modified":"2026-08-18T01:19:51.798986735Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"updated_at":"2026-11-02T15:52:46.087701Z"},{"id":"OESA-2026-2324","title":"python-dotenv security update","description":"Python-dotenv reads key-value pairs from a .env file and can set them as environment variables. It helps in the development of applications following the 12-factor principles.\r\n\r\nSecurity Fix(es):\n\npython-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.(CVE-2026-28684)","status":"published","published":"2026-05-15T11:11:17Z","modified":"2026-08-18T01:19:54.263897828Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"updated_at":"2026-11-02T15:52:46.087701Z"},{"id":"PYSEC-2026-2270","description":"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.","status":"published","published":"2026-04-20T17:16:33.087Z","modified":"2026-07-13T07:15:44.015809613Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"updated_at":"2026-11-02T15:52:55.537808Z"},{"id":"ROOT-APP-PYPI-CVE-2026-28684","title":"CVE-2026-28684 in python-dotenv - Patched by Root","description":"Root has patched CVE-2026-28684 in the python-dotenv package for Root:PyPI. Multiple fixed versions available.","status":"published","published":"2026-10-04T14:01:30Z","modified":"2026-10-04T15:15:03.070007235Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"updated_at":"2026-11-02T15:53:27.940619Z"},{"id":"UBUNTU-CVE-2026-28684","description":"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.","status":"published","published":"2026-04-20T17:16:00Z","modified":"2026-10-17T12:58:08.103490576Z","cvss":[{"version":"3.1","score":6.6,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H","level":"medium"}],"updated_at":"2026-11-02T16:56:08.726452Z"},{"id":"openSUSE-SU-2026:20952-1","title":"Security update for python-python-dotenv","description":"This update for python-python-dotenv fixes the following issue:\n\n- CVE-2026-28684: Prior to version 1.2.2, `set_key()` and `unset_key()` in python-dotenv follow symbolic links when\n  rewriting `.env` files, allowing a local attacker to overwrite arbitrary files via a crafted symlink (bsc#1262423).\n","status":"published","published":"2026-06-11T12:45:01Z","modified":"2026-06-16T18:24:43.520031156Z","updated_at":"2026-11-02T16:58:41.885092Z"},{"id":"SUSE-SU-2026:22118-1","title":"Security update for python-python-dotenv","description":"This update for python-python-dotenv fixes the following issue:\n\n- CVE-2026-28684: Prior to version 1.2.2, `set_key()` and `unset_key()` in python-dotenv follow symbolic links when\n  rewriting `.env` files, allowing a local attacker to overwrite arbitrary files via a crafted symlink (bsc#1262423).\n","status":"published","published":"2026-06-11T12:44:37Z","modified":"2026-06-17T18:24:19.844172567Z","updated_at":"2026-11-02T15:54:02.654057Z"},{"id":"SUSE-SU-2026:2724-1","title":"Security update for python-python-dotenv","description":"This update for python-python-dotenv fixes the following issue:\n\n- CVE-2026-28684: follow symbolic links when rewriting `.env` files (bsc#1262423)\n","status":"published","published":"2026-07-01T18:09:54Z","modified":"2026-07-03T11:00:05.553880623Z","updated_at":"2026-11-02T15:54:04.519245Z"}]}}
