Per-user installs are safer when the environment allows them because they reduce the chance that one user can stage files for another user’s interpreter session. Shared installs should only exist where the parent directory structure is tightly controlled and privileged launches are isolated from user-writeable paths.
Why per-user Python installs are safer on Windows
Per-user installs reduce the blast radius of a Python runtime on Windows because the interpreter, packages, and script entry points live under a user-owned profile instead of a shared location. That makes it harder for one user to plant or modify files that another user’s Python session will later load, which is exactly the kind of cross-user staging problem shared installs can create.
This is not about convenience alone. The security question is whether a lower-privilege user can influence what a different user executes. When the runtime is shared and the parent path or package directories are writable by standard users, the trust boundary is already weakened before Python even starts.
For adjacent identity and access considerations, a shared runtime behaves like a multi-tenant asset: one user’s write access can become another user’s execution path. That is why a safer default is to make the install scope match the account scope, then treat any wider reach as an explicit exception with compensating controls.
When a shared install becomes a security problem
A system-wide install becomes risky when standard users can write to directories that the interpreter, launcher, or site-packages path will consult. In that situation, an attacker does not need to replace the interpreter itself to cause harm, they only need to influence import resolution, startup scripts, PATH precedence, or package contents.
Shared installs are especially weak when privileged launches and interactive user sessions share the same filesystem tree. If an elevated process uses the same Python installation as an unprivileged user, any writable ancestor directory, search-path ambiguity, or stale package location can become a privilege boundary issue. The right mental model is not “one Python for everyone”, it is “one execution trust zone per audience”.
That is why Windows hardening should focus on directory ownership, ACLs, and launch context, not just on package versions. If the system install must exist, it should be tightly controlled, with user-writeable paths removed from the interpreter’s effective search chain and with administrative launches isolated from user-modifiable areas.
How to decide between the two models
Use per-user installs when developers or automation can operate without a shared runtime, because it is the cleaner default and usually the safer one. Prefer a shared install only when there is a clear operational reason, such as a managed workstation image, central tooling, or a dependency set that must be identical across accounts.
The decision rule is simple: if users do not need to collaborate through the same runtime, do not give them the same runtime. If you must share, then the parent directory structure, package directories, and launcher locations should be admin-owned and non-writable by standard users, and elevated execution should come from a controlled administrative context, not a user profile.
For teams that manage Python at scale, the practical test is whether you can prove that one user cannot alter what another user’s interpreter will import or execute. If you cannot prove that, the shared install is too permissive, even if it appears to work.
Risk and Threat Considerations
Shared Python installs on Windows can turn ordinary file-write access into cross-user code execution, especially when PATH order, import search paths, or package directories are writable by non-admin users. The risk is not limited to malware, because benign-looking package files, helper scripts, or leftover artifacts can still change what a later session runs.
Failure mechanism: A lower-privilege user stages or modifies a file in a location that a different user’s interpreter session trusts, then the higher-trust session loads or executes it through normal Python startup, module resolution, or launcher behaviour.
Impact: The result can be unauthorized code execution, tampering with developer workflows, cross-user data exposure, or privilege escalation if an elevated process depends on the shared runtime.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
MITRE ATT&CK addresses the attack surface, CIS Controls v8 and NIST SP 800-53 Rev 5 set the technical controls, and ISO/IEC 27001:2022 defines the regulatory obligations.
| Framework | Control / Reference | Relevance |
|---|---|---|
| CIS Controls v8 | CIS-5 — Account Management | Controls who can write to shared Python paths and launch locations. |
| Recommendation — Restrict writable paths and administrative access to the shared interpreter tree. | ||
| NIST SP 800-53 Rev 5 | CM-6 — Configuration Settings | Shared installs depend on hardened interpreter paths and directory settings. |
| AC-6 — Least Privilege | Per-user installs reduce cross-user influence by limiting shared write access. | |
| Recommendation — Lock down Python search paths and directory permissions as approved configuration. Apply least privilege so standard users cannot modify another user’s execution path. | ||
| ISO/IEC 27001:2022 | A.8.9 — Configuration management | Install scope and directory ownership are configuration decisions with security impact. |
| Recommendation — Manage Python install scope and filesystem permissions as controlled configuration. | ||
| MITRE ATT&CK | T1574 — Hijack Execution Flow | Writable shared paths can let an attacker redirect what Python loads or runs. |
| Recommendation — Hunt for writable-path abuse that can redirect interpreter execution. | ||
Practitioner Guidance
What to verify: Confirm which directories the interpreter and launchers actually read from, then check whether any of those paths are writable by standard users. The install is only defensible if the effective execution path is closed to cross-user modification.
Common mistake: Treating “installed for all users” as a packaging choice instead of a security boundary. That assumption fails as soon as a writable ancestor directory, user-level PATH entry, or per-user package location can influence another account’s session.
What good looks like: Per-user installs for normal development, centrally managed shared installs only for locked-down machines, and explicit separation between unprivileged user sessions and any elevated Python execution.
Practitioner takeaway: Default to per-user installs, then allow shared installs only when you can show that no non-admin user can steer another user’s interpreter path or package loading.
Related resources from NHI Mgmt Group
- When should organisations keep permissions in a relational database versus move to a dedicated authorization system?
- Should organisations keep graymail controls in end-user portals or move them into central email security workflows?
- How should organisations move away from password-based authentication without hurting user productivity?
- How should organisations decide whether to keep Nessus or move to a broader platform?