LPM-cli

lpm rebuild

Run lifecycle scripts for installed packages — the second step of the install pipeline.

lpm rebuild [packages...]

lpm install downloads and links packages without running any lifecycle scripts. lpm rebuild is the second step — it selectively runs lifecycle scripts based on the active script policy and the project's trustedDependencies.

Matches npm rebuild / pnpm rebuild semantics.

What runs

lpm rebuild executes exactly three phases, in order:

  1. preinstall
  2. install
  3. postinstall

Other lifecycle names — prepare, prepublishOnly, preuninstall, uninstall, postuninstall — are recognized by LPM for detection (a package declaring them shows up in lpm audit and the lpm approve-scripts review queue) but never executed by the install pipeline. Approving a package whose only declared script is prepare is harmless: nothing runs.

Examples

lpm rebuild                                  # rebuild every trusted package
lpm rebuild esbuild                          # rebuild a specific package
lpm rebuild esbuild sharp                    # multiple
lpm rebuild --dry-run                        # preview, run nothing
lpm rebuild --force                          # re-run scripts even for already-built packages
lpm rebuild --all                            # bypass trust policy — DANGEROUS
lpm rebuild --policy=allow                   # allow every scripted package this run
lpm rebuild --triage                         # tiered gate (greens auto, ambers/reds blocked)
lpm rebuild --timeout 600                    # 10-minute per-script timeout

Human output reports each completed lifecycle phase as one compact row and keeps the script command body out of the transcript:

 Rebuilding lifecycle scripts for trusted packages
 esbuild@0.25.1  postinstall
 sharp@0.34.2    install

 Completed 2 scripts
  skipped: 1 blocked package
  hint: run lpm approve-scripts

 Done · rebuild finished in 1.04s

When you name packages explicitly, every requested package must be installed and must have an executable lifecycle script. A typo or an installed package with no preinstall / install / postinstall exits non-zero instead of silently succeeding as an empty rebuild.

Default selection

With no arguments and no --all, lpm rebuild runs scripts for packages whose name appears in:

  • package.json > lpm > trustedDependencies (legacy array form), OR
  • package.json > lpm > trustedDependencies (rich-map form, with matching integrity + script hash), OR
  • A scope glob in package.json > lpm > scripts.trustedScopes (e.g. @myorg/*)

Manage that allowlist with lpm trust and lpm approve-scripts.

Policy override

The CLI policy flags share semantics with lpm install — they govern which scripted packages are eligible to run.

FlagBehavior
--policy=deny (default)Filter to trustedDependencies only
--policy=allow / --yoloInclude every scripted package regardless of trust
--policy=triage / --triageFilter to trusted-only, but tiered greens are auto-promoted into the rebuild set

--all overrides the filter under every policy.

--policy, --yolo, and --triage are mutually exclusive.

Sandbox

Lifecycle scripts run inside a filesystem sandbox by default — Seatbelt on macOS, landlock on Linux, AppContainer + Job Object on Windows. Three modes:

ModeFilesystemEnvOutbound networkProject secret filesHow to engage
DefaultWrite-containment to the package's own directory + anything in package.json > lpm > scripts.sandboxWriteDirsCredential vars stripped (LPM_TOKEN, NPM_TOKEN, GITHUB_TOKEN, etc.)AllowedDenied.env*, .npmrc, .aws/, *.pem, … (see below)Nothing — this is the default
StrictSame as defaultSame as defaultDeniedDenied--strict-sandbox (or alias --paranoid), persistent via [sandbox] mode = "strict" in ~/.lpm/config.toml / ./lpm.toml, or env LPM_STRICT_SANDBOX=1
DisabledNo containmentNot scrubbedLPM_TOKEN, NPM_TOKEN, etc. all pass throughAllowedReadable — every file in project_dir reachable--no-sandbox (per-command, loud banner), or lpm config sandbox --set none (persistent)
lpm rebuild --strict-sandbox     # filesystem + env + network containment for this run
lpm rebuild --paranoid           # alias — same behavior

Strict network denial coverage is platform-asymmetric: macOS Seatbelt denies every socket family; Linux landlock denies TCP connect(2)/bind(2) and seccomp-bpf denies direct UDP / raw / AF_PACKET / AF_NETLINK sockets; Windows uses AppContainer + Windows Filtering Platform. On platforms where strict isn't fully implementable, the sandbox refuses to start unless the allow_degraded config is set.

Project secret files

Sandbox blocks lifecycle script reads of project secrets by default; opt in via [sandbox] script-read-allow if your build needs a specific secret file.

Lifecycle scripts have read access to most of project_dir so they can locate sources, configs, generated files, etc. But certain conventional secret-file locations are denied even though they sit inside the project tree — a malicious or compromised script has no legitimate reason to read your .env, .aws/credentials, or a *.pem private key.

The deny list covers:

  • dotenv conventions.env, .env.local, .env.production(.local), .env.development(.local), .env.staging(.local), .env.test(.local), .envrc
  • package-manager auth files.npmrc, .yarnrc, .yarnrc.yml, .pypirc
  • shell / HTTP auth files.netrc, _netrc, .git-credentials, .htpasswd
  • git credential URLs.git/config, .git/credentials
  • SSH keys at project rootid_rsa, id_ecdsa, id_ed25519, id_dsa (and .pub variants)
  • cloud / credential dirs.ssh/, .aws/, .kube/, .gcp/, .config/gcloud/, .terraform/, secrets/, secret/
  • filetype patterns at any depth*.pem, *.key, *.pfx, *.p12, *.tfstate, *.tfvars, *.tfvars.json

On macOS the denial fires as a Seatbelt (deny file-read* ...) rule layered after the project-dir allow (SBPL last-match-wins). On Linux the sandbox enters a fresh user + mount namespace before the script runs and bind-mounts /dev/null over each enumerated secret file — reads return zero bytes. Hardened distros that block unprivileged user namespaces (Debian with kernel.unprivileged_userns_clone=0, some AppArmor profiles, unprivileged containers) silently no-op the overlay; the script falls back to baseline access. Windows AppContainer has no equivalent overlay layer today.

Opting specific files back in

Some builds genuinely need access — prisma generate reads DATABASE_URL from .env, a custom build script may read a project-rooted CA bundle, etc. Two surfaces let you exempt named files:

package.json (per-project)
{
  "lpm": {
    "scripts": {
      "sandboxReadAllow": [".env", "services/api/.env"]
    }
  }
}
~/.lpm/config.toml (per-user, applied to every project)
script-read-allow = [".env", ".npmrc"]

Per-project (package.json > lpm > scripts > sandboxReadAllow) and per-user (~/.lpm/config.toml > script-read-allow) lists are unioned at install time. Every entry must resolve to a path inside project_dir — traversal escapes (..) and absolute paths pointing outside the project are rejected with an actionable error naming the config source and key. Duplicate entries across the two lists are deduplicated.

Disabling the sandbox

lpm rebuild --no-sandbox

A single flag drops both containment AND env scrubbing — scripts run with full host access including credential-bearing env (LPM_TOKEN, NPM_TOKEN, GITHUB_TOKEN). Reserve for debugging a sandbox false-positive that sandboxWriteDirs can't express.

--no-sandbox is mutually exclusive with --strict-sandbox, --paranoid, and --sandbox-log. Persistent off-mode goes through lpm config sandbox --set none instead — the CLI flag is the per-command escape.

Diagnostic mode

lpm rebuild --sandbox-log

macOS-only. Rule triggers are logged via sandboxd but not enforced. View accesses via log show --last 5m --predicate 'senderImagePath CONTAINS "Sandbox"' and grep for the script's pid. Not a safety signal — a clean run under --sandbox-log does not mean the script would pass under the full sandbox; it only means the logged accesses were visible for review.

On Linux and Windows, --sandbox-log errors at sandbox init (no native observe-only primitive) and points at --no-sandbox for the same debug case.

--force flag

lpm rebuild --force esbuild

By default, packages whose scripts have already run (and whose script_hash hasn't changed) are skipped. --force ignores that record and re-runs them. Useful after deleting node_modules if you want fresh postinstall work without a full reinstall.

--deny-all

lpm rebuild --deny-all

Refuses to run any scripts, even trusted ones. Useful as a CI smoke test: "would my install want to execute scripts?" Or paired with package.json > lpm > scripts.denyAll: true for a project-wide kill switch.

JSON output

lpm rebuild --json --dry-run emits a dry-run envelope with dry_run: true and packages[], including when the set is empty. A live lpm rebuild --json emits success, built, and failed; an empty rebuild reports success: true, built: 0, failed: 0.

Errors such as a missing lpm.lock, a failing lifecycle script, or an explicitly requested package that is not rebuildable emit success: false in the top-level JSON error envelope and exit non-zero.

Flags

FlagEffect
--allRebuild every scripted package (bypasses trust)
--dry-runPreview without executing scripts
--forceRe-run scripts even for already-built packages
--timeout <SECS>Per-script timeout (default 300 = 5 min)
--deny-allRefuse to run any scripts
--policy <deny|allow|triage>Override the policy for this invocation
--yoloAlias for --policy=allow
--triageAlias for --policy=triage
--strict-sandboxEngage strict sandbox (filesystem + env + outbound network denial)
--paranoidAlias for --strict-sandbox
--no-sandboxDrop all containment for this run — also drops env scrubbing. Mutually exclusive with --strict-sandbox / --paranoid / --sandbox-log
--sandbox-logmacOS only: observe-only sandbox
--no-engine-strictSkip engines.lpm / engines.node enforcement (see lpm install)

Plus the global flags.

See also