← Back to Feed CACHED · 2026-09-29 20:37:15 · CACHE_KEY CVE-2026-12345
CVE-2026-12345 · CWE-59 · Disclosed 2026-09-29

The cleanup of tempfile.TemporaryDirectory is vulnerable to a race condition.

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

Someone left the janitor's closet unlocked and a prankster swapped the trash bin labels so the janitor throws away the good files

CVE-2026-12345 is a TOCTOU (time-of-check-time-of-use) race condition in CPython's tempfile.TemporaryDirectory._cleanup() method. When the cleanup routine encounters permission errors, it re-resolves file paths before deletion. During that narrow re-resolution window, a local attacker who can write to the temporary directory tree can swap a subdirectory for a symbolic link pointing outside the tree. The cleanup process then follows the symlink and deletes or resets permissions on arbitrary files — with whatever privileges the victim process holds. All CPython versions prior to 3.15.0 are affected (3.10.x, 3.11.x, 3.12.x, 3.13.x, 3.14.x). This is a close relative of CVE-2023-6597 (CVSS 7.8 HIGH), which addressed an earlier incomplete fix for the same symlink-following behavior in TemporaryDirectory.

The published CVSS 4.0 base score of 5.9 MEDIUM is honest and reasonable. The attack vector is strictly local (AV:L) — there is no network-reachable entry point. The attack requires present timing conditions (AT:P), meaning the attacker must win a race. There is no confidentiality impact (VC:N); the damage is limited to integrity (file deletion, permission clobbering) and availability (data loss). Unlike CVE-2023-6597 which scored 7.8 under CVSS 3.1, this CVE uses the stricter CVSS 4.0 methodology which better accounts for the timing prerequisite. For a fleet of 10,000 hosts, the practical risk is constrained to multi-user systems where an unprivileged attacker already has shell access on the same box as a privileged Python process using TemporaryDirectory — a real but narrow scenario.

"Local symlink race in Python tempdir cleanup — file deletion, not RCE, limits real-world blast radius."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Local shell access on target host

The attacker obtains unprivileged local code execution on a system running a Python application that uses tempfile.TemporaryDirectory. This could be via SSH, a compromised web app spawning under a low-privilege user, or a shared CI runner. The attacker needs write access to the filesystem, specifically to a location where they can observe or predict the temporary directory path.
Conditions required:
  • Local code execution on the target host
  • Ability to enumerate or predict /tmp paths (trivial on most Linux systems)
Where this breaks in practice:
  • Requires the attacker to already be on the box — this is post-initial-access
  • Cloud workloads in containers often run single-process, eliminating the multi-user scenario
Detection/coverage: Host-based monitoring (auditd, osquery) can detect unexpected symlink creation in /tmp.
STEP 02

Identify a privileged Python process using TemporaryDirectory

The attacker identifies a process running as root or a higher-privileged user that is actively creating and cleaning up tempfile.TemporaryDirectory objects. They monitor the /tmp directory (or whatever tempdir is configured) to observe the creation of tmp* directories. On Linux, /proc and inotify make this straightforward.
Conditions required:
  • A privileged process on the same host actively uses tempfile.TemporaryDirectory
  • The temp directory base path is world-readable (default for /tmp)
Where this breaks in practice:
  • Many applications use tempfile.mkdtemp() or tempfile.NamedTemporaryFile() instead of TemporaryDirectory
  • Containerized Python apps rarely run as root alongside unprivileged users
Detection/coverage: Process auditing can flag inotify watches on /tmp by non-root users.
STEP 03

Race the cleanup — swap directory for symlink

When the target process begins its TemporaryDirectory cleanup and hits a permission error triggering re-resolution, the attacker replaces a subdirectory inside the temp tree with a symbolic link pointing to a target directory outside the tree (e.g., /etc, /var/lib/important_data). The timing window is narrow but the attacker can retry rapidly. No public exploit tool exists; this requires a custom C or Python script using inotify + rename(2).
Conditions required:
  • Write access to the TemporaryDirectory tree (or its parent if permissions allow)
  • The cleanup must encounter a permission error to trigger the vulnerable re-resolution code path
