LPM CLI

lpm trust

Manage project lifecycle trust and release-age exclusions.

lpm trust diff [--assert-none] [--json]
lpm trust prune [--dry-run] [--yes] [--json]
lpm trust lifecycle-scope add <SCOPE> [--json]
lpm trust lifecycle-scope remove <SCOPE> [--json]
lpm trust lifecycle-scope list [--json]
lpm trust release-age-exclude add <SELECTOR> [--json]
lpm trust release-age-exclude remove <SELECTOR> [--json]
lpm trust release-age-exclude list [--json]

lpm trust manages security decisions that belong to the current project. These decisions stay in package.json so that the project can share them.

For lifecycle scripts, lpm trust manages project scope trust. It also inspects and cleans trustedDependencies.

Use lpm approve-scripts for strict package approvals. These approvals include the version, integrity, script hash, and requested capabilities.

For the release-age gate, lpm trust manages minimumReleaseAgeExclude. These selectors exempt selected packages from the project cooldown.

Project trust only. Global lifecycle trust uses ~/.lpm/global/trusted-dependencies.json. Manage it with lpm approve-scripts --global.

User release-age exclusions use ~/.lpm/config.toml. Manage them with lpm config release-age-exclude.

Storage contract

LPM stores each exception or approval in one policy layer. A command does not copy a policy entry into another file.

DecisionCommandPersistent location
Project release-age exclusionlpm trust release-age-excludepackage.json > lpm > minimumReleaseAgeExclude
Project lifecycle scopelpm trust lifecycle-scopepackage.json > lpm > scripts > trustedScopes
Project package approvallpm approve-scriptspackage.json > lpm > trustedDependencies
User release-age exclusionlpm config release-age-exclude~/.lpm/config.toml > minimum-release-age-exclude
Global-install package approvallpm approve-scripts --global~/.lpm/global/trusted-dependencies.json
One-run release-age exclusionlpm install --min-release-age-excludeNone

lpm.lock never stores release-age exclusions, lifecycle scopes, or lifecycle approvals. lpm trust prune reads the lockfile only to find stale project approvals.

Each command writes one declared policy layer. It does not cascade through package.json, user configuration, global trust, or the lockfile.

Project commands use the package.json in the current directory. In a workspace member, the member manifest receives the policy entry.

The workspace root and sibling manifests stay unchanged. User and global commands continue to use their fixed locations under ~/.lpm.

List commands read only their declared layer. They do not show the effective merged release-age list.

During an install, release-age exclusions merge in this order: CLI entries, project entries, then user entries. Duplicate selectors collapse.

An exact-version selector matches only that version. A scope wildcard matches every package in that scope.

A lifecycle scope addition also requires local signed authorization under ~/.lpm/security. This authorization approves the write and is not another trust list.

Mutating policy commands use atomic file replacement. A duplicate addition does not rewrite a normalized destination file.

Subcommands

SubcommandWhat it does
diffShow how the current trustedDependencies differs from the last install's snapshot
pruneRemove entries whose package is no longer in the resolved tree
lifecycle-scopeAdd, remove, or list project lifecycle scope trust
release-age-excludeAdd, remove, or list project release-age exclusions

Flag Reference

CommandFlagEffect
lpm trust diff--jsonEmit the diff envelope instead of human output. Same as global --json for this command.
lpm trust diff--assert-noneExit non-zero if any added, removed, or changed entries are present.
lpm trust prune-y, --yesSkip the confirmation prompt. Required on non-TTY / CI when pruning would write.
lpm trust prune--dry-runPreview stale entries without writing package.json.
lpm trust prune--jsonEmit the prune envelope instead of human output. Same as global --json for this command.
lpm trust lifecycle-scope--jsonEmit the lifecycle scope envelope instead of human output.
lpm trust release-age-exclude--jsonEmit the release-age exclusion envelope instead of human output.

lpm trust lifecycle-scope

lpm trust lifecycle-scope add '@company/*'
lpm trust lifecycle-scope remove '@company/*'
lpm trust lifecycle-scope list

A lifecycle scope trusts every package in one npm scope. The trust applies to all package versions and script contents.

Use this command only when your organization controls the complete scope. Use lpm approve-scripts for one package.

