June 6, 2024·4 min read·engineeringpythoncpythonwindowssecurity

Python 3.12.4 makes private temporary directories explicit on Windows

Python 3.12.4 gives Windows directory creation a specific interpretation of mode 0o700. The fix connects tempfile's privacy promise to the Windows access-control mechanism.

A temporary directory's unpredictable name does not establish who can read its contents. Its permissions still have to provide the promised isolation. Python 3.12.4, released June 6, includes a Windows correction that makes this distinction concrete. os.mkdir now handles mode 0o700 through an explicit security descriptor, fixing temporary-directory exposure when the base directory has unusually permissive access.

The release contains more than 250 bug fixes, build improvements, and documentation changes. Its tagged security notes identify this directory change as the correction for CVE-2024-4030. The relevant condition is more specific than every Windows temporary directory being exposed. The notes identify scenarios where the base temporary directory is more permissive than the default.

I appreciate Python's context managers because they make resource lifetime readable at the point of use. Temporary-directory privacy is a separate obligation underneath that convenience. A context manager can arrange eventual cleanup while an incorrect access policy still exposes files during their lifetime. This patch addresses the access policy applied when the directory comes into existence.

The caller already requested a private directory

The 3.12.4 implementation of tempfile.mkdtemp states that the created directory is readable, writable, and searchable only by its creating user. It generates candidate names and calls os.mkdir with 0o700. If a candidate already exists, it tries another. The function returns the absolute pathname after successful creation and leaves deletion to its caller.

Those operations address different problems. Trying another candidate avoids treating an existing path as a newly created directory. Passing a restrictive mode requests privacy for the new object. Returning a name provides the caller with a location to use. Correct handling of collisions does not compensate for a platform implementation that fails to apply the intended access restrictions.

That separation is useful when evaluating a standard-library security guarantee. The high-level helper can request the right policy while the operating-system wrapper interprets it incompletely. An application using the documented helper has no reason to recreate the wrapper's implementation. The correction belongs where the portable request becomes a native directory-creation operation.

The Windows branch installs an access-control list

The patched os.mkdir implementation has a special branch for mode 0700. It converts a Windows security-descriptor string into the attributes supplied to CreateDirectoryW. The descriptor defines a protected discretionary access-control list. Its entries grant full control to the owner, SYSTEM, and the Administrators group, with inheritance flags for child objects.

This is a specific translation of a useful portable request into Windows security machinery. It is not a general implementation of all Unix permission combinations. The equality check for 0700 matters because it identifies exactly which mode activates the new behavior. The code then creates the directory with the resulting attributes and releases the allocated descriptor. Conversion and cleanup errors have explicit handling.

The privileged entries also matter when describing the result. Calling the directory private to its owner should not imply that Windows administrative authority has disappeared. The code gives named system and administrative principals access. Its purpose is to prevent broad access inherited from an unsuitable parent directory, while retaining the access expected by those privileged principals.

Application code can retain the high-level interface

The standard-library pattern remains straightforward:

from tempfile import TemporaryDirectory

with TemporaryDirectory(prefix="processing-") as workdir:
    print(workdir)

Here workdir names a temporary working directory whose lifetime follows the context. This example prints the path without placing sensitive information in it. Creation policy and cleanup policy remain separate aspects of the helper. The same tagged tempfile source shows TemporaryDirectory using mkdtemp during initialization and cleaning up on context exit.

Cleanup cannot substitute for access control during processing. A workspace can contain intermediate data for the entire duration of an operation, and successful deletion at the end says nothing about who could inspect it beforehand. Applying the descriptor during directory creation addresses that interval directly, before the caller begins placing files in the workspace.

For applications using a custom temporary root, the patch's condition is particularly relevant. Choosing that root changes the parent under which the helper creates directories. The helper's promise should remain meaningful there, rather than depending entirely on the default root having restrictive permissions. An explicit creation policy reduces that dependence at the point where each new directory is allocated.

I prefer this kind of repair to application code that tries to reconstruct platform security behavior around every temporary workspace. Python's value here is a shared implementation of a common operation. The new Windows branch gives the existing 0o700 request an access-control implementation suited to the platform, allowing the high-level directory helper to preserve its intended privacy under a permissive parent.