Migrating tools to Python 3.13 on Kali Linux: from TLS handshakes to c-api changes
Back to blog

Migrating tools to Python 3.13 on Kali Linux: from TLS handshakes to c-api changes

10/14/2026 · 4 min · Development

Anyone using rolling-release distributions like Kali Linux or upgrading environments to Python 3.13 has likely experienced legacy scripts breaking in cascade. The Linux ecosystem evolves rapidly: stricter cryptographic standards in user-space and deep structural shifts in the CPython runtime often invalidate tools relying on outdated dependencies and rigid build assumptions.

In this guide, we walk through resolving a real-world sequence of errors when deploying a security auditing tool: starting with TLS handshake failures during Git clones, moving through externally managed environment restrictions (PEP 668), and addressing internal Python 3.13 C-API breaking changes and deprecated standard library modules.


1. TLS certificate validation failures during Git clones#

Attempting to clone a repository via Git failed immediately before any transfer began:

fatal: unable to access 'https://github.com/mxrch/GitFive/': Problem with the SSL CA cert (path? access rights?)

What happens under the hood#

The git clone command spawns the git-remote-https helper binary, dynamically linked to libcurl.so, which relies on OpenSSL or GnuTLS to validate certificates.

This error indicates neither a network outage nor a DNS failure, but an inability of the TLS engine to locate, read, or parse the operating system's root CA certificate bundle.

Syscall tracing with strace#

To determine whether the issue was a missing path (ENOENT) or a permissions problem (EACCES), we traced file open calls:

GIT_CURL_VERBOSE=1 strace -f -e trace=open,openat git clone https://github.com/mxrch/GitFive 2>&1 | grep -E "cert|ca-"

If the trace returns -1 ENOENT, the configured path is missing. To rebuild and re-index the distribution's trusted certificate store:

sudo apt-get install --reinstall ca-certificates -y
sudo update-ca-certificates --fresh

The --fresh flag deletes broken symlinks inside /etc/ssl/certs/ and reconstructs hash indexes from scratch.


2. Scope isolation failures building pillow on Python 3.13#

Once Git was restored, package installation stalled while generating metadata for older Pillow releases:

  File ".../setuptools/build_meta.py", line 317, in run_setup
    exec(code, locals())
  File "<string>", line 26, in get_version
KeyError: '__version__'
ERROR: Failed to build 'Pillow' when getting requirements to build wheel

Internal changes in Python 3.13#

Legacy Pillow setup.py scripts opened src/PIL/_version.py and called exec(code, locals()) to dynamically extract version numbers.

In Python 3.13, local scope handling in optimized code blocks was tightened for performance and isolation. Variables created inside exec() no longer leak into the caller's dictionary under build_meta. When setup.py queries __version__, it encounters an empty dictionary and raises a KeyError.

How to fix it#

Upgrade to modern Pillow releases (10.0 or later), which adopt static metadata definitions via pyproject.toml:

pip install "Pillow>=10.0.0"

3. Managing system package restrictions under PEP 668#

Installing packages system-wide triggered a safety barrier:

error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install python3-xyz...
hint: See PEP 668 for the detailed specification.

The purpose of PEP 668#

Debian-based distributions include /usr/lib/python3.13/EXTERNALLY-MANAGED. The goal is to prevent pip from overwriting files inside /usr/lib/python3/dist-packages/, protecting core operating system utilities managed by apt.

  1. Virtual environment (recommended):
   python3 -m venv .venv
   source .venv/bin/activate
   pip install -r requirements.txt
  1. Native distribution packages:
   sudo apt install python3-pil python3-requests
  1. Forced installation (containers and disposable testing only):
   pip install -r requirements.txt --break-system-packages

4. Sha-1 signature rejection in APT via sequoia PGP#

When switching to native system packages, apt update threw a verification error against a third-party PPA:

Error: https://ppa.launchpadcontent.net/... InRelease
  Sub-process /usr/bin/sqv returned an error code (1):
  Signing key on ... is not bound: SHA1 is not considered secure since 2026-02-01

The sha-1 deprecation policy#

Modern distributions delegate repository signature checks to /usr/bin/sqv (Sequoia OpenPGP Verifier), which strictly rejects SHA-1 signatures due to known collision risks. When a third-party repository uses deprecated keys, apt update refuses synchronization to protect repository integrity.

Removing the conflicting repository#

