VisCommandsvis security

vis security

Inspect and sync vis-config security settings to the native package-manager config

vis security

Inspect the build-script triage, push vis.config security settings to the active package manager's native config files, and verify the resolved lockfile closure against supply-chain policies. The subcommands give the same view that the post-install drift report uses.

Usage

vis security <action> [options]

Actions

list

Show the full build-script triage report:

vis security list
vis security list --json

What it reports

  • Approved — installed packages with lifecycle scripts that match an entry in security.allowBuilds. Wildcard (@scope/*, name@*) and version-pinned (name@1.2.3, requires pinVersions: true) patterns are honored.
  • Unapproved — installed packages with lifecycle scripts that have no matching entry. Includes the synthetic install (binding.gyp) hook for packages that ship a binding.gyp but no explicit install script — npm runs node-gyp rebuild for them implicitly.
  • Stale allowlist entriessecurity.allowBuilds keys that don't match anything installed. Safe to prune.
  • Version drift — only emitted when security.pinVersions: true. Entries pointing at a version that is no longer installed, with a from → to migration suggestion.
  • Native-config drift — when vis.config and the PM's own config files (pnpm-workspace.yaml, bunfig.toml, .npmrc, .yarnrc.yml) disagree on allowBuilds, minimumReleaseAge, or minimumReleaseAgeExclude.

--json output shape

{
    "packageManager": "pnpm",
    "pinVersions": false,
    "installed": [{ "name": "esbuild", "version": "0.20.0", "hooks": ["postinstall"] }],
    "unapproved": [{ "name": "sharp", "version": "0.32.6", "hooks": ["install"] }],
    "excess": ["removed-pkg"],
    "versionDrift": [{ "from": "esbuild@0.19.0", "to": "esbuild@0.20.0" }],
    "drift": { "hasDrift": false, "packageManager": "pnpm" }
}

sync

Push security.allowBuilds, minimumReleaseAge, and minimumReleaseAgeExclude from vis.config into the active package manager's native config files:

vis security sync
vis security sync --skip-allow-builds       # only sync minimumReleaseAge
vis security sync --skip-min-release-age    # only sync allowBuilds

run

LavaMoat allow-scripts run parity — execute the lifecycle scripts for every package in security.allowBuilds without reinstalling the dependency tree. Use this after installing with ignore-scripts=true and triaging via vis approve-builds.

vis security run                # run preinstall/install/postinstall for every approved package
vis security run --with-root    # also run the workspace root's prepublish + prepare hooks
vis security run --root-only    # skip dependency scripts; run only root prepublish + prepare

Wildcards (@scope/*, name@*) and version-pinned patterns (name@1.2.3, requires pinVersions: true) are honored — the runner expands them against installed packages before invoking each hook. Unapproved packages are skipped silently.

tripwire

Install @lavamoat/preinstall-always-fail as a devDependency. Its preinstall script always fails, but is masked by ignore-scripts=true. If somebody removes that gate, the next install fails loudly instead of silently running every dependency's lifecycle scripts.

vis security tripwire             # install the tripwire devDependency via the detected PM
vis security tripwire --status    # report whether the tripwire is currently installed
vis security tripwire --remove    # delete the tripwire entry from package.json

The installer uses the detected PM's idiomatic add -D invocation (pnpm add -D -w, bun add -d, npm install --save-dev, yarn add -D). --remove only edits package.json — run your PM's install afterwards to clean node_modules.

Per-PM targets

PMallowBuilds targetminimumReleaseAge targetExcludes target
pnpmpnpm-workspace.yaml allowBuilds map + onlyBuiltDependencies listpnpm-workspace.yaml minimumReleaseAge (minutes)minimumReleaseAgeExclude list
bunpackage.json trustedDependenciesbunfig.toml [install] minimumReleaseAge (seconds)minimumReleaseAgeExcludes (plural)
npm(no native allowlist).npmrc min-release-age=<duration> (e.g. 2d, 48h)(npm has no native excludes list)
yarn(no native allowlist).yarnrc.yml npmMinimalAgeGate: "<duration>" (berry only)(yarn has no native excludes list)

Vis canonicalises all durations to minutes. The syncer rounds sub-minute fractional values up to whole minutes before writing so that the same vis-config value round-trips identically across PMs.

Yarn classic (no .yarnrc.yml) is silently skipped because it has no equivalent native setting.

keys-refresh

Force-refresh the cached npm signing keys used by the signatures marshall. The keys are fetched from registry.npmjs.org and cached on disk; use this to drop a stale cache or pre-warm a fresh key set.

vis security keys-refresh           # drop the disk cache and fetch a fresh key set
vis security keys-refresh --clear   # only drop the cache, do not refetch
vis security keys-refresh --json    # emit the refresh result as JSON for tooling

verify-lockfile

Re-validate the entire resolved lockfile closure against the supply-chain policies — vis's counterpart to pnpm v11's lockfile-verification phase. Unlike the pre-install marshalls (which only inspect packages being added), this catches a tampered/poisoned lockfile even on npm/yarn/bun and even when nothing is being added.

vis security verify-lockfile            # re-validate every locked entry; exit non-zero on a violation
vis security verify-lockfile --offline  # skip network-bound policies (firstSeen, publisherChange)
vis security verify-lockfile --json     # emit the verification result as JSON for CI

It composes three configured checks into one pass/fail attestation:

  • security.policies.firstSeen.minutes — block any locked version published less than N minutes ago.
  • security.policies.publisherChange.mode: "no-downgrade" — block a locked version that dropped a provenance attestation a prior version carried.
  • security.blockExoticSubdeps — flag transitive edges resolving from a git repo or remote tarball (honoring security.exoticSubdepsAllow).

When none of the three is configured the run is skipped (exit 0). A missing lockfile fails the attestation rather than silently passing — the closure cannot be attested. firstSeen / publisherChange are network-bound and emit an info-level skip under --offline; blockExoticSubdeps is offline-pure and always runs. Output mirrors pnpm's attestation lines:

✓ Lockfile passes supply-chain policies (148 entries, 0.4s)
✗ Lockfile failed supply-chain policy check (148 entries, 0.4s)
  [firstSeen] evil@1.0.0 was published 30 min ago — below the 1440 min firstSeen cooldown.
  [blockExoticSubdeps] git-dep pulled from exotic source by prod-pkg@1.0.0: github:attacker/evil#deadbeef

Options

vis security list

OptionDefaultDescription
--jsonfalseEmit the report as JSON instead of human-readable text

vis security sync

OptionDefaultDescription
--skip-allow-buildsfalseSkip syncing allowBuilds (trustedDependencies / onlyBuiltDependencies etc.)
--skip-min-release-agefalseSkip syncing minimumReleaseAge and its excludes

vis security run

OptionDefaultDescription
--with-rootfalseAfter dependency scripts, also run the workspace root's prepublish + prepare
--root-onlyfalseSkip dependency scripts and only run the workspace root's prepublish + prepare

vis security tripwire

OptionDefaultDescription
--statusfalseReport whether @lavamoat/preinstall-always-fail is installed
--removefalseStrip @lavamoat/preinstall-always-fail from package.json

vis security keys-refresh

OptionDefaultDescription
--clearfalseOnly clear the cache, do not refetch
--jsonfalseEmit the result as JSON instead of human-readable text

vis security verify-lockfile

OptionDefaultDescription
--jsonfalseEmit the verification result as JSON instead of human-readable text
--offlinefalseSkip network-bound policies (firstSeen, publisherChange)

Drift report (post-install)

After vis install / vis update / vis add, vis automatically runs the drift check and prints a one-line nudge per drifted field. The hint suggests vis security sync as the remediation:

vis.config and pnpm-native config disagree on security settings:
  allowBuilds — only in vis.config: sharp
  minimumReleaseAge — vis.config: 2880 min, pnpm: unset
  Run 'vis security sync' to push vis.config values to the native config.
  • vis approve-builds — interactive review of unapproved build scripts.
  • vis init — bootstrap a vis.config.ts with secure defaults and an optional --sync-native step.
  • vis audit — full supply-chain audit; verify-lockfile is the closure-only attestation subset suitable for a single CI gate line.
Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues