LPM CLI

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-sandbox
  • lpm install --allow-new
  • lpm install --yolo
  • package.json > lpm.scriptPolicy = "allow"
  • package.json > lpm.minimumReleaseAge = 0
  • package.json > lpm.minimumReleaseAgePolicy = "direct" on a machine approved for strict cooldown scope
  • lpm.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:

  • unlock creates a short-lived approval for one guarded weakening. Manual unlock defaults to the machine-global target unless you pass --project.
  • lock revokes one active temporary unlock. Manual lock also defaults to the machine-global target unless you pass --project.
  • status shows the effective security floor, active runtime overrides, and whichever unlock scope you asked to inspect. status still defaults to the current project.
  • protect installs, removes, or inspects OS-managed local protection.
  • repair quarantines 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 --json

Subcommands

SubcommandWhat 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 statusPrint the effective floor, runtime overrides, source attribution, and active unlocks for the selected scope
lpm security protectManage OS-protected local npm firewall policy for this machine
lpm security repairQuarantine signed local security state that fails verification

Flag Reference

CommandArgument / flagEffect
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--globalCreate 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--globalRevoke 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--globalShow 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

  • all expands to every concrete unlock scope.
  • default expands 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, and source-analysis-disable.
  • default intentionally excludes trust-bulk-approve, trust-scope-widen, capability-widen, and floor-edit.

Common scopes

Copy the exact scope from the error's suggested_command whenever possible. The most common ones today are:

ScopeTypical trigger
sandbox-nonelpm install --no-sandbox or lpm.toml > [sandbox] mode = "none"
sandbox-defaultrequesting default sandbox mode when the approved floor requires stricter containment
sandbox-allow-degradedlpm.toml or ~/.lpm/config.toml enables [sandbox].allow-degraded = true beyond the current floor
cooldown-bypasslpm install --allow-new, --min-release-age=0, or a lower minimumReleaseAge
cooldown-windowminimumReleaseAgePolicy = "direct" or release-age-policy = "direct" when the approved floor requires strict direct+transitive cooldown
scripts-allowlpm install --yolo, --policy=allow, or scriptPolicy = "allow"
scripts-triagelpm install --triage or scriptPolicy = "triage" when that weakens the current floor
trust-bulk-approvelpm approve-scripts --yes, lpm approve-scripts --global --yes, or direct trustedDependencies widening
trust-scope-widendirect trustedScopes widening in package.json
capability-widendirect widening of passEnv, readProject, or sandboxLimits in package.json
provenance-ignore-drift--ignore-provenance-drift <pkg> or --ignore-provenance-drift-all
provenance-unverifiedLPM_PROVENANCE_ENFORCE=warn, LPM_PROVENANCE_ENFORCE=off, raw or persisted [sigstore].verify = "warn" or "off", or --unverified-provenance*
typosquat-disableraw or persisted typosquat-guard = "off" when the approved floor keeps suspicious-name analysis enabled
firewall-disableraw or persisted [firewall].mode = "off" or "monitor" when the approved floor requires stronger firewall.lpm.dev verdict checks
source-analysis-disableraw or persisted install-time-source-analysis = false when the approved floor requires install-time source analysis
floor-editguarded 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 (project or global)
  • the selected root for that target: project in project mode, global-root in 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 disable

protect 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.toml

With --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 repair

Quarantines 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 verification

Typical 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 install

LPM 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 = 0
  • package.json > lpm.minimumReleaseAgePolicy = "direct" on a machine approved for strict cooldown scope
  • lpm.toml > [sandbox] mode = "none"
  • direct trustedDependencies, trustedScopes, passEnv, readProject, or sandboxLimits widening

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 false

LPM 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_floor when 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.toml

Example:

/etc/lpm/security-policy.toml
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.

See also