LPM CLI

lpm audit

Scan dependencies for vulnerabilities and source risks, inspect secrets, and fix vulnerable direct dependencies.

Scan your dependencies before a release, or use an audit policy as a CI gate.

lpm audit [OPTIONS] [COMMAND]
lpm audit fix [OPTIONS]
lpm audit signatures [OPTIONS]

Quickstart

lpm audit
lpm audit --level high --fail-on vuln
lpm audit fix --dry-run

LPM CLI works with npm, LPM.dev Registry, and private registries. Only @lpm.dev/* packages always use LPM.dev Registry.

ModePurpose
lpm auditReport vulnerabilities, source capabilities, suspicious source patterns, and registry quality findings.
lpm audit --secretsScan installed package files for hardcoded credentials. This is a separate scan.
lpm audit fixUpdate vulnerable direct dependencies in an LPM CLI project, then reinstall.
lpm audit signaturesVerify npm-compatible registry signatures for the installed inventory.

The regular audit does not scan for secrets. Run both modes when you need both reports.

Examples

# Gate on High and Critical vulnerabilities
lpm audit --level high --fail-on vuln

# Include High source risks in the failure policy
lpm audit --fail-on all

# Scan installed packages for hardcoded credentials
lpm audit --secrets --fail-on secrets

# Save a report for a CI artifact
lpm audit --json > audit.json

# Preview, apply, then inspect remaining findings
lpm audit fix --dry-run
lpm audit fix
lpm audit

# Verify registry signatures
lpm audit signatures --json

These commands do not prompt. A preview never approves or applies a fix.

Configuration

There is no dedicated audit section in lpm.json or package.json > lpm. You can define an uncached task:

lpm.json
{
  "tasks": {
    "security:audit": {
      "command": "lpm audit --fail-on vuln",
      "cache": false
    }
  }
}
lpm run security:audit

command defines the invocation. cache: false keeps the security result fresh. An explicit lpm.json task command takes precedence over a same-name package.json script.

For task dependencies and environment configuration, see lpm.json and lpm run.

Audit after installation

lpm install --audit-after-install
lpm install --no-audit-after-install

To enable the audit by default, set the user configuration:

~/.lpm/config.toml
audit-after-install = true

The precedence is the install flag, then LPM_AUDIT_AFTER_INSTALL, then the user configuration. The default is off.

The environment variable accepts 1, true, yes, or on to enable the audit. It accepts 0, false, no, or off to disable it.

The automatic audit is informational. Findings or incomplete coverage do not fail a successful install. An incomplete audit prints a warning instead of completed counts.

For a CI gate, run the standalone audit after lpm install.

Inventory and network requests

lpm audit and lpm audit fix act on the nearest directory with a package.json, as lpm install does. From a subdirectory, they print the directory they chose.

The regular audit reads lpm.lock, package-lock.json, pnpm-lock.yaml, yarn.lock, or Bun lockfiles. It also analyzes available local package source.

Without a supported lockfile, it walks real package directories under node_modules/, including nested dependencies. This inventory has weaker identity and reports degraded: true.

Malformed or unreadable package manifests stop fallback discovery. Linked package directories require a supported lockfile. A package manifest must be a regular file, not a link.

LPM CLI sends names and versions outside @lpm.dev/* to OSV.dev for vulnerability data. This includes names from private registries and proxies.

For @lpm.dev/*, it requests exact version metadata from LPM.dev Registry. Local source analysis does not upload source code.

OSV requests run on each invocation. Local source analysis can use cached results. The automatic audit uses the same network scope.

An unavailable service, incomplete response, inconsistent version identity, or unsupported advisory severity prevents a clean audit. Reconnect or correct the metadata, then rerun the command.

Severity and CI policies

lpm audit --level high --fail-on vuln

--level filters both the report and the findings that can fail the command. It does not suppress coverage errors.

The severity order is critical, high, moderate, then info. medium aliases moderate, and low aliases info. The default reports every level.

--fail-onRegular audit fails forSecret scan fails for
OmittedRetained vulnerabilities or Critical findings.Any secret finding.
vulnRetained vulnerabilities.Findings are informational.
behaviorHigh or Critical non-vulnerability findings and capabilities.Findings are informational.
secretsFindings are informational.Any secret finding.
allRetained vulnerabilities, plus High or Critical non-vulnerability findings and capabilities.Any secret finding.

Non-vulnerability findings include registry security and quality findings. Quality scores below 40 are Moderate, and scores below 20 are High.

--fail-on all applies to the selected mode. It does not add the secret scan to a regular audit.

An incomplete scan exits with code 1, regardless of the policy. Findings that meet the policy also exit with code 1.

Invalid flag combinations produce a usage error. --level cannot accompany --secrets. Scan options cannot accompany fix or signatures.

With regular or secret JSON output, success: true means the scan completed. Findings can still produce a nonzero exit status. Use the process status for CI.

For individual capability selectors, see lpm query.

Source capabilities

The Capabilities section describes API use. A capability does not prove execution or malicious intent, and it does not increase security finding counts. Explicit --fail-on=behavior and --fail-on=all policies still include high-severity capabilities.

The eval capability covers runtime evaluation and compilation through these APIs:

  • Global eval and Function, including recognized aliases and indirect calls.
  • Node vm.runInContext, vm.runInNewContext, vm.runInThisContext, and vm.compileFunction.
  • Node vm.Script and vm.SourceTextModule constructors.

A constant body, such as Function('return this'), still uses runtime code generation. A local class named Function or a method declaration named eval does not establish built-in evaluation. Calls named eval on unresolved objects retain conservative capability evidence. The evidence states that built-in evaluation is not established.

Source evidence includes a package-relative path, a reason, and a file context. Precise matches also include a line, a byte column, and a short excerpt. Evidence contains a bounded set of examples. It does not list every call or prove that an API executes.

With --json, each package stores capabilities in capabilities, and total_capabilities counts them. Each capability includes rule_id, name, policy_severity, source, and evidence. Capabilities do not contribute to issues, total_issues, packages_with_issues, or security severity counts.

Source coverage

An audit can find no threats and still have incomplete coverage. Missing package sources, missing or invalid package manifests, unreadable files, unsupported syntax, and scan limits prevent a clean result.

An incomplete source audit prints Audit incomplete and exits with code 1, regardless of --fail-on or --level.

The source scan covers JavaScript and TypeScript files. It keeps these resource limits:

  • Full analysis stops at 4 MiB for one file. Larger files receive a sample from each end.
  • One package has a 50 MiB scan budget and a 5,000-file limit.
  • A sampled file does not count as a fully scanned file.

If sources are missing, install the dependencies with the project's package manager, then rerun lpm audit. For Yarn PnP, provide a node_modules installation to enable local source analysis. If a file exceeds a limit or uses unsupported syntax, review that file separately. Increasing --level does not complete the scan.

With --json, incomplete source coverage sets success: false. The behavioral_coverage object includes:

FieldMeaning
completeEvery selected package source scan completed within its supported scope.
scanned_packagesPackages with a local analysis, including partial analyses.
partial_packagesPackages with analysis limits, input failures, or unsupported source syntax.
unavailable_packagesPackages without accessible source files.
packagesCoverage gaps with package identity, path, reason, file count, and byte count.

Gap reasons are source_unavailable, source_missing_or_unreadable, scan_limit_reached, source_read_failed, or unsupported_source_syntax. Oversized-file evidence includes the file path, full size, sampled byte count, and detected signals. The top-level scanned field counts discovered packages. It does not mean that every package received complete source analysis.

Quoted examples and comments do not establish executable capabilities. Obfuscation checks distinguish executable identifiers and decoder calls from quoted examples. Encoded strings can still contribute to obfuscation evidence. Executable template expressions remain part of the scan. Bounded parser loops and ordinary locale argument validation do not establish protestware. Source analysis does not execute package code or fully track values between functions and modules. Runtime mutation, custom wrappers, unsupported syntax, and sampled files can hide behavior. Static checks cannot prove that a package is safe. LPM CLI refreshes local cached analyses when scanner rules change. Previously stored LPM.dev Registry tags require a publication rescan before they change.

Credential exfiltration

The Critical :credential-exfiltration finding identifies supported uploads of credential-file contents, private-key material, or the complete process environment. It checks fetch request headers and bodies with fixed HTTP URLs, Node HTTP request bodies, and supported repository-content uploads. It follows supported assignments, encodings, local helpers, and bundled classes within complete files. Evidence points to the upload call. Sampled file ranges cannot establish this finding.

Normal token authentication, public addresses, signatures, and local secret scanning do not establish this finding. The detector recognizes selected secret names and wallet APIs. It does not prove that a value contains credentials or that an upload executes. Dynamic endpoints, arbitrary wrappers, runtime mutation, separate files, and files beyond the scan limits can hide these flows.

Credential-file checks cover supported reads of .env, .npmrc, AWS credentials, SSH private keys, Kubernetes config, and Git credential files. A file read alone does not establish an upload. The finding requires a supported path from its contents to a network payload.

Downloaded and encrypted code

The High :downloaded-execution finding identifies a supported path from a downloaded response to code evaluation or a launched file. The High :encrypted-execution finding identifies a supported path from decrypted bytes to code evaluation or a launched file. These findings describe risky code paths. They do not establish that an external stage is malicious or that an activation condition occurs.

Ordinary downloads, local data decryption, and unrelated process launches do not establish these findings. The checks require recognized APIs and supported value relationships within a complete source file. Concealed API names, callbacks, runtime mutation, and separate modules can prevent detection.

Broad filesystem deletion

The Critical :destructive-filesystem finding identifies supported recursive deletion of home directories, filesystem roots, or current project contents. It includes supported helpers that enumerate a broad directory and remove its entries. Cleanup restricted to a build directory or selected cache entries does not establish this finding.

A warning does not prove that deletion occurs during installation. The code can require an explicit call or depend on a remote response. Review the evidence and activation conditions before deciding whether the behavior is acceptable.

Foreign lockfiles

lpm audit reads one fixed snapshot of each selected foreign lockfile. It rejects malformed files and unsupported versions instead of using a degraded inventory.

The audit supports npm lockfile versions 1, 2, and 3. It supports pnpm lockfile major versions 5, 6, and 9.

Each foreign lockfile has these limits:

  • The file size limit is 64 MiB.
  • The package-entry limit is 200,000.

npm lockfiles have these additional limits:

  • The package-path limit is 32 KiB.
  • The package nesting limit is 256 levels.
  • The dependency limit for one package is 10,000.
  • The total dependency-edge limit is 1,000,000.

If a lockfile exceeds a limit, the audit exits non-zero. It does not use an ancestor lockfile or the node_modules/ fallback.

For bun.lockb, LPM CLI copies the selected file before conversion. The Bun converter has these limits:

  • The time limit is 30 seconds.
  • The converted output limit is 64 MiB.
  • The diagnostic-output limit is 64 KiB.

If Bun exceeds a limit, the audit stops the converter and exits non-zero.

JSON package identity

lpm audit --json

Each object in packages includes name, version, instance_id, and path.

For an LPM CLI-managed package, instance_id identifies the exact lockfile instance. It is null when the selected inventory does not contain an exact instance ID.

The path value identifies the directory that the audit analyzed. It points to the exact installed package materialization when that path is available.

For a file: or link: dependency, path identifies the original live source directory. The value is null when discovery cannot resolve an analyzed directory.

Two rows can have the same name and version. Their instance_id, path, and findings can differ.

Use a non-null instance_id as the row key. Otherwise, use a non-null path. If both are null, the report lacks a stable unique key.

The combination of name and version is not necessarily unique.

Fixing vulnerabilities

lpm audit fix --dry-run
lpm audit fix

# Equivalent compatibility form
lpm audit --fix --dry-run

The fixer supports dependencies, devDependencies, optionalDependencies, and npm aliases. It requires an LPM CLI lockfile and installed direct dependencies.

The fixer updates the package.json that the command selected, in the nearest directory at or above the current one.

For each direct dependency, the fixer checks the installed name and version against the selected lockfile instance. Modern lockfiles preserve exact root identities.

If a legacy lockfile leaves the source ambiguous, the fixer skips that dependency. It does not guess a source or send the ambiguous name to a registry.

The fixer chooses the lowest newer stable version that the available advisory data identifies as unaffected. The target can cross a major version boundary.

An applied fix pins the manifest to the exact planned version. An npm alias retains its npm: identity. Review the preview and run your project tests afterward.

Transitive-only findings remain unchanged. Non-registry protocols, such as workspace:, catalog:, file:, git, and JSR, cannot be rewritten automatically.

The report lists skipped dependencies and their reasons. Any skipped dependency makes the command exit nonzero, even when other fixes succeed.

Recovery

Application holds the project install lock. Before writing, the fixer checks that the manifest and selected lockfile still match the plan.

A concurrent file change stops the fix. Rerun the preview with the current files.

The fixer saves package.json, lpm.lock, and lpm.lockb, then installs the exact targets and checks their vulnerability status again.

A failed install or verification restores those files. The transaction does not restore node_modules/.

If the installed tree differs after failure, restore it from the restored lockfile:

lpm install
lpm audit

--dry-run leaves the manifest, lockfiles, and installed tree unchanged. With --json, the fixer emits one report without nested install output.

Registry signatures

lpm audit signatures

The command verifies npm registry package signatures for the installed tree. LPM CLI reads stored signatures and publication times from lpm.lock when available.

If stored evidence is incomplete, LPM CLI gets exact metadata from the authorized registry. A signature with an expiring key requires a publication time.

For foreign lockfiles, the recorded tarball URL must match an authorized registry path. This path can match npm or LPM.dev Registry.

The path can also match a registry that is configured in .npmrc.

LPM CLI keeps the configured registry path and its authentication. It never trusts an unconfigured origin from a foreign lockfile.

Human output is intentionally compact:

! Registry signatures · 1 verified · 1 not verified
  unsigned-pkg@1.0.0  missing dist.signatures

A clean tree exits zero:

✓ Registry signatures verified · 247 verified

Under --json, the envelope includes aggregate counts and one row per package:

{
  "success": false,
  "scanned": 2,
  "verified": 1,
  "not_verified": 1,
  "skipped": 0,
  "packages": [
    { "name": "signed-pkg", "version": "1.0.0", "status": "verified", "signatures_verified": 1 },
    {
      "name": "unsigned-pkg",
      "version": "1.0.0",
      "status": "not_verified",
      "reason": "missing_signatures",
      "reason_detail": "missing dist.signatures",
      "signatures_verified": 0
    }
  ]
}

Statuses are verified, not_verified, or skipped. Skipped rows include non-registry sources and @lpm.dev/* packages from LPM.dev Registry.

A foreign @lpm.dev/* name from another authorized registry still requires signature verification. Any not_verified row makes the command exit with code 1.

Secret scanning

lpm audit --secrets
lpm audit --secrets --fail-on secrets --json

This mode scans installed packages for API keys, tokens, private keys, and credential files. It does not scan your root project files.

A node_modules/ installation is required. The scanner follows supported installed package links within allowed project and store locations. Dangling package links fail the scan.

Selected text files have a 2 MiB read limit. The operation limits are 100,000 packages, 250,000 files, 512 MiB, and 10,000 findings.

Unreadable inputs, directory errors, or exceeded limits stop the scan with an error. An informational finding policy does not suppress these errors.

The JSON report contains packagesScanned, packagesWithSecrets, and findings. Each match includes a credential type, a description with its file path, line, and severity.

Matched credential values are not included in the report. A detected pattern still needs review because example data can resemble a real credential.

If a real credential is present, revoke it and replace the affected package. Removing the file does not revoke the credential.

For your own package before upload, lpm publish also checks the prepared package files for secrets.

Flags

FlagEffect
--level <LEVEL>Minimum severity for the regular report and failure decision. Values: info, moderate, high, critical. Aliases: low, medium.
--fail-on <POLICY>Finding policy for the selected scan. Values: vuln, behavior, secrets, all.
--secretsSelect the separate installed-package secret scan.
--fixAlias for lpm audit fix. Conflicts with scan options.
--dry-runWith --fix, preview without file changes.
fix --dry-runPreview fixes with the subcommand form.
--jsonEmit machine-readable output for the selected mode.

See global flags for other shared options.

See also

  • Security audit — security layers and source tags.
  • lpm query — inspect dependencies with selectors.
  • lpm install — restore the installed tree and enable automatic audits.
  • lpm publish — check package files before upload.