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 --jsonWhat 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, requirespinVersions: 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 abinding.gypbut no explicit install script — npm runsnode-gyp rebuildfor them implicitly. - Stale allowlist entries —
security.allowBuildskeys 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 afrom → tomigration suggestion. - Native-config drift — when
vis.configand the PM's own config files (pnpm-workspace.yaml,bunfig.toml,.npmrc,.yarnrc.yml) disagree onallowBuilds,minimumReleaseAge, orminimumReleaseAgeExclude.
--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 allowBuildsrun
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 + prepareWildcards (@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.jsonThe 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
| PM | allowBuilds target | minimumReleaseAge target | Excludes target |
|---|---|---|---|
| pnpm | pnpm-workspace.yaml allowBuilds map + onlyBuiltDependencies list | pnpm-workspace.yaml minimumReleaseAge (minutes) | minimumReleaseAgeExclude list |
| bun | package.json trustedDependencies | bunfig.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 toolingverify-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 CIIt 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 (honoringsecurity.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#deadbeefOptions
vis security list
| Option | Default | Description |
|---|---|---|
--json | false | Emit the report as JSON instead of human-readable text |
vis security sync
| Option | Default | Description |
|---|---|---|
--skip-allow-builds | false | Skip syncing allowBuilds (trustedDependencies / onlyBuiltDependencies etc.) |
--skip-min-release-age | false | Skip syncing minimumReleaseAge and its excludes |
vis security run
| Option | Default | Description |
|---|---|---|
--with-root | false | After dependency scripts, also run the workspace root's prepublish + prepare |
--root-only | false | Skip dependency scripts and only run the workspace root's prepublish + prepare |
vis security tripwire
| Option | Default | Description |
|---|---|---|
--status | false | Report whether @lavamoat/preinstall-always-fail is installed |
--remove | false | Strip @lavamoat/preinstall-always-fail from package.json |
vis security keys-refresh
| Option | Default | Description |
|---|---|---|
--clear | false | Only clear the cache, do not refetch |
--json | false | Emit the result as JSON instead of human-readable text |
vis security verify-lockfile
| Option | Default | Description |
|---|---|---|
--json | false | Emit the verification result as JSON instead of human-readable text |
--offline | false | Skip 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.Related
vis approve-builds— interactive review of unapproved build scripts.vis init— bootstrap avis.config.tswith secure defaults and an optional--sync-nativestep.vis audit— full supply-chain audit;verify-lockfileis the closure-only attestation subset suitable for a single CI gate line.