How cc-settings defends Claude Code against the supply-chain attack class that emerged in 2026, and what to do if you suspect compromise.
In May 2026, the "Mini Shai-Hulud" npm/PyPI worm compromised 172 packages across @tanstack, @mistralai, @guardrails-ai, @uipath, @opensearch-project and others. The persistence mechanism specifically targets AI developer tooling:
- Compromised package's
postinstallruns a 2.3 MB obfuscated payload. - Payload writes a
SessionStarthook into~/.claude/settings.json(and a task into.vscode/tasks.json). - The hook re-executes the payload on every Claude Code session — even
after
npm uninstallremoves the package fromnode_modules. - The payload exfiltrates credentials, scans for more secrets, and propagates to packages the developer publishes.
Why this works: Claude Code hooks are advisory shell commands that run
on every event. There is no per-hook signing, sandboxing, or allowlist in
the base product. Anything that can write settings.json can persist.
Three layers, all installed by setup.sh:
At install time, setup.sh writes a SHA256 of the canonicalized hooks
section of your merged ~/.claude/settings.json to
~/.claude/.cc-settings-hooks-fingerprint.
On every SessionStart, the verify-hooks.ts hook re-hashes the current
hooks block and compares. Mismatch surfaces a loud terminal warning
with remediation steps. Match is silent.
Source: src/lib/hooks-fingerprint.ts, src/hooks/verify-hooks.ts.
A standalone scanner that classifies every hook command in
~/.claude/settings.json into:
| Severity | Meaning |
|---|---|
| trusted | Matches the cc-settings shipped pattern: bun "$HOME/.claude/src/{scripts,hooks,lib}/<name>.ts". Or a compound of those. |
| unknown | Doesn't match the trusted pattern. User-added hooks land here — review manually, then either remove or re-run setup.sh to fingerprint them. |
| suspicious | Matches a known supply-chain malware signature: curl | sh, wget | bash, base64 decode + exec, eval $(...), node -e, python -c, /tmp/<exec>, hidden node_modules/.bin/, atob(...), opaque base64 blob (>250 char single-token). Exit code 1. |
Exit code is non-zero on any suspicious finding so CI can gate on this.
Source: src/lib/audit-hooks.ts, src/scripts/audit-hooks.ts.
Every hook command that cc-settings ships starts with
bun "$HOME/.claude/src/{scripts,hooks,lib}/<name>.ts". This is the
structural invariant the auditor and fingerprint rely on. Injected hooks
from compromised packages do not match this shape — they call inline
Node/Python, decoded base64, or /tmp/ payloads. The asymmetry is the
defense.
Future hook additions to config/40-hooks.json must follow the same
convention. If a third-party tool needs a hook, wrap it in a cc-settings
script under src/scripts/ rather than referencing the third-party binary
directly.
⚠ cc-settings: hooks-block fingerprint mismatch — SUSPICIOUS HOOKS DETECTED
Step 1 — Audit. Run bun run audit:hooks from anywhere. It will print
every hook in ~/.claude/settings.json grouped by severity, with the
reasons each suspicious entry was flagged.
Step 2 — Triage.
- If the suspicious entry is a tool you knowingly installed (e.g., a CI helper), that's a false positive — see Step 4.
- If you don't recognize it, you're likely compromised. Continue to Step 3.
Step 3 — Remediate compromise.
# 1. Back up the current settings.json before touching it.
cp ~/.claude/settings.json ~/.claude/settings.json.compromised-$(date +%s)
# 2. Open settings.json in your editor.
$EDITOR ~/.claude/settings.json
# 3. Manually delete every entry the auditor flagged as suspicious.
# Keep the legitimate cc-settings entries (bun "$HOME/.claude/src/...").
# 4. Re-run setup.sh from your cc-settings clone to refresh the fingerprint
# against the now-clean hooks block.
cd ~/.claude/cc-settings && bash setup.sh
# 5. Investigate which package introduced the malicious hook.
# Recent installs are the place to start:
grep -l "postinstall" node_modules/*/package.json | xargs -I{} dirname {} | xargs -I{} basename {} | sort -u | tail -20
# 6. Rotate any credentials that were on disk while the hook had a chance
# to run: ~/.aws/credentials, ~/.npmrc auth tokens, ~/.config/gh/hosts.yml,
# SSH keys, .env files in active projects.Step 4 — False positive (legitimate custom hook).
If the unknown/suspicious entry is something you added intentionally:
# Re-run setup.sh — the merger preserves your custom hooks AND refreshes
# the fingerprint. After this, the warning clears on the next session.
cd ~/.claude/cc-settings && bash setup.shThe fingerprint is deliberately refreshed only by setup.sh, never by the
auditor itself. If audit:hooks could update the fingerprint, malware
could call it to whitelist itself.
If you maintain personal hooks alongside cc-settings:
- Add the entry to
~/.claude/settings.json. - Run
bun run audit:hooks— confirm only your new entry shows up as "unknown" (not "suspicious"). Suspicious means your pattern overlaps with a known-bad signature; rewrite it. - Run
setup.shto fingerprint the new state. - Or: contribute the hook upstream to cc-settings as a
src/scripts/script and propose aconfig/40-hooks.jsonentry — it then ships as "trusted" without needing per-user fingerprint refresh.
When the fingerprint warns, surgically remove the suspicious entries from
~/.claude/settings.json (Step 3 above) rather than flipping
disableAllHooks or allowManagedHooksOnly. Wholesale-disabling hooks
also disables features built on the hooks system — most visibly
/goal, which is a session-scoped
prompt-based Stop hook and reports itself unavailable if hooks are off
at any settings level. The verify-hooks fingerprint and the /goal
evaluator coexist cleanly: the fingerprint hashes only the persisted
hooks block in settings.json, not the in-memory session-scoped hook
that /goal installs for its lifetime.
- Auto-quarantine on suspicious match. The session-start hook only warns; it never disables, rewrites, or deletes hooks. Automated remediation that touches the user's settings.json is itself a high-trust operation and we'd rather the human read the diff.
- Block npm installs. Pre-install package scanning is a separate
problem with better-suited tools (
snyk,socket.dev,osv-scanner). Use one of those in CI; cc-settings catches what gets past it. - Cryptographic hook signing. Claude Code doesn't ship a signing primitive yet; signing would require upstream support. The fingerprint is the practical alternative.
- Sandbox hook execution. Hooks run with the user's full privileges by Claude Code's design. cc-settings doesn't subvert that.
Suspicious activity, false positives, or signature gaps — open an issue at darkroomengineering/cc-settings with the audit output.
For the underlying Claude Code product, report to Anthropic via the official channels documented at docs.claude.com/claude-code.
- Snyk: TanStack npm Packages Hit by Mini Shai-Hulud
- Socket: TanStack npm Packages Compromised in Ongoing Mini Shai-Hulud Supply Chain Attack
- StepSecurity: Mini Shai-Hulud Is Back: A Self-Spreading Supply Chain Attack
- Wiz: Mini Shai-Hulud Strikes Again
- The Hacker News: Mini Shai-Hulud Worm Compromises TanStack, Mistral AI, Guardrails AI
- Mend: 172 npm and PyPI Packages Compromised in Latest Wave