JSPM

@vouchjs/vouch

0.4.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 667
  • Score
    100M100P100Q83603F
  • License MIT

A dependency-decision ledger: every dependency is recorded, explained, and reviewable in the PR — for Node.js projects and coding agents.

Package Exports

  • @vouchjs/vouch
  • @vouchjs/vouch/package.json

Readme

vouch

banner

A dependency-decision ledger for Node.js projects and coding agents.

It doesn't prevent the bypass. It makes the bypass impossible to hide.

vouch doesn't decide whether a dependency is safe. It makes sure every dependency that enters your repo was recorded, explained, and reviewable in the pull request — a memory and conscience for your package.json.

vouch demo


Contents


The idea

  1. Every dependency is a decision. Adding one should be recorded — with who and why — not slipped into a lockfile diff nobody reads.
  2. The record lives in your repo. Decisions go into a committed ledger (.security/dependency-approvals.json), keyed by name@version and wrapped in a { "version": 2, "entries": { … } } envelope, visible in the PR diff and auditable forever. Ledgers from 0.1.x (name-keyed) auto-migrate on first read; the updated file appears in the next mutation's diff.
  3. The record stays honest over time. When a dependency you recorded later gains a known advisory, or its version drifts from what was reviewed, CI surfaces it until a human re-decides.

What you'll see

A dependency added without vouch fails CI:

✦ vouch  Dependency review failed

  - axios: missing ledger entry

  Next — record the unrecorded dependency:
    vouch axios

Adding one with vouch records the decision (and blocks risky packages until you say why):

✦ vouch  Dependency needs review

  esbuild@0.28.0
  - install-time script detected: postinstall

  Next:
    vouch esbuild --force-with-reason "<why this dependency is needed>"

Once recorded, the ledger entry travels with the diff and CI is green:

✦ vouch  Dependency review passed

  All dependencies are recorded.

Quick start

Requirements: Node.js 18+. No other dependencies.

Install — run on demand with npx, or install the CLI globally:

npx @vouchjs/vouch --help     # no install
npm install -g @vouchjs/vouch # or install the `vouch` command

Bootstrap the config (optional, recommended) — one command, never overwrites:

vouch init                # writes vouch.config.{mjs,js} with all defaults shown

That gives you a typed config (Playwright-style): every option visible with its default, ready to be edited. Delete the keys you're happy with and vouch will pick up future defaults; change the ones you're not.

For full editor autocomplete + type errors on the generated config, also install vouch as a dev dependency so its types are reachable from your project:

npm install -D @vouchjs/vouch

vouch init detects whether vouch is in your node_modules and writes the appropriate variant — import { defineConfig } from "@vouchjs/vouch" when installed, a JSDoc-typed plain export when not. Either variant loads at runtime; only the editor experience differs.

Add a dependency — instead of npm install / pnpm add, run:

vouch some-package        # reviews, installs, and records the decision
vouch some-package -D     # devDependency

Gate it in CI — add one step that fails the build on any unrecorded dependency. Drop this in .github/workflows/vouch.yml (also in examples/github-actions-check.yml):

name: vouch
on: [pull_request]
jobs:
  vouch:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npx @vouchjs/vouch check

That's it. A raw npm install <pkg> (by a human or an agent) can no longer reach main unrecorded. A fresh clone is unaffected — npm install / npm ci restores the lockfile as usual, and check passes because the committed ledger already covers every dependency.


How it works

When you add a package (vouch <pkg>):

  1. Reviews it: version age and install-time scripts (blocked by default).
  2. Warns you right then if the version already has a known CVE — so check is never the first messenger. Configurable: cveAtInstall: "warn" (default), "block" (refuse to install advisories at or above cveAtInstallMinSeverity, default "high"), or "off".
  3. Installs it and records the decision (version, risk, who added it and why) in the ledger.

In CI (vouch check) — three states per dependency: recorded (ok), unrecorded (blocked), or needs review (blocked). It fails when:

  • a direct dependency in package.json has no ledger entry — added without vouch (covers dependencies, devDependencies, and optionalDependencies; peerDependencies too when checkPeerDependencies is enabled);
  • a dependency gained a CVE that no human has acknowledged;
  • a high-risk entry has no reason recorded for the reviewer to judge;
  • version driftcheck resolves each dependency's installed version from node_modules and asserts that exact name@version was reviewed. Run check after a complete install. If installed versions have drifted from what was recorded, run vouch <pkg>@<installed> to re-record them;
  • pinning — an opt-in requirePinned ("warn", default "off") flags deps that use a range instead of an exact version, suggesting the recorded version to pin to.

Scope: vouch check governs all workspace packages discovered from the repo root, covering dependencies, devDependencies, and optionalDependencies (plus peerDependencies when checkPeerDependencies is on). All workspaces share one root ledger. See Using vouch in a monorepo.


vouch records; the PR review approves

This distinction is the heart of the tool:

  • vouch records a decision. The ledger entry — who added it (addedBy, from git config), why (reason), at what version and risk — is attribution. It's self-asserted, not by itself an authorization.
  • The PR/MR review is the authorization. A human approving the pull request, with the ledger entry visible in the diff, is the act that approves. vouch makes the decision conscious and reviewable; it doesn't try to verify or replace that review.

You can always force a thing through with --force-with-reason. You can never do it invisibly — the reason and your identity land in the committed ledger, in front of the reviewer. See THREAT_MODEL.md for what vouch does and does not defend.


When check blocks on a CVE

A block isn't damage — it's a pause: a dependency you recorded carries a CVE no human has signed off on — either one present when you recorded it (not yet acknowledged), or one that appeared since (check flags it as NEW). Three honest options, in order of preference:

  1. Fix itvouch <pkg>@<patched-version> to record a fixed release.
  2. Remove or replace it — drop the dependency, or swap in a lighter one.
  3. Accept it knowingly — once you've judged the risk acceptable (dev-only, unreachable code path, no fix yet), vouch acknowledge <pkg> --reason "<why this is acceptable>".

acknowledge re-queries advisories for the recorded version and records the acknowledged set, who acknowledged it (from git config), why, and when — visible in the PR diff. It refuses to write while offline (we never record an acknowledgement we couldn't verify), and it blocks only on a CVE it confirmed: offline or a stalled endpoint fails open (a warning, never a failed build), and only the specific dependency that drifted — never your whole project.


Commands

Command What it does
vouch <pkg> [-D] Review, install, and record a dependency (-D for devDependencies).
vouch <pkg> --force-with-reason "<why>" Override a block, recording the reason in the ledger.
vouch check CI gate: fail on unrecorded deps, unexplained high-risk, CVE drift, or version drift.
vouch adopt Baseline the whole repo: records every installed, unrecorded dependency across all workspaces (deduped by name@version). Never installs. Idempotent.
vouch acknowledge <pkg> --reason "<why>" Knowingly accept a dependency's current advisories (CVE drift).
vouch init Bootstrap vouch.config.{mjs,js} with all defaults shown + detected packageManager. Refuses to overwrite.
vouch --help · vouch --version Help (with the wordmark) and version.

Environment: VOUCH_ADVISORY_URL overrides the npm advisory endpoint (for enterprise mirrors/proxies).


Using vouch in a monorepo

vouch is workspace-aware for pnpm (pnpm-workspace.yaml packages:) and npm/yarn (package.json workspaces). It discovers every workspace package, takes the union of their declared dependencies, and operates against one root ledger at <repo-root>/.security/dependency-approvals.json.

  • Baseline the repo: run vouch adopt at the repo root — it records every installed, unrecorded dependency across all workspaces, deduped by name@version (two workspaces on different versions of the same package get two entries).
  • Enforce in CI: run vouch check after installing. It resolves each dependency's installed version (from node_modules, falling back to pnpm-lock.yaml for workspaces with no local node_modules) per workspace and fails if that exact name@version was never reviewed. Run it after a complete install (pnpm install --frozen-lockfile).
  • Add a dependency: run vouch <pkg> inside the workspace that needs it. The package manager installs it into that workspace; vouch records it to the root ledger at the version installed for that workspace.

Internal dependencies (workspace:, link:, file:, catalog:) are skipped — they are intra-repo edges, not external packages. Single-package repos are unaffected beyond the name@version keying. Violations are grouped by workspace, and the fix-it list is capped per group with a pointer to vouch adopt.


Configuration

The preferred form is a typed config — vouch.config.{ts,mjs,js,cjs} — exporting a defineConfig() call. Every key is optional; the defaults flow through. Generate one with vouch init:

// vouch.config.mjs (or .js if your project has "type": "module" — vouch init picks correctly)
import { defineConfig } from "@vouchjs/vouch";

export default defineConfig({
  packageManager: "auto",            // "auto" | "pnpm" | "npm" | "yarn"
  allowScopedPackages: [],

  // Install-time gate
  minimumVersionAgeHours: 24,
  warnVersionAgeHours: 168,
  blockInstallScripts: true,
  requireCooldownConfigured: false,

  // CI gate — `vouch check`
  requirePinned: "off",              // "warn" | "off"
  checkPeerDependencies: false,      // also gate peerDependencies (prod/dev/optional always gated)

  // CVE handling at add time
  cveAtInstall: "warn",              // "warn" | "block" | "off"
  cveAtInstallMinSeverity: "high",   // "low" | "moderate" | "high" | "critical"
});

Runtime validation still fires (a typo'd enum value fails loudly instead of silently downgrading a gate).

Editor types — the install-as-devDep step

To make import { defineConfig } from "@vouchjs/vouch" resolve and light up editor autocomplete on the config, vouch needs to be in your project's node_modules:

npm install -D @vouchjs/vouch

vouch init detects this automatically and writes the appropriate variant:

State Generated config
@vouchjs/vouch in your node_modules import { defineConfig } from "@vouchjs/vouch"; export default defineConfig({ ... }) — full editor types via the bundled .d.ts
@vouchjs/vouch not installed locally /** @type {import("@vouchjs/vouch").Config} */ export default { ... }no runtime import, loads anywhere; types light up the moment you npm install -D @vouchjs/vouch

Both variants load at runtime; only the editor experience differs.

File format

vouch.config.ts works on Node 23+ (or 22.6+ with --experimental-strip-types). For older Node, write .js/.mjs/.cjs — vouch loads any of them via dynamic import().

Package manager detection

packageManager: "auto" reads the Corepack packageManager field from package.json first, then sniffs lockfiles (pnpm-lock.yamlyarn.lockpackage-lock.json), then falls back to npm.

Legacy: .safe-dep.json

Existing projects with a .safe-dep.json keep working (vouch reads it when no vouch.config.* is present). Editor autocomplete via JSON $schema is also supported — point at schema.json in the repo. New projects should prefer vouch.config.{ts,mjs,js}.


For coding agents

AGENTS.md tells agents to use vouch instead of raw installs, to explain why a dependency is needed before adding it, and — crucially — not to silence the gate on a human's behalf. As agents add more dependencies, the ledger becomes the place a human reviews those decisions, asynchronously and accountably.


What it is not

Not a scanner. Deep per-package analysis (typosquatting, behavioral) is the job of tools like npq and Socket. We don't scan for CVEs to discover them — we record the advisory posture of what you recorded and flag drift after the fact. vouch owns provenance and enforcement.

Not a replacement for your package manager's native defenses, either. Modern versions ship install-time gates — pnpm's minimumReleaseAge (default on in pnpm 11), Yarn's npmMinimalAgeGate, npm's release-age controls. vouch complements them: those gate what installs; vouch records who decided, and why, where the PR can see it — the one thing none of them do. On package-manager versions without those defaults (e.g. pnpm 9), vouch's install-time review is the gate you'd otherwise lack.

Zero dependencies

"dependencies": {}. Built on Node 18+ built-ins. A dependency-security tool with no dependencies of its own.