Package Security Check
Workflow
- Treat this as a base JS supply-chain check first. Do not force the result around one CVE, vendor, package family, or incident.
- Before running installs, package-manager mutation commands, or file edits, perform only read-only inspection and present a traffic-light issue analysis:
- 🔴 Blocker: compromise signal, unsafe install path, secret exposure, or policy that allows unreviewed code execution.
- 🟡 Risk: hardening gap, stale package-manager major, broad spec, lifecycle script needing review, or CI weakness.
- 🟢 OK: verified control or no finding.
- After the traffic-light analysis, ask for approval before changing files or executing package-manager operations that can install, update, publish, or rewrite lockfiles.
- From this skill directory, run the baseline scanner against the repo or workspace root:
bash
python3 scripts/check_js_supply_chain.py --root <repo-or-workspace-root>
Use
when the check should fail on hardening gaps. Use
when another tool needs machine-readable output. Use
only when
exists and installed package lifecycle metadata matters.
- For a specific active incident, add one or more IOC profiles:
bash
python3 scripts/check_js_supply_chain.py \
--root <repo-or-workspace-root> \
--ioc data/iocs/npm-supply-chain-2026-05.json \
--since 2026-05-11T19:20:00Z
Refresh incident facts from current advisory sources before relying on a profile. IOC profiles are detection data, not the base policy.
- Inspect the report in this order:
- and
effective_config_findings
package_lifecycle_scripts
, then installed_lifecycle_scripts
when requested
- , including GitHub Actions privilege/cache warnings
recent_package_manager_files
- If any IOC hits appear, stop normal package work. Do not run installs or lifecycle scripts. Report exact files/packages and recommend isolation, credential rotation, and reinstall from a known-good lockfile.
- If no compromise is visible but policy is weak and the user approves changes, patch toward the canonical pnpm 11 policy. Keep one package manager, one lockfile, and one repo-local policy source.
Canonical Policy
Use pnpm 11 or newer as the single package manager because it has the best current pnpm security model: release-age gating, lifecycle-script approval, exotic-subdependency blocking, and trust policy controls.
Verify the current pnpm release before writing
:
bash
npm view pnpm dist-tags --json
Require pnpm 11 or newer. As of 2026-05-12, npm reports
as pnpm
. Do not hardcode that value without rechecking. If the repo's Node runtime cannot run pnpm 11, report it as a compatibility blocker instead of silently falling back to pnpm 10.
Use
devEngines.packageManager
to declare the required major:
json
{
"devEngines": {
"packageManager": {
"name": "pnpm",
"version": ">=11.0.0",
"onFail": "download"
}
}
}
Also pin the verified current stable version in
for reproducibility:
json
{
"packageManager": "pnpm@11.1.1"
}
Treat pnpm 10 or older as
unless the user explicitly approves a temporary exception.
Package Manager Posture
- : canonical baseline. Prefer this for new hardening work.
- : accepted only when the repo intentionally uses Bun and has equivalent local hardening.
- : fallback only. Recommend migration to pnpm 11 unless the repo has a clear documented reason to stay npm.
- : not accepted baseline for this skill. Recommend pnpm 11 or hardened Bun.
Do not present npm as equivalent to pnpm 11. Bun can be accepted as a project-level choice, but still gets checked against Bun-specific hardening.
For npm fallback repos, require exact saves and reproducible CI while recommending pnpm migration:
Do not claim npm has a supported release-age gate unless verified in current npm docs and local
.
For Bun fallback repos, require repo-local
:
toml
[install]
minimumReleaseAge = 604800
exact = true
frozenLockfile = true
saveTextLockfile = true
Do not set
minimumReleaseAgeExcludes
without a reviewed, package-specific reason.
yaml
minimumReleaseAge: 10080
minimumReleaseAgeStrict: true
minimumReleaseAgeIgnoreMissingTime: false
blockExoticSubdeps: true
trustPolicy: no-downgrade
trustPolicyIgnoreAfter: 43200
dangerouslyAllowAllBuilds: false
savePrefix: ""
allowBuilds: {}
Use 7 days (
) for normal repos. Use 3 days only when the repo has a real dependency freshness requirement. Do not set
or
without a reviewed, package-specific reason.
Allow dependency build scripts only after review:
yaml
allowBuilds:
esbuild: true
core-js: false
CI Rules
Require frozen pnpm installs:
bash
pnpm install --frozen-lockfile
Treat these as findings unless the repo has a written reason:
- non-pnpm lockfiles in a pnpm repo
- CI using , , unfrozen , or unfrozen
- npm repos that do not use with a committed lockfile while migration is pending
- Bun repos missing release-age, exact, frozen-lockfile, or text-lockfile policy
- workflows; these are allowed only with a reviewed reason and must not checkout or run untrusted PR code
- shared caches in publish/release pipelines, including GitHub Actions cache, Turborepo, and Nx cache
- any path where PR-controlled cache content can feed a privileged publish/release workflow
- lockfile rewrite during CI/deploy
- , , broad ranges, Git/GitHub shorthands, HTTP tarballs, or external specs
- dependency lifecycle scripts that are not explicitly approved
dangerouslyAllowAllBuilds: true
- workflow use of or publish credentials in broad build jobs
Script
scripts/check_js_supply_chain.py
performs deterministic local checks:
- detects package-manager and lockfile policy
- checks repo-local and effective pnpm hardening settings
- reports npm fallback and Bun fallback hardening gaps
- reports risky direct dependency specs
- reports lifecycle scripts in workspace manifests and optionally installed packages
- reports risky GitHub Actions install/publish/secret patterns
- warns on and shared cache patterns that can become supply-chain escalation paths
- reports package-manager file mtimes after
- applies optional IOC JSON profiles for incident-specific fingerprints, payload files, persistence paths, workflow markers, and known bad package versions
Keep incident profiles under
. Do not add incident-specific constants to the scanner unless they are generic across npm supply-chain attacks.