Python normally prepares an import path before the application starts. For scripts and -m execution, that path can contain the script directory or the current working directory. The behavior is convenient during development and risky when a service runs from a writable directory or a shared workspace.

What -P changes

The Python 3.12 command-line documentation describes -P as a way to avoid prepending a potentially unsafe path to sys.path. When the command uses python -m module, -P stops Python from prepending the current working directory. For python script.py, it stops Python from prepending the script's directory, including resolving a symbolic link before the decision. The PYTHONSAFEPATH environment variable provides the same control.

This does not remove every import path. The installed standard library, installed site packages, and explicit PYTHONPATH entries still participate unless they are excluded by other options. The isolated mode flag -I also disables user site packages and ignores PYTHON* environment variables, which is a larger change.

The practical threat is straightforward: a process runs from a directory that another user or a compromised build job can write. A file with the same name as a standard or third-party module shadows the intended package, and the process imports attacker-controlled code before the application logic begins.

Why service directories are a weak trust boundary

Many deployment scripts start with cd /srv/app and then run python manage.py or python -m worker. If /srv/app is writable by the application, a package manager, or a compromised deployment user, the script directory contains files that the Python interpreter will consider before the normal installation.

The risk becomes clearer when the process handles a release artifact or a user-uploaded file. A service that changes into a job directory and launches a short script inherits that directory as an import location. The attacker does not need a shell. A file named like an imported dependency can be enough, provided the process later imports that module.

The same issue appears in developer laptops and CI runners. A repository checkout is normally writable by the checkout process, and the checkout can contain files that are not reviewed as executable code. -P makes the launch directory less authoritative, but it does not validate the code that the deployment process deliberately installs.

Apply the control where launches are defined

Set the flag in the service definition, wrapper script, or scheduler rather than asking every developer to remember it:

ExecStart=/usr/bin/python3 -P /opt/app/worker.py

For environments where an application must import local, reviewed modules, add the intended path explicitly with a directory owned by the application and protected from untrusted writes. Then test the result:

python -P -c "import sys; print(sys.path)"
python -P -c "import json; print(json.__file__)"

The second command should show the standard-library location, not a file in the current directory. Run with the same user and working directory as the real service. A test from an administrator's home directory does not prove that a temporary job directory is safe.

When the application needs a stronger boundary, use -I and pass configuration through explicit arguments or environment variables that the service controls. Review PYTHONPATH separately. A safe-path flag is not a sandbox, and it cannot protect a process that is designed to load plug-ins from an untrusted directory.

Review the remaining import surface

Search launch configurations for bare python, python3, and PYTHONSAFEPATH usage. Record the working directory and the owner of every path that can be imported. Add a startup check that logs the resolved module file for security-critical imports. Avoid logging full environment variables, which may contain credentials.

Finally, keep dependency installation and runtime launch as separate controls. -P reduces shadowing at launch, while hash-checked packages and a controlled package index reduce tampering in the installed environment. Neither control substitutes for the other. Together they make the first few seconds of a Python service more predictable.

References