{"uuid": "8919c7a0-7388-4bc4-91a8-3ff60124d6d3", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://gist.github.com/prashanthnatraj/6405cf30127b74b185e049dd2fd746e6", "content": "#!/usr/bin/env bash\n# =============================================================================\n# shai-hulud-detector.sh \u2014 One-click Mini Shai-Hulud detection\n# Built by Lume AI (https://getlumeai.com) \u00b7 CVE-2026-45321 (CVSS 9.6)\n#\n# Why detection-before-rotation matters: the worm installs a persistence\n# daemon (gh-token-monitor) that runs `rm -rf ~` if it detects token\n# revocation. NEVER rotate credentials before confirming the daemon is gone.\n#\n# Usage:  curl -fsSL https://gist.githubusercontent.com/prashanthnatraj/6405cf30127b74b185e049dd2fd746e6/raw/shai-hulud-detector.sh | bash\n#         bash shai-hulud-detector.sh [--dir /path/to/project]\n#\n# Exit codes: 0 = CLEAN  |  1 = SUSPICIOUS  |  2 = INFECTED\n# =============================================================================\n\nset -euo pipefail\n\n# \u2500\u2500 Formatting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nRED='\\033[0;31m'\nYELLOW='\\033[1;33m'\nGREEN='\\033[0;32m'\nCYAN='\\033[0;36m'\nBOLD='\\033[1m'\nRESET='\\033[0m'\n\nbanner() { printf \"\\n${CYAN}${BOLD}%s${RESET}\\n\" \"$1\"; }\nok()     { printf \"  ${GREEN}[CLEAN]${RESET}      %s\\n\" \"$1\"; }\nwarn()   { printf \"  ${YELLOW}[SUSPICIOUS]${RESET} %s\\n\" \"$1\"; }\nhit()    { printf \"  ${RED}[INFECTED]${RESET}   %s\\n\" \"$1\"; }\ninfo()   { printf \"  ${CYAN}[INFO]${RESET}       %s\\n\" \"$1\"; }\nsep()    { printf '%s\\n' \"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\"; }\n\n# \u2500\u2500 State tracking \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nINFECTED=0\nSUSPICIOUS=0\nFINDINGS=()\n\nflag_infected() { INFECTED=1; FINDINGS+=(\"INFECTED: $1\"); }\nflag_suspicious() { SUSPICIOUS=1; FINDINGS+=(\"SUSPICIOUS: $1\"); }\n\n# \u2500\u2500 Argument parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPROJECT_DIR=\"${PWD}\"\nwhile [[ $# -gt 0 ]]; do\n  case \"$1\" in\n    --dir|-d)\n      PROJECT_DIR=\"${2:-}\"\n      shift 2\n      ;;\n    --help|-h)\n      grep '^#' \"$0\" | sed 's/^# \\{0,1\\}//'\n      exit 0\n      ;;\n    *)\n      shift\n      ;;\n  esac\ndone\n\nif [[ ! -d \"$PROJECT_DIR\" ]]; then\n  printf \"${RED}Error:${RESET} Directory not found: %s\\n\" \"$PROJECT_DIR\" &gt;&amp;2\n  exit 1\nfi\n\n# \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nprintf \"\\n\"\nsep\nprintf \"${BOLD}  Mini Shai-Hulud Detector${RESET}  |  CVE-2026-45321  |  Built by Lume AI\\n\"\nsep\nprintf \"  Scanning: %s\\n\" \"$PROJECT_DIR\"\nprintf \"  Date:     %s\\n\" \"$(date)\"\nsep\n\n# \u2500\u2500 Step 1: Lockfile presence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"1/7  Lockfile check\"\n\nLOCKFILE=\"\"\nfor f in \"$PROJECT_DIR/package-lock.json\" \"$PROJECT_DIR/pnpm-lock.yaml\" \"$PROJECT_DIR/yarn.lock\"; do\n  if [[ -f \"$f\" ]]; then\n    LOCKFILE=\"$f\"\n    info \"Found lockfile: $f\"\n    break\n  fi\ndone\n\nif [[ -z \"$LOCKFILE\" ]]; then\n  warn \"No lockfile found (package-lock.json / pnpm-lock.yaml / yarn.lock). Cannot run lockfile checks.\"\n  flag_suspicious \"no lockfile present \u2014 cannot verify dependency tree\"\nfi\n\nPKGJSON=\"$PROJECT_DIR/package.json\"\nif [[ ! -f \"$PKGJSON\" ]]; then\n  warn \"No package.json found at $PROJECT_DIR. Is this an npm project?\"\nfi\n\n# \u2500\u2500 Step 2: @tanstack/setup \u2014 single fastest tell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"2/7  @tanstack/setup (fastest tell)\"\n\nTANSTACK_SETUP_HIT=0\nfor f in \"$PKGJSON\" \"${LOCKFILE:-}\"; do\n  [[ -z \"$f\" || ! -f \"$f\" ]] &amp;&amp; continue\n  if grep -q \"@tanstack/setup\" \"$f\" 2&gt;/dev/null; then\n    hit \"@tanstack/setup found in $f \u2014 this package does NOT exist in the legitimate TanStack org\"\n    flag_infected \"@tanstack/setup present in $f\"\n    TANSTACK_SETUP_HIT=1\n  fi\ndone\n\nif [[ $TANSTACK_SETUP_HIT -eq 0 ]]; then\n  ok \"@tanstack/setup not found in project manifests\"\nfi\n\n# Also scan node_modules if present\nNM=\"$PROJECT_DIR/node_modules\"\nif [[ -d \"$NM/@tanstack/setup\" ]]; then\n  hit \"@tanstack/setup directory exists in node_modules \u2014 malicious package installed\"\n  flag_infected \"node_modules/@tanstack/setup directory present\"\nelif [[ -d \"$NM\" ]]; then\n  ok \"@tanstack/setup not installed in node_modules\"\nfi\n\n# \u2500\u2500 Step 3: Known malicious package namespaces in lockfile \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"3/7  Known malicious namespaces (CVE-2026-45321 IOC list)\"\n\nMALICIOUS_PATTERNS=(\n  \"@tanstack/setup\"\n  \"@uipath/setup\"\n  \"@mistralai/setup\"\n  \"@guardrails-ai/setup\"\n  \"@opensearch-project/setup\"\n  \"@cap-js/setup\"\n)\n\nif [[ -n \"$LOCKFILE\" &amp;&amp; -f \"$LOCKFILE\" ]]; then\n  for pattern in \"${MALICIOUS_PATTERNS[@]}\"; do\n    if grep -q \"$pattern\" \"$LOCKFILE\" 2&gt;/dev/null; then\n      hit \"Lockfile contains known IOC: $pattern\"\n      flag_infected \"lockfile contains IOC package: $pattern\"\n    else\n      ok \"$pattern \u2014 not in lockfile\"\n    fi\n  done\n\n  # Broader namespace sweep (legitimate packages in these namespaces are fine;\n  # this surfaces them for manual review if @tanstack/setup was found above)\n  SUSPICIOUS_NAMESPACES=(\"@uipath\" \"@mistralai\" \"@guardrails-ai\" \"@opensearch-\" \"@cap-js\")\n  if [[ $INFECTED -eq 1 ]]; then\n    banner \"   Extended namespace sweep (infection confirmed \u2014 showing all hits for manual review)\"\n    for ns in \"${SUSPICIOUS_NAMESPACES[@]}\"; do\n      hits=$(grep -c \"$ns\" \"$LOCKFILE\" 2&gt;/dev/null || true)\n      if [[ \"$hits\" -gt 0 ]]; then\n        warn \"$ns \u2014 $hits lockfile reference(s). Verify these are legitimate packages.\"\n      fi\n    done\n  fi\nelse\n  warn \"Skipping lockfile namespace sweep \u2014 no lockfile\"\nfi\n\n# \u2500\u2500 Step 4: Payload files on disk \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"4/7  Payload files (router_init.js / tanstack_runner.js)\"\n\nKNOWN_BAD_HASHES=(\n  \"ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c  router_init.js\"\n  \"2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96  tanstack_runner.js\"\n)\n\nPAYLOAD_FILES=$(find ~ -maxdepth 10 \\( -name \"router_init.js\" -o -name \"tanstack_runner.js\" \\) 2&gt;/dev/null || true)\n\nif [[ -z \"$PAYLOAD_FILES\" ]]; then\n  ok \"No payload files (router_init.js / tanstack_runner.js) found in home directory\"\nelse\n  while IFS= read -r fpath; do\n    [[ -z \"$fpath\" ]] &amp;&amp; continue\n    fname=$(basename \"$fpath\")\n    actual_hash=$(shasum -a 256 \"$fpath\" 2&gt;/dev/null | awk '{print $1}' || true)\n    for known in \"${KNOWN_BAD_HASHES[@]}\"; do\n      expected_hash=$(echo \"$known\" | awk '{print $1}')\n      expected_name=$(echo \"$known\" | awk '{print $2}')\n      if [[ \"$fname\" == \"$expected_name\" &amp;&amp; \"$actual_hash\" == \"$expected_hash\" ]]; then\n        hit \"Payload file with known malicious hash: $fpath\"\n        hit \"  SHA-256: $actual_hash\"\n        flag_infected \"malicious payload file found: $fpath\"\n      fi\n    done\n    # File exists but hash doesn't match known bad \u2014 still suspicious\n    if [[ $INFECTED -eq 0 ]]; then\n      warn \"Suspicious payload filename found (hash doesn't match known bad, but verify): $fpath\"\n      warn \"  SHA-256: $actual_hash\"\n      flag_suspicious \"unexpected payload-named file: $fpath\"\n    fi\n  done &lt;&lt;&lt; \"$PAYLOAD_FILES\"\nfi\n\n# \u2500\u2500 Step 5: gh-token-monitor daemon \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"5/7  Persistence daemon (gh-token-monitor)\"\n\nDAEMON_FOUND=0\n\n# macOS LaunchAgents\nif [[ -d ~/Library/LaunchAgents ]]; then\n  DAEMON_PLIST=$(ls ~/Library/LaunchAgents/ 2&gt;/dev/null | grep -i \"token\" || true)\n  if [[ -n \"$DAEMON_PLIST\" ]]; then\n    hit \"Suspicious LaunchAgent found: $DAEMON_PLIST\"\n    hit \"  Full path: ~/Library/LaunchAgents/$DAEMON_PLIST\"\n    flag_infected \"gh-token-monitor LaunchAgent present \u2014 DO NOT rotate tokens yet\"\n    DAEMON_FOUND=1\n  fi\n\n  if launchctl list 2&gt;/dev/null | grep -qi \"gh-token\"; then\n    hit \"gh-token-monitor service is RUNNING (launchctl)\"\n    flag_infected \"gh-token-monitor daemon is active\"\n    DAEMON_FOUND=1\n  fi\nfi\n\n# Linux systemd user units\nif command -v systemctl &amp;&gt;/dev/null; then\n  if systemctl --user list-units 2&gt;/dev/null | grep -qi \"gh-token\"; then\n    hit \"gh-token-monitor systemd user unit found\"\n    flag_infected \"gh-token-monitor daemon present (systemd)\"\n    DAEMON_FOUND=1\n  fi\nfi\n\n# Broader token-monitor process check\nif pgrep -f \"gh-token-monitor\" &amp;&gt;/dev/null; then\n  hit \"gh-token-monitor process is running right now\"\n  flag_infected \"gh-token-monitor process active\"\n  DAEMON_FOUND=1\nfi\n\nif [[ $DAEMON_FOUND -eq 0 ]]; then\n  ok \"No gh-token-monitor daemon detected (LaunchAgents, systemd, pgrep)\"\nfi\n\n# \u2500\u2500 Step 6: Claude Code hooks injection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"6/7  Claude Code hooks (~/.claude/settings.json)\"\n\nCLAUDE_SETTINGS=~/.claude/settings.json\nif [[ -f \"$CLAUDE_SETTINGS\" ]]; then\n  # Look for PreToolUse / PostToolUse hooks the user didn't add\n  if grep -qE \"(PreToolUse|PostToolUse)\" \"$CLAUDE_SETTINGS\" 2&gt;/dev/null; then\n    warn \"Claude Code settings.json contains hook entries \u2014 verify these are yours:\"\n    grep -E \"(PreToolUse|PostToolUse|hooks)\" \"$CLAUDE_SETTINGS\" 2&gt;/dev/null | head -20 | while IFS= read -r line; do\n      warn \"  $line\"\n    done\n    flag_suspicious \"unexpected hook entries in ~/.claude/settings.json \u2014 manual review required\"\n  else\n    ok \"No unexpected hooks in ~/.claude/settings.json\"\n  fi\nelse\n  info \"~/.claude/settings.json not found (Claude Code not installed, or no settings)\"\nfi\n\n# \u2500\u2500 Step 7: GitHub Actions / Dune-themed branches \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"7/7  GitHub Actions (local .github/workflows check)\"\n\nWORKFLOWS_DIR=\"$PROJECT_DIR/.github/workflows\"\nif [[ -d \"$WORKFLOWS_DIR\" ]]; then\n  # Check for pull_request_target triggers (the attack vector)\n  PRT_HIT=$(grep -rl \"pull_request_target\" \"$WORKFLOWS_DIR\" 2&gt;/dev/null || true)\n  if [[ -n \"$PRT_HIT\" ]]; then\n    warn \"Workflow(s) with pull_request_target trigger found \u2014 this was the CI attack vector:\"\n    while IFS= read -r wf; do\n      warn \"  $wf\"\n    done &lt;&lt;&lt; \"$PRT_HIT\"\n    flag_suspicious \"pull_request_target in workflows \u2014 verify intentional use\"\n  else\n    ok \"No pull_request_target triggers in .github/workflows\"\n  fi\n\n  # Check for unpinned uses: (tags instead of SHA)\n  UNPINNED=$(grep -rh \"uses:\" \"$WORKFLOWS_DIR\" 2&gt;/dev/null | grep -v \"@[0-9a-f]\\{40\\}\" | grep \"@\" || true)\n  if [[ -n \"$UNPINNED\" ]]; then\n    warn \"Workflow action(s) not pinned to full commit SHA (supply-chain risk):\"\n    echo \"$UNPINNED\" | head -10 | while IFS= read -r line; do\n      warn \"  $line\"\n    done\n    flag_suspicious \"unpinned workflow actions \u2014 pin to SHA not tag for defense-in-depth\"\n  else\n    ok \"All detected workflow actions appear pinned to SHA\"\n  fi\nelse\n  info \"No .github/workflows directory found \u2014 skipping Actions check\"\nfi\n\n# \u2500\u2500 Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nprintf \"\\n\"\nsep\nprintf \"${BOLD}  SCAN COMPLETE${RESET}\\n\"\nsep\n\nif [[ ${#FINDINGS[@]} -gt 0 ]]; then\n  printf \"\\n${BOLD}  Findings:${RESET}\\n\"\n  for f in \"${FINDINGS[@]}\"; do\n    printf \"    \u2022 %s\\n\" \"$f\"\n  done\nfi\n\nprintf \"\\n\"\n\nif [[ $INFECTED -eq 1 ]]; then\n  printf \"${RED}${BOLD}\"\n  sep\n  printf \"  VERDICT: INFECTED\\n\"\n  sep\n  printf \"${RESET}\"\n  printf \"\\n\"\n  printf \"${RED}${BOLD}  !! CRITICAL \u2014 READ BEFORE DOING ANYTHING !!${RESET}\\n\\n\"\n  printf \"  The worm's daemon (gh-token-monitor) runs 'rm -rf ~' if it detects\\n\"\n  printf \"  token revocation. Follow this order EXACTLY:\\n\\n\"\n  printf \"    1. DISCONNECT NETWORK NOW (Wi-Fi off, ethernet unplugged)\\n\"\n  printf \"    2. Do NOT run npm install / npm uninstall / git push\\n\"\n  printf \"    3. Remove the daemon FIRST:\\n\"\n  printf \"         macOS: launchctl unload ~/Library/LaunchAgents/.plist\\n\"\n  printf \"                rm ~/Library/LaunchAgents/.plist\\n\"\n  printf \"         Linux: systemctl --user stop gh-token-monitor.service\\n\"\n  printf \"                rm ~/.config/systemd/user/gh-token-monitor.service\\n\"\n  printf \"    4. Remove payload files:\\n\"\n  printf \"         find ~ \\\\( -name router_init.js -o -name tanstack_runner.js \\\\) -delete\\n\"\n  printf \"    5. ONLY THEN reconnect and rotate tokens (GitHub PAT first)\\n\"\n  printf \"    6. Reinstall dependencies: rm -rf node_modules &amp;&amp; npm install --ignore-scripts\\n\\n\"\n  printf \"  Full remediation runbook:\\n\"\n  printf \"  https://github.com/prashanthnatraj/shai-hulud-detector#remediation\\n\\n\"\n  exit 2\nelif [[ $SUSPICIOUS -eq 1 ]]; then\n  printf \"${YELLOW}${BOLD}\"\n  sep\n  printf \"  VERDICT: SUSPICIOUS \u2014 manual review required\\n\"\n  sep\n  printf \"${RESET}\"\n  printf \"\\n\"\n  printf \"  Some checks returned warnings. This does not confirm infection,\\n\"\n  printf \"  but you should investigate each finding above before proceeding.\\n\\n\"\n  printf \"  Community scanners for deeper analysis (run before rotating tokens):\\n\"\n  printf \"    \u2022 https://github.com/Cobenian/shai-hulud-detect\\n\"\n  printf \"    \u2022 https://github.com/omarpr/mini-shai-hulud-ioc-scanner\\n\"\n  printf \"    \u2022 StepSecurity scanner (most comprehensive)\\n\\n\"\n  printf \"  Full runbook: https://github.com/prashanthnatraj/shai-hulud-detector#remediation\\n\\n\"\n  exit 1\nelse\n  printf \"${GREEN}${BOLD}\"\n  sep\n  printf \"  VERDICT: CLEAN\\n\"\n  sep\n  printf \"${RESET}\"\n  printf \"\\n\"\n  printf \"  No indicators of Mini Shai-Hulud (CVE-2026-45321) found.\\n\\n\"\n  printf \"  Hardening recommendations (apply regardless of scan result):\\n\"\n  printf \"    \u2022 Add to .npmrc:  ignore-scripts=true\\n\"\n  printf \"    \u2022 Add to .npmrc:  minimum-release-age=4320   (3-day buffer)\\n\"\n  printf \"    \u2022 Pin GitHub Actions to full commit SHA, not version tags\\n\"\n  printf \"    \u2022 Enable npm 2FA: npm profile enable-2fa auth-and-writes\\n\"\n  printf \"    \u2022 Sign up for a supply-chain scanner: socket.dev, aikido.dev, or snyk.io\\n\\n\"\n  exit 0\nfi\n", "creation_timestamp": "2026-05-19T05:42:43.000000Z"}