lpm security
Temporary approvals, effective machine floor, managed policy, and local security-state repair.
lpm security <unlock|lock|status|protect|repair>LPM CLI treats some security-lowering requests as guarded operations. Examples:
lpm install --no-sandboxlpm install --allow-newlpm install --yolopackage.json > lpm.scriptPolicy = "allow"package.json > lpm.minimumReleaseAge = 0package.json > lpm.minimumReleaseAgePolicy = "direct"on a machine approved for strict cooldown scopelpm.toml > [sandbox] mode = "none"~/.lpm/config.toml > [firewall] mode = "off"on a machine approved for monitor or enforce mode~/.lpm/config.toml > install-time-source-analysis = false
lpm security is the command surface for two related jobs:
unlockcreates a short-lived approval for one guarded weakening. Manualunlockdefaults to the machine-global target unless you pass--project.lockrevokes one active temporary unlock. Manuallockalso defaults to the machine-global target unless you pass--project.statusshows the effective security floor, active runtime overrides, and whichever unlock scope you asked to inspect.statusstill defaults to the current project.protectinstalls, removes, or inspects OS-managed local protection.repairquarantines signed local security-state files that can no longer be verified.
Examples
lpm security unlock default --ttl 10m
lpm security unlock sandbox-none --project . --ttl 10m
lpm security unlock provenance-ignore-drift --project . --package esbuild --ttl 10m
lpm security unlock provenance-unverified --global --ttl 10m
lpm security unlock all --global --ttl 1h
lpm security lock default
lpm security lock provenance-ignore-drift --project . --package esbuild
lpm security lock all --global
lpm security status
lpm security status --project /path/to/repo
lpm security status --global
lpm security status --json
lpm security protect status
lpm security protect enable
lpm security protect enable --firewall monitor
lpm security protect disable
lpm security repair
lpm security repair --jsonSubcommands
| Subcommand | What it does |
|---|---|
lpm security unlock <scope> | Create a temporary approval for one guarded weakening, defaulting to the global trust surface |
lpm security lock <scope> | Revoke one active temporary approval, defaulting to the global trust surface |
lpm security status | Print the effective floor, runtime overrides, source attribution, and active unlocks for the selected scope |
lpm security protect | Manage OS-protected local npm firewall policy for this machine |
lpm security repair | Quarantine signed local security state that fails verification |
Flag Reference
| Command | Argument / flag | Effect |
|---|---|---|
unlock | <scope> | Scope selector to approve: all, default, or one concrete scope. |
unlock | --project <PATH> | Create a project-scoped unlock. Mutually exclusive with --global. |
unlock | --global | Create a machine-global unlock. This is the default when no target flag is passed. |
unlock | --ttl <DUR> | Unlock lifetime. Default: 10m. Accepts raw seconds or durations such as 5m, 1h, 30d, 365d. |
unlock | --package <PKG> | Restrict a concrete-scope unlock to one package name. Repeatable. Rejected with all and default. |
lock | <scope> | Scope selector to revoke: all, default, or one concrete scope. |
lock | --project <PATH> | Revoke project-scoped unlocks. Mutually exclusive with --global. |
lock | --global | Revoke machine-global unlocks. This is the default when no target flag is passed. |
lock | --package <PKG> | Match unlocks created with exactly these package filters. Repeatable. Rejected with all and default. |
status | --project <PATH> | Show project security state for that root. Defaults to the current directory. |
status | --global | Show the global trust surface instead of project state. |
protect status | (none) | Show whether managed npm firewall protection is active. |
protect enable | --firewall <monitor|enforce> | Install or update managed npm firewall protection. Defaults to enforce. |
protect disable | (none) | Remove the managed npm firewall protection entry. Other managed-policy controls are preserved. |
repair | (none) | Quarantine unverifiable signed local security state. |
Plus the global flags. Use global --json for structured output.
unlock
lpm security unlock <scope|all|default> [--project PATH | --global] [--ttl 10m] [--package PKG]Creates a temporary approval under ~/.lpm/security/unlocks/. If you pass --project, the unlock is tied to that project root. If you pass --global, or omit both target flags, the unlock applies to the machine-global trust surface. The command does not run the risky action itself. It only creates the unlock so you can rerun the original install, rebuild, or trust command.
Default TTL is 10m. Maximum TTL is 365d.
Accepted TTL inputs use the shared duration parser, so m, h, d, and raw seconds are recognized. For lpm security unlock, the parsed value must still resolve to at most 365d, so examples such as 5m, 1h, 30d, 365d, and 600 all work.
Manual unlock defaults global, but suggested commands coming from guarded install/config errors stay explicit (--project . or --global) so you can see the intended scope before approving it.
Use --package <name> only with a concrete scope when the weakening is package-specific. Repeat --package to cover more than one package. Package filter values must not be empty. A package-scoped unlock only covers requests for those package names; it does not cover blanket all-package flags such as --ignore-provenance-drift-all or --unverified-provenance-all. Bundle selectors all and default reject --package.
Bundle selectors
allexpands to every concrete unlock scope.defaultexpands to the common install/runtime weakeners:cooldown-bypass,cooldown-window,provenance-ignore-drift,provenance-unverified,scripts-triage,scripts-allow,sandbox-default,sandbox-none,sandbox-allow-degraded,typosquat-disable,firewall-disable, andsource-analysis-disable.defaultintentionally excludestrust-bulk-approve,trust-scope-widen,capability-widen, andfloor-edit.
Common scopes
Copy the exact scope from the error's suggested_command whenever possible. The most common ones today are:
| Scope | Typical trigger |
|---|---|
sandbox-none | lpm install --no-sandbox or lpm.toml > [sandbox] mode = "none" |
sandbox-default | requesting default sandbox mode when the approved floor requires stricter containment |
sandbox-allow-degraded | lpm.toml or ~/.lpm/config.toml enables [sandbox].allow-degraded = true beyond the current floor |
cooldown-bypass | lpm install --allow-new, --min-release-age=0, or a lower minimumReleaseAge |
cooldown-window | minimumReleaseAgePolicy = "direct" or release-age-policy = "direct" when the approved floor requires strict direct+transitive cooldown |
scripts-allow | lpm install --yolo, --policy=allow, or scriptPolicy = "allow" |
scripts-triage | lpm install --triage or scriptPolicy = "triage" when that weakens the current floor |
trust-bulk-approve | lpm approve-scripts --yes, lpm approve-scripts --global --yes, or direct trustedDependencies widening |
trust-scope-widen | direct trustedScopes widening in package.json |
capability-widen | direct widening of passEnv, readProject, or sandboxLimits in package.json |
provenance-ignore-drift | --ignore-provenance-drift <pkg> or --ignore-provenance-drift-all |
provenance-unverified | LPM_PROVENANCE_ENFORCE=warn, LPM_PROVENANCE_ENFORCE=off, raw or persisted [sigstore].verify = "warn" or "off", or --unverified-provenance* |
typosquat-disable | raw or persisted typosquat-guard = "off" when the approved floor keeps suspicious-name analysis enabled |
firewall-disable | raw or persisted [firewall].mode = "off" or "monitor" when the approved floor requires stronger firewall.lpm.dev verdict checks |
source-analysis-disable | raw or persisted install-time-source-analysis = false when the approved floor requires install-time source analysis |
floor-edit | guarded edits to the signed approved security floor; usually copied from a suggested command |
lock
lpm security lock <scope|all|default> [--project PATH | --global] [--package PKG]Revokes active temporary unlocks from ~/.lpm/security/unlocks/. If you pass --project, lock inspects that project's unlocks. If you pass --global, or omit both target flags, it inspects machine-global unlocks.
lock is non-interactive. If a matched unlock contains multiple scopes, lock removes only the requested scopes and leaves the rest active. lock only touches temporary unlock grants; it does not rewrite the approved machine posture, project policy approvals, or managed policy.
Use --package <name> only with a concrete scope, and pass a non-empty package value. For lock, package filters match unlocks created with that exact package filter set.
Interactive vs automation
lpm security unlock is interactive today. In a TTY it asks for native system approval, then writes the signed unlock grant on success.
In any of these modes, it refuses instead of prompting:
--json- non-TTY stdin/stdout
- CI (
CI=1,true,yes, etc.)
The refusal uses error_code: "security_approval_required" and includes a suggested_command.
Guarded install/config attempts plus interactive unlock decisions are appended to ~/.lpm/security/audit.jsonl as signed hash-chained entries. The fast refusal path for lpm security unlock in --json, CI, or other non-interactive mode returns the structured error immediately and does not append a separate unlock-declined entry. Production builds also store the audit head in the OS keyring so local truncation or rewrite attempts can be detected on the next append. This is local tamper evidence, not a remote immutable log.
status
lpm security status [--project PATH | --global]Shows the effective security floor for the selected project root, or for the global trust surface when you pass --global.
Human output includes:
- the selected target (
projectorglobal) - the selected root for that target:
projectin project mode,global-rootin global mode - the effective values for script policy, release-age floor, release-age policy, sandbox mode, sandbox degraded mode, Sigstore verification, typosquat guard, npm firewall mode, and install-time source analysis
- whether each value came from the built-in default, the signed approved-posture store, or a managed policy
- the path to the signed approved-posture store
- the active managed policy file, if one is present
- any active runtime overrides such as
LPM_PROVENANCE_ENFORCE, raw~/.lpm/config.toml > [sigstore].verify, or raw~/.lpm/config.toml > [firewall].mode - the active unlocks for that scope, including package scoping and min-release-age limits when present
With --json, the envelope looks like:
{
"success": true,
"status": {
"target": "project",
"project_root": "/abs/path/to/repo",
"effective_floor": {
"script_policy": "deny",
"minimum_release_age_secs": 86400,
"release_age_policy": "direct",
"sandbox_mode": "default",
"sandbox_allow_degraded": false,
"sigstore_verify": "deny",
"typosquat_guard": "default",
"firewall_mode": "off",
"install_time_source_analysis": true
},
"floor_sources": {
"script_policy": "approved-store",
"minimum_release_age_secs": "managed-policy",
"release_age_policy": "builtin-default",
"sandbox_mode": "builtin-default",
"sandbox_allow_degraded": "builtin-default",
"sigstore_verify": "approved-store",
"typosquat_guard": "builtin-default",
"firewall_mode": "builtin-default",
"install_time_source_analysis": "builtin-default"
},
"approved_posture_path": "/Users/alice/.lpm/security/approved-posture.json",
"approved_posture_source": "approved-store",
"managed_policy": {
"path": "/etc/lpm/security-policy.toml",
"name": "corp-default",
"source": "mdm",
"enforced_controls": ["minimum-release-age-secs"]
},
"active_runtime_overrides": [
{
"control": "sigstore.verify",
"value": "warn",
"source": "LPM_PROVENANCE_ENFORCE"
},
{
"control": "[firewall].mode",
"value": "off",
"source": "~/.lpm/config.toml [firewall].mode"
}
],
"active_unlocks": []
}
}The JSON field name stays project_root for envelope stability even under --global; in that mode it points at the canonical global trust root.
protect
lpm security protect status
lpm security protect enable [--firewall enforce]
lpm security protect disableprotect manages the npm firewall entry in the OS-managed policy file. It is meant for local-agent protection: if a local LLM agent tries to run with LPM_HOME=/tmp/clean, the managed policy still lives outside LPM_HOME and keeps the firewall floor active.
enable writes [firewall].mode into the managed policy path and then reloads it through the same authority checks LPM CLI uses at runtime. The default is --firewall enforce; --firewall monitor is available for admins who want managed visibility without blocking. off is intentionally not accepted by protect enable.
disable removes only the managed npm firewall entry. If the same managed policy file contains other controls such as script-policy, sandbox, Sigstore, typosquat, or release-age settings, those controls are left in place. If no enforced controls remain, the policy file is removed.
This command writes an OS-protected location, so it must run with administrator authority:
Unix/macOS: /etc/lpm/security-policy.toml
Windows: C:\ProgramData\lpm\security-policy.tomlWith --json, protect enable and protect disable return:
{
"success": true,
"change": "enabled",
"protect": {
"path": "/etc/lpm/security-policy.toml",
"active": true,
"firewall_mode": "enforce",
"managed_policy": {
"path": "/etc/lpm/security-policy.toml",
"name": "lpm local protection",
"source": "lpm security protect",
"enforced_controls": ["firewall.mode"]
}
}
}protect status --json omits change and returns the same protect object.
repair
lpm security repairQuarantines signed local security-state files under ~/.lpm/security/ when LPM CLI cannot verify them with the current signing secret. This is the recovery path for errors such as:
security approval store refused: signed security state file ~/.lpm/security/approved-posture.json failed signature verificationTypical causes are OS keyring reset, machine restore without the matching keyring secret, copying ~/.lpm/security/ from another machine, or manual edits to signed files.
repair is explicit by design: ordinary commands fail closed instead of silently trusting or deleting unverified state. The command moves unverified state aside with a .unverified-... suffix and does not create a replacement signing secret. After repair, lpm security status falls back to the built-in floor plus any managed policy until you intentionally approve new local state.
The repair pass checks signed posture, project/global approval state, unlock grants, and the signed audit log. If the signing secret exists but the OS keyring cannot be read, repair stops rather than quarantining files it could not actually verify.
With --json, the envelope looks like:
{
"success": true,
"repair": {
"security_dir": "/Users/alice/.lpm/security",
"quarantined": [
{
"original_path": "/Users/alice/.lpm/security/approved-posture.json",
"quarantine_path": "/Users/alice/.lpm/security/approved-posture.json.unverified-20260531T121314.000Z",
"reason": "signature verification failed"
}
]
}
}How guarded approvals work
1. CLI weakeners in an interactive shell
For direct CLI weakeners such as:
lpm install --no-sandbox
lpm install --allow-new
lpm install --yolo
LPM_PROVENANCE_ENFORCE=off lpm installLPM CLI may ask inline for confirmation in an interactive terminal. For most direct CLI weakeners, approval mints the same temporary unlock internally and continues the command. Global guarded flows, such as lpm install -g and lpm approve-scripts --global, use global unlocks and suggest lpm security unlock <scope> --global.
lpm install --allow-new is narrower: approving the inline prompt applies only to the current install invocation. Use lpm security unlock cooldown-bypass --project . --ttl <duration> when you intentionally want a reusable temporary project unlock.
2. CLI weakeners in CI, --json, or non-TTY mode
The same command does not prompt. It fails with security_approval_required and includes the exact scope and suggested_command.
3. Repo-file weakeners
Security-sensitive repo config is treated as a proposal, not authority.
Examples:
package.json > lpm.scriptPolicy = "allow"package.json > lpm.minimumReleaseAge = 0package.json > lpm.minimumReleaseAgePolicy = "direct"on a machine approved for strict cooldown scopelpm.toml > [sandbox] mode = "none"- direct
trustedDependencies,trustedScopes,passEnv,readProject, orsandboxLimitswidening
LPM CLI does not prompt inline for those repo-file changes. Instead, install or rebuild fails and points you at lpm security unlock ... for a temporary project exception, or at a persistent machine-level config change if that is what you want.
4. Persistent machine-wide config changes
For security-sensitive lpm config mutations such as:
lpm config scripts --set allow
lpm config release-age --set 0
lpm config release-age-policy --set direct
lpm config sandbox --set none
lpm config sigstore --set off
lpm config firewall --set off
lpm config source-analysis --set falseLPM CLI asks for interactive confirmation in a TTY before persisting the weaker machine posture.
In CI, --json, or any non-interactive shell, those same writes fail with security_approval_required instead of silently lowering the machine floor.
5. Raw file edits are not enough
Editing package.json, lpm.toml, or ~/.lpm/config.toml by hand is not enough to authorize a weaker security posture on its own.
If the file asks for something weaker than the currently approved floor, LPM CLI treats that value as an unauthorized proposal and either:
- fails with
security_approval_required, or - fails with
security_floorwhen a higher-authority managed policy owns that control
Managed machine policy
For local npm firewall protection, prefer lpm security protect enable; it writes the managed policy entry and validates that the runtime will trust it. Managed machines and headless CI can also provide a higher-authority floor directly at:
Unix/macOS: /etc/lpm/security-policy.toml
Windows: C:\ProgramData\lpm\security-policy.tomlExample:
script-policy = "deny"
minimum-release-age-secs = 86400
release-age-policy = "strict"
typosquat-guard = "on"
install-time-source-analysis = true
[policy]
name = "corp-default"
source = "mdm"
[sandbox]
mode = "strict"
allow-degraded = false
[sigstore]
verify = "deny"
[firewall]
mode = "enforce"When this file owns a control, weaker repo or user changes do not win. They fail with error_code: "security_floor" instead. In production, LPM CLI only loads managed policy from the managed path itself. On Unix/macOS, the file and parent directories must be root-owned and not group/world writable. On Windows, the file and parent directory must resolve to the ProgramData path, must not be reparse points, must be owned by SYSTEM or Administrators, and must not grant write access to non-administrator principals.
An enforced [firewall].mode floor applies to public npm package versions before LPM CLI materializes package bytes through install, global install, lpm fetch, lpm download, or the source-package download step of lpm add. Private/custom registry packages and LPM.dev Registry packages are outside the LPM Firewall verdict API.
Use lpm security protect status to inspect the managed npm firewall protection entry, and lpm security status to see exactly which controls are currently coming from managed policy.