Where this breaks in practice:
  • The race window is narrow — requires precise timing or rapid retry loops
  • The cleanup must hit the error-handling path, not the happy path
  • No known weaponized tool or public PoC exists
Detection/coverage: File integrity monitoring (AIDE, OSSEC) can detect unexpected symlinks in /tmp. Sysdig/Falco rules can alert on rename-to-symlink patterns.
STEP 04

Arbitrary file deletion or permission reset

If the race is won, the cleanup process follows the symlink and deletes files or resets permissions and file flags on the target directory using the privileges of the victim process. If the victim runs as root, this can destroy critical system files, configuration, or data. The impact is denial of service and integrity violation — not code execution.
Conditions required:
  • Successful race condition exploitation in step 3
  • The victim process has write/delete permissions on the symlink target
Where this breaks in practice:
  • Impact is file destruction, not code execution — limited lateral movement value
  • Destroying files on a production host is noisy and likely triggers alerts
  • Attacker cannot control which specific files are deleted beyond choosing the target directory
Detection/coverage: Unexpected file deletions by Python processes can be caught by auditd rules on unlink/rmdir syscalls in sensitive directories.
03 · Compensating Control

1
MEDIUM 5.5→LOW 2.5
SEVERITY REDUCED
Restrict /tmp permissions with per-user temp directories — Configure TMPDIR or TMP environment variables to point to per-user directories (e.g., /run/user/$UID on systemd systems, or mount /tmp with nosuid and use pam_namespace for per-user /tmp isolation). This eliminates the multi-user prerequisite by ensuring unprivileged users cannot write to another user's temp tree. Deploy within the noisgate remediation SLA of 365 days for MEDIUM, or sooner if you operate shared multi-user Python hosts.
2
MEDIUM 5.5→IGNORE 0.0
SEVERITY REDUCED
Run Python processes in single-tenant containers — Ensure privileged Python workloads using TemporaryDirectory run in dedicated containers or VMs where no untrusted user has local access. This removes the local attacker prerequisite entirely. Most modern Kubernetes and Docker deployments already satisfy this. Validate your CI runner configuration — shared runners with multiple UIDs are the primary remaining risk surface. No mitigation SLA for MEDIUM — go straight to the 365-day remediation window.
3
MEDIUM 5.5→IGNORE 0.0
SEVERITY REDUCED
Upgrade Python to 3.15.0+ or apply distro backport — The definitive fix is in CPython 3.15.0 (GA expected October 2026). Backport PRs are merged for 3.13 and 3.14 branches. Monitor your distro's python3 package for the security update and apply it within the noisgate remediation SLA of 365 days. For RHEL, track Bugzilla #2543492. For Debian/Ubuntu, watch the python3.x-minimal changelogs.
4
MEDIUM 5.5→MEDIUM 4.5
Enable filesystem auditing on /tmp symlink creation — Deploy auditd rules (Linux) or Sysmon rules (Windows) to alert on symlink creation in temp directories by non-root users. Example: auditctl -w /tmp -p wa -k tmp_symlink. This provides detection coverage while waiting for the patch. Does not prevent exploitation but drastically reduces dwell time.
What doesn't work
  • WAF / network IDS — This is a local vulnerability with no network component. No network-layer control can detect or prevent exploitation.
  • Application-level sandboxing (seccomp, AppArmor profiles) — While seccomp could theoretically block symlink(2), most Python application profiles do not restrict symlink creation, and doing so would break legitimate functionality in many libraries.
  • Disabling symlinks system-wide — Not practical on Linux/macOS. The nosymfollow mount option exists on some kernels but breaks too many applications to deploy broadly.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNo confirmed exploitation. Not listed in CISA KEV. Feedly reports unverified mentions on Mastodon, but no campaign or threat actor attribution. This is a local-only vulnerability with no network attack surface, making mass exploitation extremely unlikely.
