Package managers need to install code. Sometimes they also need to run code during installation: compiling native extensions, generating configuration, or setting up platform-specific files. This install-time code execution is a fundamental supply chain attack surface, and each package ecosystem handles it differently.
Understanding these differences matters because your project probably uses multiple package managers, and the weakest one defines your overall risk level.
The Spectrum of Install-Time Execution
Package managers fall on a spectrum from fully permissive to fully restrictive regarding install-time code execution:
Fully permissive: npm, pip (with setup.py), RubyGems -- arbitrary code runs during installation by default.
Partially restricted: Cargo (build.rs runs but only during compilation), NuGet (.NET install scripts deprecated).
Restrictive by design: Go modules (no install-time execution), Deno (no install-time execution).
npm: The Most Permissive
npm allows arbitrary shell commands in lifecycle scripts: preinstall, install, postinstall, prepare, prepublish. These run automatically during npm install.
Any package in your dependency tree can have lifecycle scripts. Transitive dependencies' scripts run too. There is no built-in sandboxing, capability restriction, or approval mechanism.
Risk level: Critical. The majority of JavaScript supply chain attacks use install scripts.
Mitigations available: --ignore-scripts flag, .npmrc configuration, package-lock.json auditing.
pip: Legacy Risk
Python's setup.py is an arbitrary Python program that runs during installation of source distributions. The newer PEP 517 build system (pyproject.toml) still executes build backends, though the build backends themselves are more constrained.
Wheel installations do not execute any package code. The risk is concentrated in source distributions.
Risk level: High for source distributions, low for wheels.
Mitigations available: --only-binary, --no-build-isolation (paradoxically safer when used with trusted build environments), private registries with pre-screening.
RubyGems: Similar to npm
Gem files can contain extension installers (extconf.rb) that run during installation. These are typically used for compiling C extensions but can execute arbitrary Ruby code.
The Gem specification also supports post_install_message which is not code execution but can be used for social engineering.
Risk level: High for gems with extensions, low for pure Ruby gems.
Mitigations available: --ignore-dependencies, Bundler's frozen mode, private gem servers.
Cargo: Controlled Build Scripts
Rust's build scripts (build.rs) execute during cargo build, not during cargo install of the dependency. This means the code runs during compilation rather than during package download, which is a slightly different trust boundary.
Build scripts run with full privileges but only when the crate is being compiled. Dependencies that are downloaded but not compiled (feature-gated, for example) do not run their build scripts.
Risk level: Medium. Build scripts are a real risk but the attack window is narrower than npm's.
Mitigations available: cargo-vet for auditing, cargo-deny for policy enforcement, sandbox builds.
Maven: Plugin-Based Execution
Maven does not have install scripts in the npm sense. Instead, Maven plugins execute during the build lifecycle. The distinction is that plugins must be explicitly declared in the POM, so there is no automatic execution from arbitrary dependencies.
However, Maven dependencies can include resources and configuration that affect build behavior, and Maven plugins themselves have unrestricted access.
Risk level: Low for dependencies, high for plugins.
Mitigations available: Plugin version pinning, dependency verification, repository managers.
Go: No Execution by Design
Go modules do not support any form of install-time code execution. go get downloads source code and verifies checksums. No code runs until you explicitly build or run the module.
Go's go generate command can run arbitrary code, but it must be invoked explicitly and is not part of the standard build process.
Risk level: Low. Go's design explicitly avoids install-time execution.
Mitigations available: Checksum database verification, GONOSUMCHECK for private modules.
NuGet: Evolved Away
NuGet (.NET) previously supported PowerShell install scripts (install.ps1, init.ps1). These were deprecated in favor of MSBuild targets and props files, which are more declarative and less powerful.
PackageReference format (the modern default) does not support install scripts. The older packages.config format still does.
Risk level: Low with modern PackageReference, medium with legacy packages.config.
Mitigations available: Use PackageReference format, NuGet signature verification, package source mapping.
Deno: No Package Installation
Deno uses URL-based imports with no installation step. There is no node_modules, no install scripts, and no build-time code execution during dependency resolution. Modules are downloaded, cached, and verified against integrity hashes.
Risk level: Very low for install-time attacks. Other attack vectors exist (e.g., module content changes at URL).
Cross-Ecosystem Defense Strategy
For projects using multiple package managers:
-
Identify which ecosystems have install-time execution. Map your project's dependency on each package manager and assess the install-time risk of each.
-
Apply ecosystem-specific mitigations. Disable install scripts in npm, prefer wheels in pip, audit Gradle plugins, pin Cargo build-dependency versions.
-
Sandbox the build environment. Regardless of individual package manager mitigations, run the entire build in an isolated environment.
-
Monitor all dependency trees. SCA tools that only cover one ecosystem leave gaps. Ensure your monitoring covers every package manager your project uses.
How Safeguard.sh Helps
Safeguard.sh provides unified dependency monitoring across package managers. Whether your project uses npm, pip, Maven, Cargo, or any combination, Safeguard.sh generates comprehensive SBOMs and monitors for vulnerabilities across all dependency trees. Our platform identifies packages with install-time execution capabilities and flags known supply chain compromises regardless of the ecosystem they occur in.