What Is a Python .pth File and Why Attackers Use It
Security Beginner 4 min read

What Is a Python .pth File and Why Attackers Use It

A Python pth file runs code every time the interpreter starts. Learn how .pth and sitecustomize hooks work, why attackers abuse them and how to inspect them.

Published: October 25, 2026 • Updated: October 25, 2026
pythonstartup hookssupply chainmalware persistence

A Python .pth file is a small text file in a site-packages directory that the interpreter reads every time it starts. Most lines just add directories to sys.path, but any line beginning with import is executed. That makes the python pth file a startup hook: code placed there runs before your program does, even if nothing imports the package that installed it. The malicious LiteLLM 1.107.0 release used exactly this.

What is a Python .pth file?

A .pth file is a path configuration file that Python's site module processes automatically at startup. Each line normally names a directory to append to the module search path, and lines that start with the word import followed by a space or tab are executed as code.

The site module looks for these files in the site-packages directories built from sys.prefix and sys.exec_prefix: lib/site-packages on Windows and lib/pythonX.Y/site-packages on Unix and macOS. Blank lines and lines starting with # are skipped. Directories that do not exist are never added.

The legitimate uses are mundane. Editable installs and some namespace packages use .pth files to point Python at source trees. A few tools use the import line feature to set up instrumentation or coverage when the interpreter starts.

How does sitecustomize fit in?

After processing .pth files, Python tries to import a module named sitecustomize, which an administrator can place in site-packages to run site-wide setup code. If user site-packages are enabled, it then tries usercustomize from the user's own directory.

Together these give two places where code runs on every interpreter start, for every script, every test run and every pip command. Neither requires your application to import anything.

Why do attackers use .pth files?

Attackers use .pth files because they guarantee execution without depending on the victim calling a specific function. A malicious package does not need its code to be imported. It only needs a file to land in site-packages.

In the LiteLLM supply chain attack, the poisoned wheels dropped a cook.pth file, a cook.py module and a sitecustomize.py. LiteLLM's incident post warns that any Python start, including running pip to fix the problem, reruns those files. That changes the order of cleanup: you remove the startup files and stop the background process before you run pip again.

Three properties make this technique effective:

  • It survives package removal. Uninstalling the package may not remove every file it dropped, especially if the files sit outside the package's own directory.
  • It runs in tools you trust. Linters, test runners and pip itself all start the interpreter, so a developer cleaning up can trigger the payload.
  • It hides in plain sight. Real environments contain dozens of .pth files, and reviewers rarely open them.

How to inspect a Python environment for startup hooks

Start by asking the interpreter where it looks. Running the site module with no arguments prints sys.path, the user base and user site directories, and whether user site-packages are enabled:

python -m site

Then review the startup files in each listed site-packages directory:

  1. List every .pth file and open each one. Lines that only name directories are path entries. Any line starting with import executes code and deserves a reason.
  2. Check for sitecustomize.py and usercustomize.py, plus compiled copies under __pycache__. Some distributions and corporate images ship a legitimate sitecustomize, so compare against a known-good build of the same image rather than deleting on sight.
  3. Compare the files against the packages that are supposed to own them. A .pth file that no installed package claims is a strong signal.
  4. Look for processes started by Python that outlive the command that launched them.

If you need to start Python without running startup code while you investigate, the -S option suppresses the automatic import of the site module, so .pth files and sitecustomize are not processed. The -s option disables only the user site-packages directory.

What is changing in newer Python versions?

The Python documentation marks import lines in .pth files as deprecated starting in Python 3.15. They still execute during the deprecation period, with a diagnostic shown only when you run with -v, and the docs describe later releases in which those lines are ignored.

That will narrow the technique over time, but production fleets run older interpreters for years. sitecustomize is not part of that deprecation, so it remains a startup hook either way.

Prevention beats inspection

Inspection catches a bad package after it lands. Stopping it from landing is cheaper. Pin exact versions and verify hashes so an unexpected release cannot install silently; the steps are in our guide to pinning Python dependencies with pip hash checking. Build environments from scratch rather than upgrading in place, so a rebuild from a known lockfile is your standard recovery.

If a bad package did run, assume every secret the process could read is gone. Our earlier guide on why to rebuild before you rotate credentials explains the order, and the companion piece on revoking a leaked API key covers what rotation has to include.