The command accepts lowercase npm scope wildcards such as @company/*. It rejects package names, package versions, uppercase scopes, and nested patterns.

add writes package.json > lpm > scripts > trustedScopes. It preserves all other fields in package.json.

An addition widens the project security policy. In an interactive terminal, LPM CLI requests native user approval before it writes the scope.

For an agent or JSON command, create a signed project unlock before you add the scope:

lpm security unlock trust-scope-widen --project . --ttl 10m
lpm trust lifecycle-scope add '@company/*' --json

The unlock is local to the project and user. The project scope list remains in package.json for the team.

The signed authorization remains under ~/.lpm/security. LPM CLI does not write lifecycle scope trust to lpm.lock.

remove narrows the security policy and does not need an unlock. It removes the signed authorization for that scope.

It also revokes trust-scope-widen from each active temporary unlock for the project. Other scopes in the same unlock remain active.

As a result, a later add requires new authorization. Removal matches the complete scope selector.

list shows the configured project scopes. It does not show strict package approvals or global lifecycle trust.

The JSON envelope contains success, schema_version, command, scope, action, changed, normalized, count, and scopes. The add and remove actions also contain selector.

changed reports whether the requested scope changed. normalized reports whether the command repaired existing stored entries. count is the number of scopes.

lpm trust release-age-exclude

lpm trust release-age-exclude add react
lpm trust release-age-exclude add '@company/*'
lpm trust release-age-exclude add react@19.1.0
lpm trust release-age-exclude remove react@19.1.0
lpm trust release-age-exclude list

The command writes package.json > lpm > minimumReleaseAgeExclude. It preserves the other fields in package.json.

The command accepts these selector types:

SelectorEffect
reactExclude all versions of react.
@company/*Exclude all packages in the @company scope.
react@19.1.0Exclude only version 19.1.0 of react.

Version selectors must contain one exact semantic version. Ranges such as react@^19 are invalid.

add is idempotent. If the selector exists in a normalized list, the command does not rewrite package.json.

If stored entries need normalization, the command repairs the list. It returns changed: false and normalized: true when the selector stays unchanged.

remove matches the complete selector. For example, removal of react@19.1.0 does not remove the react selector.

list shows only the project list. It does not include the user list or one-run CLI exclusions.

The JSON envelope contains success, schema_version, command, scope, action, changed, normalized, count, and exclusions. The add and remove actions also contain selector.

changed reports whether the requested selector changed. normalized reports whether the command repaired existing stored entries. count is the number of exclusions.

User release-age exclusions

Use lpm config to apply an exclusion to all installs for the current user:

lpm config release-age-exclude add '@company/*'
lpm config release-age-exclude remove '@company/*'
lpm config release-age-exclude list

The user command writes ~/.lpm/config.toml > minimum-release-age-exclude. Project and user lists merge during an install.

lpm trust diff

lpm trust diff
lpm trust diff --assert-none
lpm trust diff --json
lpm trust diff --assert-none --json

Read-only. Compares the current package.json > lpm > trustedDependencies against the snapshot LPM CLI took on the last lpm install. Surfaces three classes of change:

  • Additions — entries that weren't there last install. Potential silent PR poisoning vector ("someone added evil-package to the trust list — was that reviewed?")
  • Removals — entries that were there but are now gone. Usually intentional cleanup, but worth a glance.
  • Same-key binding changes — same name@version key, but a different integrity or scriptHash. Indicates the package contents changed without a version bump (re-publish, tampering).

The snapshot lives at <project>/.lpm/trust-snapshot.json, written by every successful lpm install. If the project has never been installed with LPM CLI, diff reports "no prior snapshot" and exits cleanly — there's no baseline to compare against yet.

trust diff is informational by default — it prints whatever it found. For a built-in CI gate, use --assert-none:

lpm trust diff --assert-none

That exits non-zero when any added, removed, or changed entry is present.

If you still want machine-readable output for bots or logs, --json works with the same gate:

lpm trust diff --json --assert-none

The JSON envelope: success, schema_version, command, assertion_failed, diff_count, snapshot_captured_at, current_binding_count, added[], removed[], changed[]. Each entry carries key (e.g., esbuild@0.25.1), previous, and current (the integrity / scriptHash before and after). When --assert-none fails in JSON mode, LPM CLI emits this same single diff envelope with success: false, error_code: "trust_diff_assert_none", and exits non-zero.

FlagEffect
--jsonMachine-readable output for CI gating via jq
--assert-noneExit non-zero if any diff entries are present

lpm trust prune

lpm trust prune                   # interactive — prompts before writing
lpm trust prune --dry-run         # preview, don't write
lpm trust prune -y                # skip the prompt (required on non-TTY / CI)
lpm trust prune --json

Removes trustedDependencies entries whose package isn't in the resolved tree anymore. "No longer in the tree" means: lpm.lock has zero entries with this name (regardless of version). Per-version drift on a name that's still installed is not considered stale — that's drift, handled by the install-time gate, not by prune.

Entries become stale when:

  • A dep was removed from package.json but its trust entry stayed
  • A dep was renamed (the old name's pin is now orphaned)
  • A dep was moved (e.g., from dependencies to optionalDependencies and never reinstalled)

lpm trust prune reads lpm.lock to determine what's installed. If the lockfile is missing, the command exits with no lpm.lock found — run \lpm install` before pruning trust entries` — refusing to prune from an empty installed-set (which would mark everything stale).

The interactive prompt confirms the removal list before writing. In CI / non-TTY contexts, -y is required — without it, prune errors with lpm trust prune needs a TTY for confirmation. Pass \--yes` to ...`.

--dry-run runs the analysis normally but doesn't touch package.json.

The JSON envelope: success, schema_version, command, dry_run, mutated (the actual final state — false on --dry-run or no-op), stale_count, stale[] (the keys removed).

FlagEffect
-y, --yesSkip the confirmation prompt (required on non-TTY)
--dry-runPreview without writing
--jsonMachine-readable output

CI usage

- run: lpm install --offline
- run: lpm trust diff --assert-none
- run: lpm trust prune --dry-run --json # informational — would prune anything?

trust diff --assert-none is the built-in drift gate: fail the build if trustedDependencies changed since the last reviewed install. Add --json if you also want structured output in CI logs.

prune --dry-run is for hygiene reports only — it always exits zero. Gate it the same way if you want to fail on stale entries:

lpm trust prune --dry-run --json | jq -e '.stale_count == 0'

See also