hookseal audits npm package-lock.json files for dependency install hooks that can execute during npm install or npm ci.
Recent npm supply-chain incidents keep landing in the same place: developer and CI machines run lifecycle scripts from packages before humans have reviewed what changed. hookseal makes that boundary explicit. It reads the committed lockfile, finds dependencies marked with hasInstallScript, and fails unless they are in a small reviewed allowlist.
npm audit is useful after a vulnerability is known. Install hooks are earlier in the chain: a new transitive package can gain execution just because a lockfile changed. Larger commercial scanners can cover this, but small projects often need a local, deterministic CI check that does not call an external service.
hookseal is intentionally narrow:
- It scans npm lockfiles offline.
- It focuses on lifecycle hook execution, not every package risk.
- It uses explicit allowlists instead of scoring packages by popularity.
- It works as both a library and a CLI.
Install from npm:
npm install -D @foxom/hooksealRequires Node.js 20 or newer.
Audit the current project:
npx hooksealAudit another directory:
npx hookseal ./packages/webAllow a reviewed dependency hook:
npx hookseal --allow esbuild --allow @parcel/watcher@2.5.1Use JSON output in CI:
npx hookseal --json --fail-on mediumIgnore dev-only dependency hooks:
npx hookseal --no-devCreate .hookseal.json at the project root:
{
"allow": [
"esbuild",
"@parcel/watcher@2.5.1"
],
"allowedRootScripts": [
"prepare"
]
}Allow by exact package name when any version is acceptable, or by package@version when you want the review to expire on upgrade.
import { auditPackageLock, formatTextReport } from "@foxom/hookseal";
const lockfile = {
lockfileVersion: 3,
packages: {
"": { name: "app", version: "1.0.0" },
"node_modules/esbuild": {
version: "0.25.0",
integrity: "sha512-example",
hasInstallScript: true
}
}
};
const report = auditPackageLock(lockfile, {
allowedPackages: ["esbuild@0.25.0"]
});
console.log(report.ok);
console.log(formatTextReport(report));Reads package-lock.json, optional package.json, and optional .hookseal.json from options.cwd.
Options:
cwd: project directory. Default:process.cwd().lockfile: lockfile path relative tocwd. Default:package-lock.json.packageJson: package metadata object for direct API use.policyPath: policy file path, orfalseto disable policy loading.allowedPackagesorallow: package names orpackage@versionentries.allowedRootScripts: root lifecycle scripts allowed inpackage.json.includeDev: include dev-only dependencies. Default:true.
Audits a parsed package lock object and returns:
ok:truewhen no findings are present.lockfileVersion: detected lockfile version.packageNameandpackageVersion: root package metadata when available.totals: finding counts by severity.findings: sorted findings with rule id, severity, package, path, message, and remediation.
Formats a human-readable terminal report. Pass { verbose: true } to include resolved tarball URLs.
Returns whether a report should fail CI for low, medium, or high.
HS001: dependency declares an install lifecycle script and is not allowed.HS002: rootpackage.jsondefines an install lifecycle script and is not allowed.HS003: dependency declares an install lifecycle script without integrity metadata.HS004: lockfile has nopackagesmetadata, so hook status cannot be verified.
hookseal relies on npm lockfile metadata instead of installing packages or fetching tarballs. That keeps it safe to run before install and makes it useful in CI review gates. The tradeoff is scope: it only knows what the lockfile records. If a project uses another package manager, pair it with that manager's own hook approval mechanism.
The allowlist is small on purpose. Native builders and platform packages often need install hooks, but each hook should be an explicit review decision, not lockfile noise.
This is not a package-manager wrapper and it does not mutate package.json. Tools such as install-script blockers can control whether scripts run; hookseal is the review gate that answers a narrower question from committed files: "Did this lockfile introduce executable dependency hooks that we have not approved?"
npm install
npm test
npm run typecheck
npm pack --dry-runRun the CLI locally:
node bin/hookseal.js --helpMIT