Skip to content

feat(security): defend against mini-shai-hulud 2nd wave#48

Merged
susumutomita merged 2 commits into
mainfrom
feat/mini-shai-hulud-2nd-defense
May 13, 2026
Merged

feat(security): defend against mini-shai-hulud 2nd wave#48
susumutomita merged 2 commits into
mainfrom
feat/mini-shai-hulud-2nd-defense

Conversation

@susumutomita

@susumutomita susumutomita commented May 13, 2026

Copy link
Copy Markdown
Owner

Summary

Adds layered defenses against the mini-shai-hulud 2nd wave npm worm described in the Flatt Tech write-up (200+ packages compromised starting 2026-05-12 from TanStack Router). The worm injects itself via optionalDependencies pointing at attacker-controlled GitHub forks and executes a prepare-script runner; this PR closes both vectors and adds an IOC scanner that gates every commit and every CI run.

  • .npmrcignore-scripts=true, minimum-release-age=10080 (7 days), audit-level=high. Blocks lifecycle-script propagation and freshly hijacked releases for any npm-flavored tooling.
  • bunfig.tomlinstall.scripts.enabled = false (Bun refuses dependency lifecycle scripts) plus frozenLockfile = true. Neutralises the worm's primary injection step for Bun installs.
  • scripts/security/check-supply-chain.mjs — Audits for the four IOC categories from the write-up:
    • Dropper filenames: tanstack_runner.js, router_init.js
    • C2 / blackmail strings: git-tanstack.com, filev2.getsession.org, IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner, gh-token-monitor
    • Non-registry optionalDependencies (github:, git+, https?:, file:) — the actual injection vector
    • Unexpected lifecycle scripts in any workspace package.json (root prepare: husky is allowlisted)
    • Includes the IR warning to disable gh-token-monitor before rotating tokens — the worm rm -rf ~/'s on revoke.
  • Wiringmake security_audit target, runs first in make before-commit (gates the husky pre-commit hook); added pre-install Audit supply chain step to both ci.yml and pages.yml so a poisoned tree is rejected before any dependency code can execute.

Test plan

  • make security_audit passes clean on main (exit 0, "no indicators found")
  • make before-commit runs the full chain: audit → lint:text → biome → typecheck → 66 tests → vite build, all green
  • Planted IOC fixtures trigger findings across all four categories (dropper filename, C2 string, github-URL optionalDependency, unexpected prepare script)
  • CI green on this PR (audit step runs before install on both ci.yml and pages.yml)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Added automated supply‑chain audit steps to CI, site build workflow, and pre‑commit checks.
    • Introduced a local repository audit tool to scan for risky package lifecycle usage and indicators of compromise.
    • Hardened install behavior and build tooling to disable lifecycle scripts by default and require explicit re-bootstrap of project hooks.
    • Updated install targets and build config to enforce the new security policy.

Review Change Stack

The Flatt Tech write-up on mini-shai-hulud 2nd
(blog.flatt.tech/entry/mini_shai_hulud_2nd) describes an npm worm that
propagates via optionalDependencies pointing at attacker-controlled
forks and a `prepare` script runner. This adds layered defenses:

- .npmrc: ignore-scripts, minimum-release-age=7d, audit-level=high
- bunfig.toml: disable dependency lifecycle scripts, frozen lockfile
- scripts/security/check-supply-chain.mjs: audit for IOC filenames
  (tanstack_runner.js, router_init.js), C2 strings (git-tanstack.com,
  filev2.getsession.org, blackmail token description, gh-token-monitor),
  non-registry optionalDependencies, and unexpected lifecycle scripts
- Makefile + .github/workflows/{ci,pages}.yml: run audit before install
  so a poisoned tree is rejected before any dependency code executes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a Node audit script that scans manifests, tracked files, and a suspect workflow for IOC and dangerous lifecycle scripts; hardens install behavior via .npmrc and bunfig.toml; updates Makefile to call the audit and run installs with scripts disabled; and runs the audit in CI and Pages workflows.

Changes

Supply-chain Security Hardening

Layer / File(s) Summary
npm and Bun hardening configuration
.npmrc, bunfig.toml, Makefile
.npmrc disables lifecycle scripts, enforces a 7-day minimum release age, raises npm audit threshold to high, and disables funding prompts. bunfig.toml freezes the lockfile and disables install lifecycle scripts. Makefile targets use bun install --ignore-scripts, re-bootstrap Husky, add security_audit, and include it in before-commit.
Supply-chain audit script implementation
scripts/security/check-supply-chain.mjs
New executable script defines IOC indicators and skip rules, traverses the repo, audits package.json manifests for dangerous lifecycle scripts and non-registry optional deps, scans tracked files for IOC strings and known dropper filenames, checks a suspect CodeQL workflow, and exits 0/1/2 for OK/findings/error.
CI/CD and Pages workflow integration
.github/workflows/ci.yml, .github/workflows/pages.yml
Adds a pre-install "Audit supply chain (pre-install)" step that runs node scripts/security/check-supply-chain.mjs before dependency installation in CI and Pages build jobs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hop through lockfiles, sniffing every line,

Flags for hidden scripts and names that don't look fine.
Pre-install checks now guard each dependency,
Husky reboots, installs skip the mystery.
Quiet repo — audit done, the trail is clean.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main objective: adding security defenses against a specific supply-chain threat (mini-shai-hulud 2nd wave). It directly aligns with the changeset's primary focus on layered supply-chain hardening.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mini-shai-hulud-2nd-defense

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.npmrc:
- Line 15: Replace the invalid npm config key "minimum-release-age" with the
correct key "min-release-age" and set its value to the intended number of days
(e.g., "7" for a 7‑day gate) so the npm setting is recognized and enforced;
specifically update the line containing "minimum-release-age=10080" to
"min-release-age=7" (units are days).

In `@bunfig.toml`:
- Around line 18-19: The Toml key `[install.scripts] enabled = false` is invalid
and won't disable lifecycle scripts; update the bun configuration to use the
documented setting by removing the invalid `[install.scripts]` block and set
`ignoreScripts = true` under the existing `[install]` table (i.e., replace the
`[install.scripts] enabled = false` entry with `ignoreScripts = true` in the
`[install]` section) so Bun will actually ignore install scripts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 21ab4601-a20d-41ca-8dd2-4305f264e0be

📥 Commits

Reviewing files that changed from the base of the PR and between 4060339 and 15f915c.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • .github/workflows/pages.yml
  • .npmrc
  • Makefile
  • bunfig.toml
  • scripts/security/check-supply-chain.mjs

Comment thread .npmrc Outdated
Comment thread bunfig.toml Outdated
CodeRabbit caught two phantom config keys, and `make install` was not
actually disabling lifecycle scripts:

- .npmrc: `minimum-release-age=10080` was an invented key; the npm 11
  setting is `min-release-age` and the unit is days, not seconds.
  Switched to `min-release-age=7` so the 7-day quarantine applies.
- bunfig.toml: `[install.scripts] enabled = false` is not a valid Bun
  option (bun silently accepts but ignores it, so lifecycle scripts
  still ran). Replaced with `[install] ignoreScripts = true`.
- Makefile: bun does not honour `.npmrc`'s ignore-scripts or the
  `npm_config_ignore_scripts` env var, so `make install` was running
  every dependency's lifecycle scripts despite the config. Pass
  `--ignore-scripts` explicitly to both `bun install` invocations,
  and re-bootstrap husky after `make install` since the project's own
  `prepare` script is now skipped along with the rest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Makefile (1)

3-12: ⚡ Quick win

Factor shared install safety flags into one variable.

--ignore-scripts is repeated in both targets; centralizing it reduces drift when flags evolve.

♻️ Proposed refactor
+.PHONY: install
+BUN_INSTALL_SECURITY_FLAGS := --ignore-scripts
+
-.PHONY: install
 install:
@@
-	bun install --ignore-scripts
+	bun install $(BUN_INSTALL_SECURITY_FLAGS)
 	bun x husky
@@
 .PHONY: install_ci
 install_ci:
-	bun install --ignore-scripts --frozen-lockfile
+	bun install $(BUN_INSTALL_SECURITY_FLAGS) --frozen-lockfile
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 3 - 12, Introduce a shared Makefile variable (e.g.,
INSTALL_FLAGS) containing the common safety flags (currently --ignore-scripts)
and use that variable in both places where bun install is invoked: the top-level
bun install and the install_ci target's bun install --frozen-lockfile, so you
don't duplicate the flag; keep the subsequent bun x husky invocation as-is and
ensure install_ci still appends --frozen-lockfile in addition to the shared
INSTALL_FLAGS.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Makefile`:
- Around line 3-12: Introduce a shared Makefile variable (e.g., INSTALL_FLAGS)
containing the common safety flags (currently --ignore-scripts) and use that
variable in both places where bun install is invoked: the top-level bun install
and the install_ci target's bun install --frozen-lockfile, so you don't
duplicate the flag; keep the subsequent bun x husky invocation as-is and ensure
install_ci still appends --frozen-lockfile in addition to the shared
INSTALL_FLAGS.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e26f4fa8-32b0-4f19-a885-b14f59f52935

📥 Commits

Reviewing files that changed from the base of the PR and between 15f915c and dc2eb30.

📒 Files selected for processing (3)
  • .npmrc
  • Makefile
  • bunfig.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • bunfig.toml

@susumutomita susumutomita merged commit 4a91415 into main May 13, 2026
6 checks passed
@susumutomita susumutomita deleted the feat/mini-shai-hulud-2nd-defense branch May 13, 2026 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant