@ajna-inc/npmvc
v0.3.4
Published
Verifiable-credential supply chain compliance for npm. Sign attestations, verify dependencies, revoke compromised packages.
Maintainers
Readme
@ajna-inc/npmvc
Supply-chain credential system for npm. Sign attestations when you publish, verify every dependency when you install.
Pure TypeScript. Zero native dependencies. Works everywhere npm runs.
Quick start — consumer
npm install -g @ajna-inc/npmvc
npmvc install-hook # default preset: "balanced"
# or:
npmvc install-hook --preset strict # block on unattested too
npmvc install-hook --preset permissive # warn-only, never blocksThis adds a preinstall hook to your package.json and writes
npmvc-policy.json. Every subsequent npm install then runs
npmvc verify first.
What each preset does
| Preset | Bad signature | Tampered bytes | Revoked credential | Unattested dep |
| ------------ | ------------- | -------------- | ------------------ | -------------- |
| strict | block | block | block | block |
| balanced (default) | block | block | block | warn |
| permissive | warn | warn | warn | warn |
Most of npm is still unattested, so balanced warns on unattested
deps but blocks the explicitly-bad ones. Flip to strict once your
internal packages are all attested.
There is no env-var bypass. To temporarily disable, edit
package.json#scripts.preinstall directly.
Quick start — publisher
npm install -g @ajna-inc/npmvc
npmvc init # plaintext key
# or:
npmvc init --encrypt --passphrase "..." # encrypted at rest
npm pkg set "publisher"="$(jq -r .did ~/.npmvc/wallet/key.json)"
npm pkg set scripts.prepack="npmvc attest --registry https://npmvc.com"
npm pkg set files='["dist", ".npmvc"]' --jsonEvery subsequent npm publish now runs attest first: signs
Provenance + Lifecycle + SBOM credentials, binds them to the content
of the tarball, and uploads to the npmvc registry.
To rotate the keys you sign with, see SECURITY.md.
What gets checked
- Signatures — DSSE envelopes verified against publisher DID (Ed25519)
- Tarball digest — installed bytes are hashed and compared to the signed claim
- Revocation — credentials revoked by the publisher are refused
- Age — versions younger than
minAge(default 7d) are flagged - Publisher continuity — DID changes across versions block (
did:webvhrotations are allowed via SCID-prefix matching) - Trust root —
trustedIssuersURL fetched, Ed25519-signed by the pinned root DID - Lifecycle scripts —
postinstallflagged unless whitelisted - Transitive deps — full lockfile tree (npm, pnpm, yarn)
- Audit credentials — third-party scanner verdicts with trustedAuditIssuers + auditMaxAge
- SBOM — CycloneDX SBOM signed and bundled
Policy
npmvc-policy.json, written by install-hook --preset <name>:
{
"enforcement": "block",
"transitive": true,
"minAge": "7d",
"unattested": "warn",
"unattestedMinAge": "30d",
"trustedIssuers": ["https://npmvc.com/trust-root.json"],
"trustedAuditIssuers": [],
"auditMaxAge": "180d",
"attestationSources": ["local", "registry"],
"registry": "https://npmvc.com",
"allowPrivateRegistry": false,
"publisherContinuity": {
"required": true,
"onUnauthorizedChange": "block",
"pinned": { "express": "did:key:z6Mk..." }
},
"revocation": {
"required": true,
"registryUrl": "https://npmvc.com",
"maxCacheAge": "10m"
},
"lifecycleScripts": { "postInstall": "whitelisted", "whitelist": [] },
"waivers": []
}Set allowPrivateRegistry: true if you point registry at an internal
mirror or http:// host. By default the SSRF guards refuse private IPs.
CI/CD
Publish from GitHub Actions:
- name: Attest + Publish
env:
NPMVC_KEY: ${{ secrets.NPMVC_KEY }} # contents of ~/.npmvc/wallet/key.json
NPMVC_KEY_PASSPHRASE: ${{ secrets.NPMVC_KEY_PASSPHRASE }} # only if the key is encrypted
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
npm install -g @ajna-inc/npmvc
mkdir -p ~/.npmvc/wallet && echo "$NPMVC_KEY" > ~/.npmvc/wallet/key.json && chmod 600 ~/.npmvc/wallet/key.json
npm publish --access publicprepack runs npmvc attest automatically before npm packs the tarball.
See packages/github-action for a packaged
action with the same behaviour.
Key management
npmvc wallet encrypt --passphrase "..." # encrypt the in-use key at rest
npmvc wallet decrypt --passphrase "..." # undo (back to plaintext)
npmvc wallet export --passphrase "..." > key.enc # portable encrypted backup
npmvc wallet import --passphrase "..." --input key.enc # restore from backup
# Recovery (lost key, guardians configured)
npmvc wallet add-guardian --did did:key:z6MkFRIEND...
npmvc wallet set-threshold --threshold 2
npmvc wallet recover --old-did did:key:... --guardians did:key:a,did:key:b
npmvc wallet approve-recovery --request recovery-request.json
npmvc wallet apply-recovery --approvals approval-1.json,approval-2.json
# Rotation (did:webvh only)
npmvc wallet rotateCommands
| Command | Purpose |
|---|---|
| init [--encrypt --passphrase ...] | Create publisher DID |
| attest [--registry URL] | Sign attestations + optionally sync to registry |
| verify | Verify the installed dep tree |
| install-hook [--preset strict\|balanced\|permissive] | Add preinstall hook + policy |
| policy init / policy show | Manage the policy file |
| register | Register your DID on an npmvc registry |
| audit publish / audit import | Issue / import a third-party scanner credential |
| credential revoke | Revoke a credential on the registry |
| credential handover | Sign a maintainer-handover credential |
| wallet encrypt / decrypt | Toggle at-rest encryption of the key file |
| wallet export / import | Encrypted key backup |
| wallet rotate (did:webvh) | Rotate to the pre-committed backup key |
| wallet add-guardian, set-threshold, guardians | Recovery setup |
| wallet recover, approve-recovery, apply-recovery | Recovery flow |
| anchor publish | Build a transparency-log Merkle root |
Threat model and trust root
See SECURITY.md for the pinned community trust-root DID, rotation policy, and known limitations.
Packages in this repo
@ajna-inc/npmvc(the CLI) — rootpackage.json, sources insrc/packages/npmvc-web— the npmvc.com registry/UIpackages/github-action— GitHub Action wrapperpackages/verdaccio-plugin— early-stage stub, not for production use
License
Apache-2.0
