📦 Installing this in an AI coding agent? See INSTALL.md — copy-paste instructions for Claude Code, Codex, Cursor, opencode, and generic agent harnesses. French version: README.fr.md.
A Claude Code skill that hardens any Node.js or Python project against package
supply-chain attacks — the kind that took down tj-actions, polyfill.io,
tanstack, coa, ua-parser-js, and dozens of others.
The core idea is simple: most malicious package versions are live for only a few hours before detection. Refusing to install any package published in the last 7 days eliminates the attack window entirely.
This skill also pins exact versions, blocks unauthorized lifecycle scripts, regenerates lockfiles, and audits Renovate/Dependabot so the protection survives every future dependency update.
/package-cooldown
The skill runs an interactive workflow:
- Diagnose — read-only audit of current pinning, lockfile state, and Renovate/Dependabot configuration.
- Configure machine-level guards —
~/.npmrc,~/.bunfig.toml, pnpm config,~/.config/pip/pip.conf, mise paranoid mode. - Configure project-level guards — pin exact versions in
package.json/pyproject.toml, setpnpm-workspace.yamlpolicies, generateuv.lock. - Nuke and reinstall — delete
node_modules+ lockfile, reinstall, commit the fresh lockfile that actually reflects the pinned versions. - Report — structured before/after summary.
| Attack vector | Mitigation |
|---|---|
| Newly published malicious version | Release-age cooldown (minimumReleaseAge) |
postinstall payload at install time |
ignore-scripts, allowBuilds, enableScripts: false |
| Transitive dep swapped for a git URL | pnpm blockExoticSubdeps |
| Trust evidence regression (signed → unsigned) | pnpm trustPolicy: no-downgrade |
^/~ re-introducing new versions |
save-exact=true, rangeStrategy: pin |
| Tampered lockfile in CI | Lockfile committed + --frozen-lockfile |
| Unpinned Python deps | uv.lock + SHA256 hashes / --require-hashes |
| Unpinned runtime (Node, Python) | mise.toml exact versions + paranoid = true |
| Future updates merging fresh packages | Renovate minimumReleaseAge: "7 days" |
| Manager | Native release-age cooldown | Postinstall blocking |
|---|---|---|
| pnpm v10+ | YES — minimumReleaseAge (minutes) |
YES — empty allowBuilds allowlist by default |
| bun | YES — minimumReleaseAge (seconds) |
YES by default — only trustedDependencies run |
| npm 11 | NO — min-release-age is parsed but ignored |
Opt-in: ignore-scripts=true |
| yarn berry v4 | NO (use Verdaccio proxy) | YES — enableScripts: false |
| yarn v1 | NO (inherits .npmrc) |
Opt-in: ignore-scripts=true |
| uv / pip | NO (use uv.lock + hashes) |
n/a (wheels run no JS scripts) |
| mise | NO | n/a (use paranoid = true) |
package-cooldown/
SKILL.md Main guide loaded when /package-cooldown is invoked
README.md You are here
README.fr.md French version
scripts/
cooldown.sh Main entry point (bash/zsh/fish compatible)
lib.sh Sourced helpers — logging, config paths, version checks
merge_config.py Atomic, idempotent config-file merger (9 subcommands)
You can call the scripts directly without going through the skill workflow:
# Apply a 7-day cooldown to every detected package manager (non-interactive)
bash scripts/cooldown.sh --days 7 --non-interactive
# Specific managers only
bash scripts/cooldown.sh --days 7 --manager bun,pnpm,uv
# Preview every write without touching disk
bash scripts/cooldown.sh --days 7 --dry-run
# Quiet mode for CI logs
bash scripts/cooldown.sh --days 7 --quiet --non-interactiveSupported managers: npm pnpm yarn pip uv bun deno cargo go conda.
Managers not on PATH are skipped with a warning, never a hard error.
Stdin not a TTY (CI, agents, pipes) → automatically non-interactive.
- Atomic writes — every config write goes through a temp file +
os.replace()so a crash never leaves a half-written file. - Idempotent — running the skill twice produces the same result. Existing lines, comments, and auth tokens are preserved.
- Permissive — managers not installed are skipped with a warning, never an error. Cargo, Go, and conda are reported with their best-available mitigations (no native cooldown exists for those ecosystems as of 2026).
- Shell-agnostic — works from bash, zsh, fish, and any agent harness via the
#!/usr/bin/env bashshebang. - Defense in depth — combines four layers: release-age cooldown, exact-version pinning, lifecycle-script blocking, and trust-policy enforcement.
For projects on GitHub, the skill can also generate a .github/workflows/socket-basics.yml
that runs Socket Basics on every PR
touching package.json, lockfiles, or Python manifests. It complements the
cooldown: the cooldown blocks brand-new malicious versions from landing during
install, Socket Basics flags issues in the code that did get installed (SAST,
secret scanning, dependency reachability).
Includes opinionated improvements over the stock template — SHA-pinned action,
path filters for every lockfile this skill cares about, concurrency to cancel
stale runs, and fetch-depth: 0 so Socket sees accurate dependency diffs.
Requires a Socket API key.
When a package compromise hits the registry — tj-actions/changed-files in
March 2025, polyfill.io in June 2024, ua-parser-js in 2021 — the malicious
version is usually live for 2 to 24 hours before Socket, Snyk, Aikido, or
npm itself catches it and pulls the version.
If your npm install runs in that window, the malware lands in node_modules,
runs its postinstall script, and starts exfiltrating secrets before any human
review.
A 7-day cooldown closes that window for every present and future incident.
MIT.