Lifecycle scripts
Packages can define lifecycle scripts such as preinstall, install,
postinstall, and prepare. scpm treats root scripts and dependency scripts
differently.
Root scripts
Root package scripts run during install unless scripts are ignored:
scpm install --ignore-scripts
Dependency scripts
Dependency lifecycle scripts follow the pnpm v11 build approval model. Packages must be explicitly allowlisted before their install-time scripts run.
scpm ignored-builds
scpm approve-builds
scpm rebuild
Supported policy fields — scpm reads all of these at install time:
In scpm-workspace.yaml or pnpm-workspace.yaml (pnpm v11's build-review
map, and what scpm writes to — scpm creates scpm-workspace.yaml from
scratch, but mutates an existing pnpm-workspace.yaml in place):
allowBuilds:
esbuild: true
sharp: true
untrusted-package: false
The old onlyBuiltDependencies and neverBuiltDependencies list keys are
still honored as read-only compatibility inputs, but new approvals go into
allowBuilds. When install sees an unreviewed dependency build, it adds that
package to allowBuilds with false; scpm approve-builds flips reviewed
entries to true.
In package.json (legacy — still honored as a read source).
Every key under pnpm.* is also accepted under scpm.*; when both are
present for the same key, scpm.* wins. Disjoint entries from either
namespace merge.
{
"scpm": {
"allowBuilds": {
"esbuild": true,
"untrusted-package": false
}
}
}
Deny rules win over allow rules. Workspace-yaml entries and
package.json entries merge; you don't have to migrate a legacy
pnpm.allowBuilds to start using scpm approve-builds.
Entry keys support a bare package name (matches every version), an
exact version pin (esbuild@0.19.0), an exact version union
(esbuild@0.19.0 || 0.20.0), or a * wildcard name (@babel/*,
*-loader, or bare * for everything). Wildcards can't be combined
with a version pin — the point of a version pin is to assert a
specific build was audited, and a wildcard defeats that. Semver
ranges aren't supported for the same reason.
Jailed dependency builds
Build approval controls whether a dependency script may run at all. Jailed builds add a second boundary for approved packages:
jailBuilds: true
With jailBuilds enabled, approved dependency preinstall, install, and
postinstall scripts run with a scrubbed environment and a temporary HOME.
On macOS and Linux, scpm also applies a native jail that denies network access
and restricts filesystem writes to the package directory and temporary
directories.
jailBuilds defaults to false today and is planned to default to true in
the next major version.
For packages that need a narrow exception, grant only that privilege:
jailBuildPermissions:
"@vendor/*":
env:
- SHARP_DIST_BASE_URL
write:
- ~/.cache/sharp
For packages that cannot run in the jail yet, disable the jail for a package glob while keeping the build approval requirement:
jailBuildExclusions:
- "@legacy-native/*"
See Jailed builds for the full profile, supported permissions, and platform behavior.
Git dependencies
Git dependencies with prepare scripts get a nested install in the clone
before scpm snapshots the package. The final linked package uses the packed
result, not the raw checkout.
Side effects cache
Allowlisted dependency builds can cache their post-build package tree and reuse it on future installs with the same input hash.
Bun comparison
Bun also treats dependency scripts as a security boundary and uses an allowlist
model through trustedDependencies. scpm reads the top-level
trustedDependencies array as an additional allow-source alongside
pnpm.onlyBuiltDependencies, so bun projects work without rewriting the
manifest. pnpm.neverBuiltDependencies still wins when both sides list the
same package.
