Python 3.15.2 Fixes tar_filter. Check the Cron Box First

Python 3.15.2 and four sibling releases patch tar_filter, http.cookiejar and the eMudhra keys. What that means for backup, scraper and deploy jobs.

Python 3.15.2 shipped on October 25 as an expedited security release, and it came with four siblings: 3.14.10, 3.13.18, 3.12.16 and 3.11.18. The headline fix is the eMudhra certificate mess, but two of the three security items touch code that automation scripts call all day: tarfile extraction filters and http.cookiejar. If you run Python on a box that restores backups, unpacks release tarballs or logs into vendor portals on a schedule, this is the week to look at which interpreter that box actually has.

The Python Insider post from release manager Hugo van Kemenade lists the same three fixes for all five lines. The catch for automation hosts is in one sentence: only 3.15.2 and 3.14.10 come with binary installers. The 3.13, 3.12 and 3.11 releases are source-only. A Windows task-scheduler machine pinned to 3.12 has no installer to click, so it waits for whoever packages its Python, or you build it yourself.

We covered the packaging side of this incident in pip 26.2.2 dropping emSign. This post is about the interpreter and the two standard-library modules that scheduled jobs lean on.

What changed in tar_filter, and who uses filter=“tar”

The tarfile fix is CVE-2026-114583, tracked as gh-160097. The 3.15.2 changelog describes it plainly: tarfile.tar_filter() now rejects link members whose path resolves to the destination directory itself, which tarfile.data_filter() already did. heise puts the CVSS score at 6.9 and spells out the effect: crafted link entries could get past the filter and place files outside the target directory when you extract with filter="tar".

The tarfile documentation has three named filters. fully_trusted honors everything in the archive. tar keeps most Unix filesystem features and blocks the obviously dangerous ones. data strips most Unix-specific features and is meant for cross-platform data archives. Since 3.14 the default is data; before that, the default behaved like fully_trusted.

So who picks tar on purpose? Mostly people restoring backups. A nightly job that snapshots /etc or a home directory and later restores it wants symlinks, permissions and ownership to come back. The data filter would clear user and group info and refuse links that point outside the destination, which is wrong for a full restore. The tar filter keeps those features, and that made it the natural choice for a restore script, and also the filter this CVE was about.

If your restore script runs on 3.14.9 or older with filter="tar", and the archives come from anywhere you do not fully control (a shared bucket, a customer upload, a mirror), upgrade the interpreter before the next restore. If the archives are your own and never leave your storage, the risk is lower. Upgrade anyway, since the job will eventually read an archive that someone else wrote.

Scripts that never set a filter

A separate, older issue has nothing to do with the CVE. A lot of automation code still looks like this:

with tarfile.open(path) as tar:
    tar.extractall(dest)

On 3.14 and later that call uses the data filter by default. On 3.13 and lower it behaves like fully_trusted, which means absolute paths, .. components and links outside the destination are all honored. The same script is safe on one box and unsafe on another, and nothing in the code tells you which.

The docs recommend setting the filter explicitly when you need to support 3.13 and lower, and they suggest checking hasattr(tarfile, 'data_filter') instead of the version number, because the feature was backported as a security update. For a scheduled job, the conservative version is a single line:

tar.extractall(dest, filter="data")

If the interpreter is too old to know about filters, that line raises instead of silently extracting everything, which is what you want from a cron job.

The docs also list extra checks that apply to unattended extraction: extract into a fresh temporary directory, refuse symlinks if you do not need them, and cap the number of files and total size. Those limits are worth having on a job that runs at 3 a.m. with nobody watching. 3.15 adds a new LinkFallbackError and normalizes .. components in tar_filter, so filter behavior on 3.15 is a little stricter than on 3.14 even before this patch.

Our earlier piece on uv 0.12.18 fixing a wheel path covered the same class of bug in a package installer. The tarfile change brings the standard library’s tar filter in line with the data filter on one specific link case.

The cookiejar fix and scheduled scrapers

The second fix, gh-160300, is in http.cookiejar.DefaultCookiePolicy. According to the changelog, the policy accepted and returned cookies with a negative Version attribute without checking their domain against the request host. heise adds the practical consequence: a server could set or overwrite cookies for other domains, though it could not read other domains’ cookies. After the update, such cookies are ignored.

http.cookiejar sits under a lot of automation code that never imports it by name. Session-based scrapers, scripts that log into a supplier dashboard to download a nightly CSV, and report bots that walk through several internal tools in one run all keep a cookie jar for the life of the process. A long-running job that talks to more than one host is the case to think about here. If one of those hosts is hostile or compromised, it could plant or overwrite a cookie meant for another host in the same jar.

This one has no CVE number in the release notes and a narrower impact than the other two, but it costs nothing extra: it arrives with the same interpreter upgrade. If you cannot upgrade a box this week, the cheap mitigation is to give each target host its own session object instead of reusing one jar across a whole run.

eMudhra: what the interpreter now blocks

The main fix is CVE-2026-115557, gh-160040, which heise scores at CVSS 9.4. Eight eMudhra intermediate CA certificates had private keys that were trivial to guess. heise reports that one of them was simply “4”. Anyone holding those keys could issue certificates that look valid for any domain.

The changelog explains what the ssl module now does: it rejects TLS chains that contain any of the compromised keys. The denylist matches on the SHA-256 digest of each key’s SubjectPublicKeyInfo, so cross-signed certificates that reuse a bad key are caught too. The CPython team is explicit that this is a last line of defense. Removing trust is primarily the job of the platform root store, and the built-in list only covers the gap while that update has not reached the OS, the container image or a trust store bundled inside some Python package.

For automation the useful line is on the release page: normal uses of pip, including upgrading pip itself, are now safe from attacks based on those keys even if pip has not been updated yet. That gives a fresh 3.15.2 or 3.14.10 interpreter a safe path to upgrade everything else in the environment.

Seth Larson’s notice on discuss.python.org from October 20 lists the package floors: certifi 2026.10.19, pip 26.2.2, uv 0.13.4, sigstore 4.5.1, conda 26.9.4, pixi 0.82.1 and virtualenv 21.14.12. A comment in that thread points out that pip always loads its vendored certifi alongside the system store, so fixing the OS trust store alone does not cover pip. uv users can rely on the system store with UV_SYSTEM_CERTS=true once that store distrusts the bad certificates.

Source-only releases and the boxes nobody rebuilds

Automation hosts tend to be the oldest machines in a fleet. They were set up once, they run a crontab, and nobody touches them while the reports keep arriving. That is why the source-only note matters more here than on a laptop.

If a scheduled-job host runs 3.13, 3.12 or 3.11 from python.org installers, there is no new installer for those versions. On Linux you are waiting for the distribution’s package or building from source. On Windows and macOS your realistic choices are to move the job to 3.14.10 or 3.15.2, or to build the older version yourself. Python 3.10 and older are end of life and get none of these fixes, as the Larson notice says.

The older lines also pick up fixes that 3.15.1 and 3.14.9 already had. The Python Insider post lists bundled Expat 2.9.0 for CVE-2026-77214 and CVE-2026-102633, a Windows change so mimetypes no longer reads mimetypes.knownfiles by default (CVE-2024-3220), and a header refolding fix in the email package. 3.13.18 and 3.12.16 also get gh-157579, a race in tempfile.TemporaryDirectory cleanup. heise describes the risk: an attacker could swap a subdirectory for a symlink during deletion and then delete files outside the directory or change their permissions. Jobs that unpack into a TemporaryDirectory and clean up afterwards are exactly the code that hits that path, so for those hosts the upgrade also covers the scratch-directory pattern the docs recommend for safe extraction.

Containers have the same problem. One post on X from @stackwatch_de summed up the usual trap: Docker images bake in an old Python and pip, so you need to rebuild without the layer cache once the base image is updated. Larson’s notice gives the same advice for CA bundles: rebuild with layer caching disabled. A scheduled job that runs docker run against an image tag built in September is still running September’s interpreter.

A short checklist for scheduled jobs

Start with the interpreter the job actually uses. Your shell’s python3 may not be the one that runs the job, so ask the job itself. Add a line to the job’s log on its next run:

import sys, ssl, tarfile
print(sys.version, ssl.OPENSSL_VERSION, hasattr(tarfile, "data_filter"))

Then work through the hosts in this order:

  1. Move hosts and images to 3.15.2 or 3.14.10 where you can, since those have binaries. For 3.13, 3.12 and 3.11, check whether your distribution has shipped 3.13.18, 3.12.16 or 3.11.18, or plan a move to 3.14.
  2. Search the job code for extractall( and extract(. Add filter="data" to anything that unpacks third-party archives. Keep filter="tar" only for real restores, and run those on a patched interpreter.
  3. In scrapers that hit several hosts, stop sharing one cookie jar across all of them until the upgrade lands.
  4. Upgrade pip to 26.2.2, certifi to 2026.10.19 and uv to 0.13.4 in every virtual environment the jobs use, then rebuild any container images with the layer cache off.
  5. Leave a note in the crontab or the job’s README with the interpreter version and the date you checked it.

Most of the work is an interpreter upgrade and an image rebuild on machines that nobody logs into until a report fails to arrive. If you already moved your notebooks and services to 3.15 when 3.15.0 landed after RC3, the cron box is probably the one you missed.

Spread The Article

Share this guide

Send this article to your network or keep a copy of the direct link.

X Facebook LinkedIn Reddit Telegram

Discussion

Leave a comment

No comments yet

Be the first to start the conversation.