Locate the offending configuration file:

grep -r "kelebek333" /etc/apt/sources.list*

Delete the obsolete list file and clear local package caches:

sudo rm /etc/apt/sources.list.d/kelebek333-*.list
sudo apt-get clean
sudo apt-get update

5. C++ build failures against the Python 3.13 c-api#

When building packages like levenshtein==0.20.5, g++ compilation aborted with missing structure members:

error: ‘PyThreadState’ has no member named ‘curexc_type’
error: ‘PyLongObject’ has no member named ‘ob_digit’

What changed in the c-api#

As part of CPython's free-threading and multi-interpreter roadmap, several internal data structures previously exposed in public headers were privatized or removed:

C++ code generated by older Cython versions attempting direct member access fails compilation on Python 3.13.

Solution#

Install the distribution's pre-compiled and patched package:

sudo apt install python3-levenshtein -y

Then remove the pinned version requirement from your requirements.txt:

sed -i '/levenshtein==0.20.5/d' requirements.txt

6. Deprecation of the cgi module and httpx updates#

Running the main application script triggered an immediate import error:

  File ".../httpx/_models.py", line 1, in <module>
    import cgi
ModuleNotFoundError: No module named 'cgi'

The impact of PEP 594#

PEP 594 removed deprecated standard library modules (the "dead batteries"), including cgi, telnetlib, and chunk.

Older httpx releases (such as 0.23.0) relied on cgi.parse_header() for header decoding. On Python 3.13, this module is gone.

Upgrading the networking stack#

Update dependencies to modern releases that parse headers without the deprecated module:

sed -i 's/httpx==0.23.0/httpx>=0.27.0/g' requirements.txt
sed -i 's/anyio==3.6.1/anyio>=4.0.0/g' requirements.txt
pip install -r requirements.txt --break-system-packages

7. Pathlib property introspection breaking trio#

In the final runtime phase, the Trio async library failed during path engine initialization:

  File ".../trio/_path.py", line 102, in generate_forwards
    raise TypeError(attr_name, type(attr))
TypeError: ('parser', <class 'property'>)

What caused the failure#

Trio reflects over pathlib.Path to dynamically create asynchronous path wrappers. In Python 3.13, pathlib introduced an internal parser property. Older Trio versions expected all attributes to be callable methods, raising a TypeError when encountering a raw property descriptor.

Upgrading Trio to release 0.27.0 or higher fixes the introspection logic:

sed -i 's/trio==0.21.0/trio>=0.27.0/g' requirements.txt
pip install -r requirements.txt --break-system-packages

Consolidated setup playbook#

To apply all fixes in one automated sequence:

#!/usr/bin/env bash
set -euo pipefail

echo "[*] 1. Rebuilding certificates and updating package repositories..."
sudo apt-get clean
sudo apt-get update
sudo apt-get install --reinstall ca-certificates -y
sudo update-ca-certificates --fresh

echo "[*] 2. Installing pre-compiled packages for Python 3.13..."
sudo apt-get install python3-pil python3-levenshtein python3-dev build-essential -y

echo "[*] 3. Updating version constraints in requirements.txt..."
sed -i '/Pillow==/d' requirements.txt
sed -i '/levenshtein==/d' requirements.txt
sed -i 's/httpx==0.23.0/httpx>=0.27.0/g' requirements.txt
sed -i 's/anyio==3.6.1/anyio>=4.0.0/g' requirements.txt
sed -i 's/trio==0.21.0/trio>=0.27.0/g' requirements.txt

echo "[*] 4. Installing remaining dependencies..."
pip install -r requirements.txt --break-system-packages

echo "[*] 5. Verifying module imports..."
python3 -c "import trio, httpx, PIL; print('[✔] Python 3.13 environment ready!')"

Practical guidelines for maintaining legacy tools on Python 3.13#

  1. Avoid overly strict version pinning: locking requirements with == rather than >= is the leading cause of migration failures. Older compiled packages rarely build against newer CPython C-API internals.
  2. Prioritize distribution-packaged binaries: packages requiring C/C++ compilation (like Pillow, Cryptography, and Levenshtein) build and run far more reliably when installed via apt, where upstream maintainers have already applied necessary compatibility patches.
  3. Use virtual environments for project isolation: relying on venv prevents conflicts with PEP 668 and keeps core system libraries safe from unintended modifications.

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments