Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🛡️ PublishGuard

The last line of defense before your npm token spreads a worm.

PublishGuard is a pre-publish tripwire for npm packages. It runs in your own prepublishOnly hook or CI — right before npm publish — and refuses to publish if it detects the behaviors that self-replicating supply-chain worms (Shai-Hulud–class) use to spread from one compromised package into all the others a maintainer owns.

Every other tool protects the people who install packages. PublishGuard protects the supply itself — it stops your package from becoming the next link in a worm's replication chain.

  • 🔒 Zero dependencies. Zero network calls. Pure Node stdlib. A supply-chain tool should not itself be a supply-chain risk.
  • 🧬 Baseline-diff detection. Flags lifecycle scripts (postinstall, prepublish, prepare, …) that appeared or changed since your last known-good commit — the #1 worm injection vector.
  • 🕵️ Behavioral static analysis. Detects token theft (~/.npmrc, NPM_TOKEN, NODE_AUTH_TOKEN), credential harvesting, self-npm publish, and exfil beacons in what you're about to ship.
  • 🚦 Blocks the publish. Exits non-zero so CI/prepublishOnly halts before a compromised artifact ever reaches the registry.
  • 📤 SARIF output. Findings ingest natively into GitHub code scanning.

PublishGuard is a defensive tool. It performs static analysis of your own package on your own machine/CI. It never executes package code and never contacts the network.

Why "pre-publish" is the missing layer

Layer Tools What they protect
Install-time (consumer) many scanners People installing packages
Publish-time (maintainer) ← PublishGuard The registry / the supply chain itself

Worms like Shai-Hulud are exponential precisely because a single compromised maintainer auto-publishes the worm into every package they control. PublishGuard breaks that replication step.

Quick start

npm install -g @black-lotus/publishguard

publishguard check                      # scan the current package
publishguard baseline                   # snapshot current lifecycle scripts as known-good
publishguard check --sarif out.sarif

Or without installing:

npx @black-lotus/publishguard check

The package is published under the @black-lotus scope; the CLI command is plain publishguard.

Wire it into your package so a publish is blocked if anything looks wormy:

{
  "scripts": {
    "prepublishOnly": "publishguard check --strict"
  }
}

Or drop the GitHub Action into .github/workflows/ (see examples/).

What it detects

Rule Severity What it catches
PG001 lifecycle-script-added CRITICAL A new install/publish lifecycle script not in your baseline
PG002 lifecycle-script-changed HIGH An existing lifecycle script whose body changed
PG003 npm-token-access CRITICAL Code reading .npmrc / NPM_TOKEN / NODE_AUTH_TOKEN
PG004 credential-harvest HIGH Reads of AWS/GCP/GitHub creds or bulk process.env dump
PG005 self-publish CRITICAL Code invoking npm publish / registry publish API
PG006 network-exfil HIGH Beacons to raw IPs, webhook.site, known C2 patterns
PG007 obfuscated-payload MEDIUM eval/Function/base64-Buffer decode of remote strings

Threat model — what this does and doesn't stop

PublishGuard is a tripwire against automated, non-targeted worms — malware that injects the same replication payload into every package it can reach. That class of attack doesn't adapt per-repository, so a local check it doesn't know about breaks the chain.

It is not a defense against a targeted attacker with commit access: anyone who can edit package.json to inject a lifecycle script can also remove the prepublishOnly hook or regenerate the baseline. For that threat you need protections PublishGuard can't provide — 2FA on npm, provenance/trusted publishing, protected branches, and review requirements. Run PublishGuard in CI (not only locally) so removing the hook is at least visible in the diff.

False positives & suppression

If a line must legitimately contain a flagged string (e.g. you're building security tooling), append a suppression comment — the same idea as eslint-disable-line:

const pattern = /npm publish/; // publishguard-disable-line

PublishGuard eats its own dog food: its detection patterns use this marker, so publishguard check on the PublishGuard repo itself is clean.

License

MIT — use it anywhere, including commercially.

About

🛡️ The last line of defense before your npm token spreads a worm. Pre-publish tripwire that blocks self-replicating supply-chain worms (Shai-Hulud class). Zero deps, zero network, SARIF output.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages