Skip to content

Back to the catalog

dfir.chwriteup

Today I learned: Python's .start Files as a Persistence Mechanism

Explains PEP 829's new .start files in Python 3.15, which let a package register package.module:callable entries in site-packages that execute automatically every time an interpreter starts, before any user code runs. The author walks through a reproducible lab showing code execution via a .start file, contrasts it with the older executable-import .pth file technique it replaces, and gives forensic hunting commands (using python -S to avoid triggering the mechanism during investigation) to locate and audit .start files on disk.

TL;DR Python 3.15's new .start startup files (PEP 829) are a fresh persistence/code-execution artifact for DFIR to hunt, demoed with a working lab and safe (-S) investigation commands.
Document

What it is

PEP 829 introduces .start files in Python 3.15, a structured replacement for the executable import lines previously allowed inside .pth files. A .start file in a site-packages directory contains a package.module:callable reference; Python resolves and invokes that callable during interpreter startup, before any user code runs.

How it works

Python already scans site-packages directories for .pth files during site module initialization. .start files live in the same directories and are processed alongside them, in alphabetical filename order per directory. Each line in a .start file is a reference like foo.submod:initialize; Python imports foo.submod, retrieves initialize, and calls it with no arguments. Unlike .pth executable imports, the file cannot contain arbitrary statements — only these structured references — which improves auditability but does not remove pre-start code execution. A migration detail: if a .start file and a .pth file share the same base name, executable import lines in that .pth file are ignored, so finding both artifacts doesn't mean both mechanisms were active.

Preconditions / Affected

Requires Python 3.15 (final release scheduled October 9, 2026; the post was tested against 3.15.0rc3). An attacker needs write access to an applicable site-packages directory — scope depends on whether that's a single virtualenv, a user's installation, or a system-wide interpreter.

What to do

To enumerate .start files without triggering them, suppress site processing with -S:

python3.15 -S -c 'import site; print(site.getsitepackages())'

For a full hunt, the post gives a script run under -S that walks site.getsitepackages() plus site.getusersitepackages(), globs for *.start, and prints each file's reference lines. For a broader forensic sweep (application-bundled or custom venvs may not appear in the system interpreter's site directories), search the filesystem directly:

find /usr /usr/local /opt /home -type f -name '*.start' -print 2>/dev/null

When a .start file is found, the author recommends correlating its creation/modification times with the referenced module, confirming both belong to an expected package, and checking application-specific virtualenvs separately — a legitimate .start file isn't inherently suspicious; unexpected provenance or modification is the signal.

Caveats

The author states they have not found evidence of this mechanism being abused in the wild, noting Python 3.15 is still new at time of writing.

Organisations named here

  • PEP 829

Technologies named here

  • Python
  • site
  • python3.15 -S flag
  • find
  • venv

Techniques named here

  • .start file abuse as persistence/pre-execution mechanism
  • .pth file executable import line abuse