Proof-of-Concept AvailabilityNo public PoC found. Checked pocindex.io, GitHub (no repos named CVE-2026-12345), ExploitDB, and Nuclei templates — all negative. The GitHub issue #157579 describes the mechanism in sufficient detail to build a PoC, but none has been published. The predecessor CVE-2023-6597 also had no widely circulated weaponized exploit.
EPSS ScoreNot yet scored. CVE was disclosed 2026-09-29 (1 day ago). FIRST EPSS model has not yet ingested this CVE. Expect a low score (likely <5th percentile) given local-only attack vector and no PoC.
CISA KEV StatusNot listed. No federal remediation deadline. Given the local attack vector and lack of exploitation evidence, KEV listing is unlikely.
CVSS Vector & InterpretationCVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N = 5.9 MEDIUM. Local vector, low complexity but requires present timing (race condition), no privileges for the attacker role, no user interaction. Zero confidentiality impact. High integrity and availability impact on the vulnerable system only — no downstream/subsequent system impact.
Affected VersionsAll CPython versions < 3.15.0: specifically 3.10.x, 3.11.x, 3.12.x, 3.13.x, 3.14.x. Python 2.x is EOL and unaffected by this specific fix. PyPy and other implementations may or may not share the vulnerable code path.
Fixed VersionsCPython 3.15.0 (main branch, expected GA October 2026). Backport PRs merged: 3.14, 3.13, 3.15 branch. Manual backports pending for 3.12, 3.11, 3.10. Distro packages (Debian, RHEL, Ubuntu) will lag — watch python3 package changelogs. Red Hat tracking: Bugzilla #2543492.
Scanning & ExposureNot applicable for network scanning. This is a local vulnerability — Shodan, Censys, GreyNoise, and FOFA cannot detect it. Vulnerability scanners (Qualys, Tenable, Rapid7) will need local authenticated checks against installed Python versions. Expect plugin coverage within 2-4 weeks.
Disclosure TimelineReserved: 2026-06-15. Published: 2026-09-29. Fix PR: #157580 by StanFromIreland, reviewed by encukou.
Reporter & CreditsReporter: Bitshift. Coordinator: Stan Ulbrych (StanFromIreland). Fix developer: Petr Viktorin (encukou). Disclosed responsibly through CPython's security process.

Sources.

  1. CPython Issue #157579 — Race condition in tempfile.TemporaryDirectory
  2. CPython Fix PR #157580 — fd-based cleanup to prevent symlink following
  3. CVE-2026-12345 Detail — cve.threatint.com
  4. CVE-2026-12345 Exploits & Severity — Feedly
  5. CVE-2026-12345 Threat Intelligence — OffSeq Radar
  6. Strix.ai CVE-2026-12345 Analysis
  7. Python Version Support Schedule — devguide.python.org
  8. CVE-2023-6597 (predecessor) — cvedetails.com
05 · The Call

Final Verdict
= UNCHANGED to MEDIUM (5.5/10)

Why this verdict

  • Local-only attack vector eliminates remote mass exploitation. The attacker must already have shell access on the target host. This is a post-initial-access primitive, not an initial access vector. For a 10,000-host fleet, this means the attacker has already breached your perimeter and landed on a specific box before this CVE becomes relevant.
  • Race condition adds real friction. The TOCTOU window is narrow and requires the cleanup to hit the error-handling code path (not the default happy path). There is no public exploit or weaponized tool, meaning each attacker must develop custom tooling. This is a deterministic friction multiplier that suppresses opportunistic exploitation.
  • Impact ceiling is file deletion and permission clobbering, not code execution. Even a successful exploit cannot achieve RCE, privilege escalation to shell, or lateral movement directly. The attacker can destroy files or reset permissions — impactful for availability and integrity, but the blast radius is confined to the single host.
  • No PoC, no KEV, no campaigns. One day post-disclosure with zero exploitation evidence, no public PoC, and no scanner coverage yet. The real-world threat pressure is near zero today.
  • Role multiplier: CPython is deployed on virtually every host class — workstations, CI/CD runners, app servers, ML training nodes, and data tiers. However, the attack requires a *multi-user scenario* where an unprivileged attacker shares a host with a privileged Python process using TemporaryDirectory. On CI/CD runners, this could occur on shared runners where build jobs run under different UIDs — a successful exploit could delete build artifacts or cached dependencies (host-level blast radius, not supply-chain). On production app servers, single-tenant containers or dedicated VMs eliminate the multi-user prerequisite entirely. On domain controllers and identity infrastructure, Python is rarely the primary runtime. The worst plausible high-value-role outcome is host-level data destruction on a shared CI runner — significant but not domain/fleet/supply-chain scale. This does NOT trigger the HIGH floor because the outcome in realistic high-value deployments stays at host level, and the multi-user prerequisite filters out the vast majority of production deployments.

Why not higher?

Upgrading to HIGH would require either a network-reachable attack vector or a code execution outcome, and this CVE has neither. The local-only, race-dependent, destruction-only impact profile is textbook MEDIUM. Even considering CPython's ubiquity, the multi-user prerequisite and the file-deletion-only impact cap the blast radius at host level in every deployment role analyzed. No exploitation evidence or PoC existence supports elevated urgency.

Why not lower?

Dropping to LOW would undercount the integrity and availability impact when the race IS won on a multi-user system running privileged Python. A successful exploit against a root-owned cleanup process can destroy arbitrary files — including OS configuration, database files, or application state. The predecessor CVE-2023-6597 was rated 7.8 HIGH under CVSS 3.1, and while CVSS 4.0 scoring is stricter, the underlying destructive capability is real. CPython's massive installed base means even a narrow attack scenario affects a non-trivial absolute number of hosts.

06 · Verification

Crowdsourced verification payload.

Run directly on each target host using the system Python you want to check. Invoke as: python3 check_cve_2026_12345.py — no arguments, no privileges required. Exit code 0 means PATCHED, 1 means VULNERABLE, 2 means UNKNOWN.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# CVE-2026-12345 checker - tempfile.TemporaryDirectory race condition
# Run on target host: python3 check_cve_2026_12345.py
# No special privileges required.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
import sys

def main():
    v = sys.version_info
    impl = getattr(sys, 'implementation', None)
    name = impl.name if impl else 'unknown'
    print(f"[*] Interpreter: {name} {v.major}.{v.minor}.{v.micro}")
    if name != 'cpython':
        print("UNKNOWN - this check targets CPython only")
        sys.exit(2)
    # Python 3.15.0+ has the fix on the main branch
    if (v.major, v.minor) >= (3, 15):
        print("PATCHED - Python >= 3.15.0 includes the fix")
        sys.exit(0)
    # EOL versions will not receive backports
    if (v.major, v.minor) < (3, 10):
        print("VULNERABLE - EOL Python, no backport expected")
        sys.exit(1)
    # For 3.10-3.14, detect if the backported fix is present
    # The fix introduces dir_fd-based operations in TemporaryDirectory._cleanup
    try:
        import tempfile
        import inspect
        src = inspect.getsource(tempfile.TemporaryDirectory._cleanup)
        if 'dir_fd' in src:
            print("PATCHED - backported fix detected in TemporaryDirectory._cleanup")
            sys.exit(0)
    except Exception as e:
        print(f"[!] Could not inspect cleanup source: {e}")
    print(f"VULNERABLE - Python {v.major}.{v.minor}.{v.micro} (no backport detected)")
    sys.exit(1)

if __name__ == '__main__':
    main()
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously