<?xml version='1.0' encoding='UTF-8'?>
<?xml-stylesheet href="/static/style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://vulnerability.circl.lu/sightings/feed</id>
  <title>Most recent sightings.</title>
  <updated>2026-05-18T21:15:38.633163+00:00</updated>
  <author>
    <name>Vulnerability-Lookup</name>
    <email>info@circl.lu</email>
  </author>
  <link href="https://vulnerability.circl.lu" rel="alternate"/>
  <generator uri="https://lkiesow.github.io/python-feedgen" version="1.0.0">python-feedgen</generator>
  <subtitle>Contains only the most 10 recent sightings.</subtitle>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/bc2d6d5a-ed81-4971-bb25-94f9dd2d374b/export</id>
    <title>bc2d6d5a-ed81-4971-bb25-94f9dd2d374b</title>
    <updated>2026-05-18T21:15:38.641302+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "bc2d6d5a-ed81-4971-bb25-94f9dd2d374b", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2020-17103", "type": "seen", "source": "https://bsky.app/profile/wdormann.infosec.exchange.ap.brid.gy/post/3mm5macicuql2", "content": "The Nightmare-Eclipse repo clearly credits James Forshaw with the CVE-2020-17103 vulnerability that MiniPlasma is based off of.\n\nDid Nightmare-Eclipse modify MiniPlasma to use a variant of CVE-2020-17103 that still works on modern Windows?\n\n**NO** [\u2026] \n\n[Original post on infosec.exchange]", "creation_timestamp": "2026-05-18T19:27:16.832154Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/bc2d6d5a-ed81-4971-bb25-94f9dd2d374b/export"/>
    <published>2026-05-18T19:27:16.832154+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/21314cce-3342-4641-a35a-1810e42c19ee/export</id>
    <title>21314cce-3342-4641-a35a-1810e42c19ee</title>
    <updated>2026-05-18T21:15:38.641238+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "21314cce-3342-4641-a35a-1810e42c19ee", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-13362", "type": "seen", "source": "https://bsky.app/profile/atomicedge.bsky.social/post/3mm5mkw4mab2s", "content": "CVE-2024-13362 auto-install-free-ssl (CVSS Score 6.1) #WordPress plugin #vulnerability #cybersecurity #wordpressfirewall #wordpresssecurity #hacking #wpsecurity #atomicedge", "creation_timestamp": "2026-05-18T19:33:07.398985Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/21314cce-3342-4641-a35a-1810e42c19ee/export"/>
    <published>2026-05-18T19:33:07.398985+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/f079bea4-04ea-4d2f-b13c-11a697102d38/export</id>
    <title>f079bea4-04ea-4d2f-b13c-11a697102d38</title>
    <updated>2026-05-18T21:15:38.641177+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "f079bea4-04ea-4d2f-b13c-11a697102d38", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-33519", "type": "seen", "source": "https://bsky.app/profile/cyberhub.blog/post/3mm5ms2tkxm2s", "content": "\ud83d\udccc CVE-2026-33519 - An incorrect authorization vulnerability exists in Esri Portal for ArcGIS 11.4, 11.5 and 12.0 on Windows, Linux and Kubernetes that did not correctly ... https://www.cyberhub.blog/cves/CVE-2026-33519", "creation_timestamp": "2026-05-18T19:37:07.340971Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/f079bea4-04ea-4d2f-b13c-11a697102d38/export"/>
    <published>2026-05-18T19:37:07.340971+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/f0f6303d-7d56-4a08-ad89-1f76166b2a29/export</id>
    <title>f0f6303d-7d56-4a08-ad89-1f76166b2a29</title>
    <updated>2026-05-18T21:15:38.641115+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "f0f6303d-7d56-4a08-ad89-1f76166b2a29", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-13362", "type": "seen", "source": "https://bsky.app/profile/potato.software/post/3mm5n4awnvd2w", "content": "CVE-2024-13362 auto-install-free-ssl (CVSS Score 6.1) #WordPress plugin #vulnerability #potatosecurity #wordpressfirewall #wordpresssecurity #mashing #wpsecurity #atomicedge", "creation_timestamp": "2026-05-18T19:42:49.064225Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/f0f6303d-7d56-4a08-ad89-1f76166b2a29/export"/>
    <published>2026-05-18T19:42:49.064225+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/ecb8ccf2-3c07-4fda-93ef-0b7155919dd3/export</id>
    <title>ecb8ccf2-3c07-4fda-93ef-0b7155919dd3</title>
    <updated>2026-05-18T21:15:38.641050+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "ecb8ccf2-3c07-4fda-93ef-0b7155919dd3", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-8836", "type": "seen", "source": "https://bsky.app/profile/thehackerwire.bsky.social/post/3mm5o3g42b62n", "content": "\ud83d\udd34 CVE-2026-8836 - Critical (9.8)\n\nA vulnerability was found in lwIP up to 2.2.1. Affected is the function snmp_parse_inbound_frame ...\n\nhttps://www.thehackerwire.com/vulnerability/CVE-2026-8836/\n\n#infosec #cybersecurity #CVE #vulnerability #security #patchstack", "creation_timestamp": "2026-05-18T20:00:15.266616Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/ecb8ccf2-3c07-4fda-93ef-0b7155919dd3/export"/>
    <published>2026-05-18T20:00:15.266616+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/654b48ae-ad1a-49af-a9bd-5e3e7580d108/export</id>
    <title>654b48ae-ad1a-49af-a9bd-5e3e7580d108</title>
    <updated>2026-05-18T21:15:38.640982+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "654b48ae-ad1a-49af-a9bd-5e3e7580d108", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-44551", "type": "seen", "source": "https://bsky.app/profile/cyberhub.blog/post/3mm5ohqepbw2p", "content": "\ud83d\udccc CVE-2026-44551 - Open WebUI is a self-hosted artificial intelligence platform designed to operate entirely offline. Prior to 0.9.0, the LDAP authentication endpoint do... https://www.cyberhub.blog/cves/CVE-2026-44551", "creation_timestamp": "2026-05-18T20:07:20.215796Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/654b48ae-ad1a-49af-a9bd-5e3e7580d108/export"/>
    <published>2026-05-18T20:07:20.215796+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/51b2fb5c-888a-48f2-bf09-25afae0cc165/export</id>
    <title>51b2fb5c-888a-48f2-bf09-25afae0cc165</title>
    <updated>2026-05-18T21:15:38.640903+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "51b2fb5c-888a-48f2-bf09-25afae0cc165", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-35433", "type": "seen", "source": "https://gist.github.com/alon710/dfd3811f977636ed8980add016a1bc17", "content": "# CVE-2026-35433: CVE-2026-35433: Heap-Based Buffer Overflow and Privilege Escalation in .NET Desktop Runtime\n\n&amp;gt; **CVSS Score:** 7.3\n&amp;gt; **Published:** 2026-05-18\n&amp;gt; **Full Report:** https://cvereports.com/reports/CVE-2026-35433\n\n## Summary\nCVE-2026-35433 is a high-severity Elevation of Privilege (EoP) vulnerability affecting the .NET Desktop Runtime. The flaw originates from a heap-based buffer overflow in the Windows Forms and WPF components due to improper input validation and integer overflow during binary data parsing. Successful exploitation allows a local attacker to execute arbitrary code with the privileges of the compromised application.\n\n## TL;DR\nA local attacker can trigger a heap buffer overflow in .NET Desktop Runtime (WinForms/WPF) by supplying malformed resource files or serialized payloads, potentially resulting in code execution and privilege escalation.\n\n## Technical Details\n\n- **Primary CWE**: CWE-122 (Heap-based Buffer Overflow)\n- **Attack Vector**: Local (User Interaction Required)\n- **CVSS v3.1 Score**: 7.3\n- **EPSS Score**: 0.00122 (30.67%)\n- **Impact**: Elevation of Privilege / Arbitrary Code Execution\n- **Exploit Status**: None (No public PoC)\n- **CISA KEV**: Not Listed\n\n## Affected Systems\n\n- Windows Desktop environments running .NET applications\n- Systems executing WinForms applications\n- Systems executing WPF applications\n- **.NET 10.0**: 10.0.0 &amp;lt;= version &amp;lt; 10.0.8 (Fixed in: `10.0.8`)\n- **.NET 9.0**: 9.0.0 &amp;lt;= version &amp;lt; 9.0.16 (Fixed in: `9.0.16`)\n- **.NET 8.0**: 8.0.0 &amp;lt;= version &amp;lt; 8.0.27 (Fixed in: `8.0.27`)\n- **.NET Framework**: 3.5, 4.7.2, 4.8, 4.8.1 (Fixed in: `4.8.9334.0`)\n\n## Mitigation\n\n- Apply vendor-provided patches updating the .NET runtime to secure versions.\n- Restrict the processing of untrusted .resx, .ico, and binary-serialized objects from external sources.\n- Implement strict input validation for any application handling external UI resources.\n\n**Remediation Steps:**\n1. Identify all systems running vulnerable versions of .NET 8.0, 9.0, 10.0, or .NET Framework 3.5 - 4.8.1.\n2. Deploy .NET 10.0.8, 9.0.16, or 8.0.27 to all endpoints and application servers as applicable.\n3. Deploy the May 2026 Cumulative Update for Windows environments running legacy .NET Framework versions.\n4. Restart affected applications and services to ensure the patched runtime libraries are loaded into memory.\n\n## References\n\n- [Microsoft Security Update Guide](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-35433)\n- [CVE.org Record](https://www.cve.org/CVERecord?id=CVE-2026-35433)\n- [WPF Dependency Update Commit](https://github.com/dotnet/wpf/commit/09e72ae8c9b1c5410ca8ad45636c52c45a2a7f29)\n\n\n---\n*Generated by [CVEReports](https://cvereports.com/reports/CVE-2026-35433) - Automated Vulnerability Intelligence*", "creation_timestamp": "2026-05-18T20:10:49.000000Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/51b2fb5c-888a-48f2-bf09-25afae0cc165/export"/>
    <published>2026-05-18T20:10:49+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/a5cb111e-2d5f-44c6-8a29-6ab730248cb4/export</id>
    <title>a5cb111e-2d5f-44c6-8a29-6ab730248cb4</title>
    <updated>2026-05-18T21:15:38.640711+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "a5cb111e-2d5f-44c6-8a29-6ab730248cb4", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "GHSA-g7cv-rxg3-hmpx", "type": "seen", "source": "https://gist.github.com/Fazzani/7a76d5ae430e05c763c6a86cbca9d42f", "content": "#!/usr/bin/env node\n'use strict';\n\n/**\n * mini-shai-hulud-audit.js\n * Workstation / runner scanner for the Mini Shai-Hulud campaign (CTI Advisory #002)\n * CVE-2026-45321 / GHSA-g7cv-rxg3-hmpx \u2014 CVSS 9.6 Critical\n * Threat actor: TeamPCP (aliases: DeadCatx3, PCPcat, ShellForce, CipherForce)\n *\n * \u26a0\ufe0f  CRITICAL SAFETY WARNING  \u26a0\ufe0f\n * If infection is suspected, DO NOT revoke any tokens before isolating the machine.\n * The malware watchdog detects revocation and triggers: rm -rf ~/\n * Incident response order: ISOLATE \u2192 IMAGE \u2192 KILL DAEMON \u2192 REVOKE \u2192 ROTATE\n *\n * Zero runtime dependencies \u2014 requires Node.js &amp;gt;= 14 only.\n * Usage: node mini-shai-hulud/mini-shai-hulud-audit.js [--root ] [--output ]\n */\n\nconst fs = require('node:fs');\nconst os = require('node:os');\nconst path = require('node:path');\nconst { execSync } = require('node:child_process');\n\nconst { MINI_SHAI_HULUD_PATTERNS } = require('./incident-patterns-mini-shai-hulud');\nconst { createLogger, dedupeByKey, makeProgress, readTextIfExists, scanTextForPatterns, walkFiles, writeReportFile } = require('../incident-utils');\n\nconst REPORT_FILE = 'mini-shai-hulud-audit-report.csv';\nconst ADVISORY_REF = 'CTI Advisory #002 \u2014 CVE-2026-45321 \u2014 TLP:AMBER';\n\nconst CSV_COLUMNS = [\n  'recordType',\n  'auditDate',\n  'host',\n  'platform',\n  'root',\n  'checkType',\n  'targetedFiles',\n  'scannedFiles',\n  'filesWithHits',\n  'totalHits',\n  'criticalHits',\n  'categories',\n  'path',\n  'size',\n  'mtime',\n  'id',\n  'label',\n  'category',\n  'severity',\n  'match',\n  'snippet',\n];\n\n// \u2500\u2500 CLI argument parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction parseArgs(argv) {\n  const args = argv.slice(2);\n  const opts = {\n    output: REPORT_FILE,\n    root: null,\n    help: false,\n    maxDepth: 4,\n    lockfiles: false, // opt-in: scan lockfiles for IOCs (slow on large repos)\n  };\n\n  for (let i = 0; i &amp;lt; args.length; i++) {\n    switch (args[i]) {\n      case '--output':\n      case '-o':\n        opts.output = args[++i];\n        break;\n      case '--root':\n      case '-r':\n        opts.root = args[++i];\n        break;\n      case '--max-depth':\n      case '--depth':\n        opts.maxDepth = Number(args[++i]);\n        break;\n      case '--lockfiles':\n        opts.lockfiles = true;\n        break;\n      case '--help':\n      case '-h':\n        opts.help = true;\n        break;\n    }\n  }\n\n  return opts;\n}\n\n// \u2500\u2500 Help \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction showHelp() {\n  const { C } = createLogger();\n  console.log(`\n${C.bold}${C.red}\u26a0\ufe0f  CRITICAL SAFETY WARNING${C.reset}\n  Do NOT revoke any tokens before isolating this machine.\n  The worm's watchdog triggers ${C.bold}rm -rf ~/${C.reset} on token revocation.\n\n${C.bold}${C.cyan}mini-shai-hulud-audit${C.reset}\nLocal evidence collector for the Mini Shai-Hulud campaign\n${C.dim}${ADVISORY_REF}${C.reset}\n\nUSAGE\n  node mini-shai-hulud/mini-shai-hulud-audit.js [options]\n\nOPTIONS\n  --root, -r      Extra folder to scan recursively for text evidence\n  --output, -o    Output file (default: ${REPORT_FILE})\n  --max-depth        Max recursion depth for --root (default: 4)\n  --lockfiles           Also scan lockfiles (package-lock.json, yarn.lock...)\n                        for IOC references \u2014 slow but thorough\n  --help, -h            Show this help\n\nDEFAULT SCAN TARGETS\n  Payload files   setup_bun.js, bun_environment.js, router_init.js,\n                  router_runtime.js, tanstack_runner.js (home + temp dirs)\n  Linux           /tmp/transformers.pyz (PyPI mistralai payload)\n  Persistence     ~/.claude/settings.json, .vscode/tasks.json, ~/.claude.json,\n                  */.claude/setup.mjs, */.vscode/setup.mjs\n  Daemons         ~/.config/**/gh-token-monitor*, ~/.local/bin/gh-token-monitor.sh\n  Shell history   bash_history, zsh_history, PSReadLine\n  npm logs        ~/.npm/_logs, AppData npm-cache logs\n\nINCIDENT RESPONSE ORDER (if infection confirmed)\n  1. ISOLATE  \u2014 disconnect machine from network\n  2. IMAGE    \u2014 forensic disk image before any cleanup\n  3. KILL     \u2014 disable gh-token-monitor daemon\n  4. REVOKE   \u2014 npm, GitHub PAT/OAuth, AWS, Azure, GCP, Kubernetes, SSH\n  5. ROTATE   \u2014 all credentials reachable from this host\n  6. AUDIT    \u2014 CloudTrail / Azure Activity / GCP Audit logs\n\nREFERENCES\n  CVE-2026-45321        https://tenable.com/cve/CVE-2026-45321\n  GHSA-g7cv-rxg3-hmpx   https://github.com/TanStack/router/security/advisories/GHSA-g7cv-rxg3-hmpx\n  TanStack postmortem   https://tanstack.com/blog/npm-supply-chain-compromise-postmortem\n`);\n}\n\n// \u2500\u2500 IOC-specific artefact paths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction joinIfPresent(...parts) {\n  if (parts.some((part) =&amp;gt; !part)) return null;\n  return path.join(...parts);\n}\n\nfunction getDefaultTargets() {\n  const home = os.homedir();\n  const tempDir = process.env.TEMP || process.env.TMP || os.tmpdir();\n  const appData = process.env.APPDATA;\n  const localAppData = process.env.LOCALAPPDATA;\n\n  // \u2500\u2500 Payload files to check for existence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  // These files should not exist on a clean machine \u2014 their presence is high-confidence.\n  const payloadFileNames = ['setup_bun.js', 'bun_environment.js', 'router_init.js', 'router_runtime.js', 'tanstack_runner.js'];\n\n  const payloadSearchRoots = [home, tempDir, os.tmpdir()].filter(Boolean);\n  const knownPayloadPaths = [];\n  for (const root of payloadSearchRoots) {\n    for (const name of payloadFileNames) {\n      knownPayloadPaths.push(path.join(root, name));\n    }\n  }\n\n  // Linux-specific PyPI payload\n  knownPayloadPaths.push('/tmp/transformers.pyz');\n\n  // \u2500\u2500 Persistence configs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const persistenceFiles = [\n    joinIfPresent(home, '.claude', 'settings.json'),\n    joinIfPresent(home, '.vscode', 'tasks.json'),\n    joinIfPresent(home, '.claude.json'),\n    joinIfPresent(home, '.claude', 'setup.mjs'),\n    joinIfPresent(home, '.vscode', 'setup.mjs'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 Daemon locations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const daemonFiles = [\n    joinIfPresent(home, '.local', 'bin', 'gh-token-monitor.sh'),\n    joinIfPresent(home, '.config', 'systemd', 'user', 'gh-token-monitor.service'),\n    joinIfPresent(home, 'Library', 'LaunchAgents', 'com.gh-token-monitor.plist'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 Shell history \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const historyFiles = [\n    joinIfPresent(home, '.bash_history'),\n    joinIfPresent(home, '.zsh_history'),\n    joinIfPresent(home, '.local', 'share', 'fish', 'fish_history'),\n    joinIfPresent(appData, 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt'),\n    joinIfPresent(appData, 'Microsoft', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt'),\n    joinIfPresent(home, 'AppData', 'Roaming', 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 npm debug logs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const logDirs = [\n    joinIfPresent(home, '.npm', '_logs'),\n    joinIfPresent(appData, 'npm-cache', '_logs'),\n    joinIfPresent(localAppData, 'npm-cache', '_logs'),\n    joinIfPresent(home, 'Library', 'Logs', 'npm'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 Daemon config dirs to scan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const daemonSearchDirs = [joinIfPresent(home, '.config'), joinIfPresent(home, '.local', 'bin'), joinIfPresent(home, 'Library', 'LaunchAgents')].filter(Boolean);\n\n  return {\n    files: [...knownPayloadPaths, ...persistenceFiles, ...daemonFiles, ...historyFiles],\n    dirs: [...logDirs, ...daemonSearchDirs],\n  };\n}\n\n// \u2500\u2500 File filter for directory walks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction isTextLikeEvidence(filePath, opts) {\n  const name = path.basename(filePath).toLowerCase();\n\n  if (!opts.lockfiles) {\n    // Lock files have high noise for most patterns \u2014 skip unless opted in.\n    // Exception: we DO want to check package-lock.json for the poisoned commit ref\n    // and optionalDependencies IOC; caller handles that via specific file checks.\n    if (name === 'yarn.lock' || name === 'pnpm-lock.yaml' || name === 'pnpm-lock.yml' || name === 'composer.lock' || name === 'gemfile.lock' || name === 'poetry.lock' || name === 'cargo.lock')\n      return false;\n  }\n\n  return (\n    name.endsWith('.log') ||\n    name.endsWith('.txt') ||\n    name.endsWith('.json') ||\n    name.endsWith('.yaml') ||\n    name.endsWith('.yml') ||\n    name.endsWith('.mjs') ||\n    name.endsWith('.js') ||\n    name.endsWith('.history') ||\n    name.endsWith('.ps1') ||\n    name.endsWith('.sh') ||\n    name.endsWith('.zsh') ||\n    name.endsWith('.bash') ||\n    name.endsWith('.py') ||\n    name.endsWith('.pyz') ||\n    name.endsWith('.plist') ||\n    name.endsWith('.service') ||\n    name === 'fish_history' ||\n    name === 'consolehost_history.txt' ||\n    name.startsWith('npm-debug') ||\n    name.startsWith('pnpm-debug') ||\n    name.startsWith('yarn-error') ||\n    name === 'gh-token-monitor'\n  );\n}\n\n// \u2500\u2500 File scanner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction scanFile(filePath) {\n  const file = readTextIfExists(filePath);\n  if (!file) return [];\n\n  const hits = scanTextForPatterns(file.body, MINI_SHAI_HULUD_PATTERNS);\n  return hits.map((hit) =&amp;gt; ({\n    path: file.path,\n    size: file.size,\n    mtime: file.mtime.toISOString(),\n    ...hit,\n  }));\n}\n\nfunction groupByFile(hits) {\n  const byFile = new Map();\n  for (const hit of hits) {\n    const bucket = byFile.get(hit.path) ?? [];\n    bucket.push(hit);\n    byFile.set(hit.path, bucket);\n  }\n  return [...byFile.entries()].map(([filePath, fileHits]) =&amp;gt; ({\n    path: filePath,\n    hits: fileHits,\n  }));\n}\n\n// \u2500\u2500 Payload file existence check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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// These files should not normally exist \u2014 their presence alone is a critical IOC.\n\nfunction checkPayloadFileExists(filePath) {\n  if (!fs.existsSync(filePath)) return null;\n  try {\n    const stats = fs.statSync(filePath);\n    return {\n      path: filePath,\n      size: stats.size,\n      mtime: stats.mtime.toISOString(),\n      id: 'file-exists-' + path.basename(filePath).replace(/\\W+/g, '-'),\n      label: `[FILE EXISTS] ${path.basename(filePath)} \u2014 worm payload detected`,\n      category: 'filesystem',\n      severity: 'critical',\n      match: path.basename(filePath),\n      snippet: `File exists: ${filePath} (${stats.size} bytes)`,\n    };\n  } catch {\n    return null;\n  }\n}\n\n// \u2500\u2500 Process list check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction checkSuspiciousProcesses(C) {\n  const processHits = [];\n  const suspicious = ['tanstack_runner', 'router_runtime', 'gh-token-monitor'];\n\n  try {\n    const cmd = process.platform === 'win32' ? 'tasklist /FO CSV /NH' : 'ps aux';\n    const output = execSync(cmd, { timeout: 5000, encoding: 'utf8' }).toLowerCase();\n\n    for (const proc of suspicious) {\n      if (output.includes(proc.toLowerCase())) {\n        processHits.push({\n          path: '[process list]',\n          size: 0,\n          mtime: new Date().toISOString(),\n          id: 'process-' + proc.replace(/\\W+/g, '-'),\n          label: `[PROCESS RUNNING] ${proc} \u2014 worm/daemon active`,\n          category: 'persistence',\n          severity: 'critical',\n          match: proc,\n          snippet: `Process \"${proc}\" found in running process list`,\n        });\n      }\n    }\n\n    // Unexpected bun execution (not in standard package manager positions)\n    if (/\\bbun\\b/.test(output) &amp;amp;&amp;amp; !output.includes('bun-as-installer')) {\n      processHits.push({\n        path: '[process list]',\n        size: 0,\n        mtime: new Date().toISOString(),\n        id: 'process-bun-unexpected',\n        label: '[PROCESS RUNNING] bun \u2014 unexpected Bun runtime (evasion technique)',\n        category: 'execution',\n        severity: 'high',\n        match: 'bun',\n        snippet: 'Bun runtime process found running \u2014 may indicate worm preinstall hook activity',\n      });\n    }\n  } catch {\n    // Process list not available \u2014 not a fatal error\n  }\n\n  return processHits;\n}\n\n// \u2500\u2500 Core scan orchestration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction buildScanTargets(opts) {\n  const defaults = getDefaultTargets();\n  const fileTargets = [...defaults.files];\n  const dirTargets = [...defaults.dirs];\n\n  if (opts.root) {\n    const resolvedRoot = path.resolve(opts.root);\n    if (fs.existsSync(resolvedRoot)) {\n      const stats = fs.statSync(resolvedRoot);\n      if (stats.isDirectory()) {\n        dirTargets.push(resolvedRoot);\n      } else if (stats.isFile()) {\n        fileTargets.push(resolvedRoot);\n      }\n    }\n  }\n\n  return { files: fileTargets, dirs: dirTargets };\n}\n\nfunction buildAudit(opts) {\n  const { C, log } = createLogger();\n\n  log.title('MINI SHAI-HULUD AUDIT \u2014 CVE-2026-45321');\n  console.log(`  ${C.red}${C.bold}\u26a0  DO NOT revoke tokens before isolating the machine  \u26a0${C.reset}`);\n  console.log(`  ${C.dim}${ADVISORY_REF}${C.reset}\\n`);\n\n  const targets = buildScanTargets(opts);\n\n  // \u2500\u2500 Step 1: Payload file existence (presence alone = critical) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  log.step('Checking for payload files (existence check)...');\n  const payloadNames = ['setup_bun.js', 'bun_environment.js', 'router_init.js', 'router_runtime.js', 'tanstack_runner.js', 'transformers.pyz'];\n  const payloadExistenceHits = targets.files\n    .filter((f) =&amp;gt; payloadNames.includes(path.basename(f)))\n    .map(checkPayloadFileExists)\n    .filter(Boolean);\n\n  // \u2500\u2500 Step 2: Process list check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  log.step('Checking running processes...');\n  const processHits = checkSuspiciousProcesses(C);\n\n  // \u2500\u2500 Step 3: Scan known files for IOC patterns \u2500\u2500\u2500\u2500\u2500\u2500\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  log.step('Scanning targeted files for IOC patterns...');\n  const knownFileHits = [];\n  const progress = makeProgress(targets.files.length, C);\n  for (const filePath of targets.files) {\n    progress.tick(path.basename(filePath));\n    knownFileHits.push(...scanFile(filePath));\n  }\n\n  // \u2500\u2500 Step 4: Walk evidence directories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  log.step('Walking evidence directories...');\n  const walkedFiles = walkFiles(targets.dirs, {\n    maxDepth: Number.isFinite(opts.maxDepth) ? opts.maxDepth : 4,\n    filter: (f) =&amp;gt; isTextLikeEvidence(f, opts),\n  });\n  const walkedHits = [];\n  const walkProgress = makeProgress(walkedFiles.length, C);\n  for (const filePath of walkedFiles) {\n    walkProgress.tick(path.basename(filePath));\n    walkedHits.push(...scanFile(filePath));\n  }\n\n  // \u2500\u2500 Aggregate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const allHits = dedupeByKey([...payloadExistenceHits, ...processHits, ...knownFileHits, ...walkedHits], (hit) =&amp;gt; `${hit.path}|${hit.id}|${hit.match}|${hit.snippet}`);\n  const filesWithHits = groupByFile(allHits);\n  const criticalHits = allHits.filter((hit) =&amp;gt; hit.severity === 'critical');\n  const scannedFiles = dedupeByKey([...targets.files, ...walkedFiles], (f) =&amp;gt; f);\n\n  const summary = {\n    targetedFiles: targets.files.length,\n    scannedFiles: scannedFiles.length,\n    filesWithHits: filesWithHits.length,\n    totalHits: allHits.length,\n    criticalHits: criticalHits.length,\n    categories: allHits.reduce((acc, hit) =&amp;gt; {\n      acc[hit.category] = (acc[hit.category] ?? 0) + 1;\n      return acc;\n    }, {}),\n  };\n\n  return {\n    C,\n    log,\n    summary,\n    filesWithHits,\n    criticalHits,\n    report: {\n      auditDate: new Date().toISOString(),\n      advisory: ADVISORY_REF,\n      scope: {\n        root: opts.root ? path.resolve(opts.root) : null,\n        host: os.hostname(),\n        platform: process.platform,\n      },\n      summary,\n      findings: allHits,\n      filesWithHits,\n    },\n  };\n}\n\n// \u2500\u2500 CSV serialisation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction buildCsvRows(result) {\n  const { summary, report, filesWithHits } = result;\n  const rows = [\n    {\n      recordType: 'summary',\n      auditDate: report.auditDate,\n      host: report.scope.host,\n      platform: report.scope.platform,\n      root: report.scope.root,\n      checkType: ADVISORY_REF,\n      targetedFiles: summary.targetedFiles,\n      scannedFiles: summary.scannedFiles,\n      filesWithHits: summary.filesWithHits,\n      totalHits: summary.totalHits,\n      criticalHits: summary.criticalHits,\n      categories: JSON.stringify(summary.categories),\n    },\n  ];\n\n  for (const file of filesWithHits) {\n    for (const hit of file.hits) {\n      rows.push({\n        recordType: 'finding',\n        auditDate: report.auditDate,\n        host: report.scope.host,\n        platform: report.scope.platform,\n        root: report.scope.root,\n        checkType: ADVISORY_REF,\n        path: file.path,\n        size: hit.size,\n        mtime: hit.mtime,\n        id: hit.id,\n        label: hit.label,\n        category: hit.category,\n        severity: hit.severity,\n        match: hit.match,\n        snippet: hit.snippet,\n      });\n    }\n  }\n\n  return rows;\n}\n\n// \u2500\u2500 Terminal report \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction printReport(result) {\n  const { C, log, summary, filesWithHits } = result;\n\n  log.step('Results:');\n\n  if (summary.criticalHits === 0) {\n    log.ok(\n      'CLEAR \u2014 no critical Mini Shai-Hulud IOCs detected.\\n' + '  Checked: payload files, persistence artefacts, C2 domains, package versions,\\n' + '           distinctive strings, daemon processes.',\n    );\n    console.log(`\\n  \ud83d\udcc4 ${result.reportPath}  (${summary.scannedFiles} files scanned)\\n`);\n    return;\n  }\n\n  // \u2500\u2500 Critical IOCs found \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const criticalFiles = filesWithHits.filter((f) =&amp;gt; f.hits.some((h) =&amp;gt; h.severity === 'critical'));\n\n  console.log(`\\n${C.red}${C.bold}\u2501\u2501\u2501 CRITICAL IOCs DETECTED \u2014 Mini Shai-Hulud \u2501\u2501\u2501${C.reset}`);\n  console.log(`${C.red}${C.bold}  CVE-2026-45321 / GHSA-g7cv-rxg3-hmpx \u2014 CVSS 9.6${C.reset}\\n`);\n\n  for (const file of criticalFiles) {\n    console.log(`  ${C.bold}${file.path}${C.reset}`);\n    for (const hit of file.hits.filter((h) =&amp;gt; h.severity === 'critical')) {\n      console.log(`    ${C.red}${hit.label}${C.reset}  ${C.dim}(${hit.category})${C.reset}`);\n      if (hit.snippet) {\n        console.log(`    ${C.dim}${hit.snippet}${C.reset}`);\n      }\n    }\n    console.log('');\n  }\n\n  console.log(`${C.red}${C.bold}\u2501\u2501\u2501 INCIDENT RESPONSE \u2014 MANDATORY ORDER \u2501\u2501\u2501${C.reset}`);\n  console.log(`\\n  ${C.red}${C.bold}\u26a0  DO NOT revoke tokens before step 3  \u26a0${C.reset}`);\n  console.log(`  ${C.dim}The worm watchdog triggers rm -rf ~/ on token revocation (HTTP 40X).${C.reset}\\n`);\n  console.log(`  ${C.red}1.${C.reset} ISOLATE  \u2014 disconnect machine from network immediately`);\n  console.log(`  ${C.red}2.${C.reset} IMAGE    \u2014 forensic disk image before any cleanup`);\n  console.log(`  ${C.red}3.${C.reset} KILL     \u2014 disable gh-token-monitor daemon and worm processes`);\n  console.log(`  ${C.red}4.${C.reset} REVOKE   \u2014 npm, GitHub PAT/OAuth, AWS, Azure, GCP, Kubernetes, SSH`);\n  console.log(`  ${C.red}5.${C.reset} ROTATE   \u2014 all credentials reachable from this host`);\n  console.log(`  ${C.red}6.${C.reset} AUDIT    \u2014 AWS CloudTrail, Azure Activity Logs, GCP Audit Logs`);\n  console.log(`  ${C.red}7.${C.reset} VERIFY   \u2014 GitHub account: recent Dune-themed public repos, new PATs\\n`);\n  console.log(`  \ud83d\udcc4 ${result.reportPath}  (full execution trace inside)\\n`);\n}\n\n// \u2500\u2500 Entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nasync function main() {\n  const opts = parseArgs(process.argv);\n\n  if (opts.help) {\n    showHelp();\n    process.exit(0);\n  }\n\n  const result = buildAudit(opts);\n  result.reportPath = opts.output;\n  writeReportFile(opts.output, result.report, buildCsvRows(result), CSV_COLUMNS);\n  printReport(result);\n}\n\nmain().catch((err) =&amp;gt; {\n  const { log } = createLogger();\n  log.alert(`Fatal: ${err.message}`);\n  process.exit(1);\n});\n", "creation_timestamp": "2026-05-18T20:22:18.000000Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/a5cb111e-2d5f-44c6-8a29-6ab730248cb4/export"/>
    <published>2026-05-18T20:22:18+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/6397bdc2-5791-4276-8c4f-1b9d52b87c4c/export</id>
    <title>6397bdc2-5791-4276-8c4f-1b9d52b87c4c</title>
    <updated>2026-05-18T21:15:38.640480+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "6397bdc2-5791-4276-8c4f-1b9d52b87c4c", "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/Fazzani/7a76d5ae430e05c763c6a86cbca9d42f", "content": "#!/usr/bin/env node\n'use strict';\n\n/**\n * mini-shai-hulud-audit.js\n * Workstation / runner scanner for the Mini Shai-Hulud campaign (CTI Advisory #002)\n * CVE-2026-45321 / GHSA-g7cv-rxg3-hmpx \u2014 CVSS 9.6 Critical\n * Threat actor: TeamPCP (aliases: DeadCatx3, PCPcat, ShellForce, CipherForce)\n *\n * \u26a0\ufe0f  CRITICAL SAFETY WARNING  \u26a0\ufe0f\n * If infection is suspected, DO NOT revoke any tokens before isolating the machine.\n * The malware watchdog detects revocation and triggers: rm -rf ~/\n * Incident response order: ISOLATE \u2192 IMAGE \u2192 KILL DAEMON \u2192 REVOKE \u2192 ROTATE\n *\n * Zero runtime dependencies \u2014 requires Node.js &amp;gt;= 14 only.\n * Usage: node mini-shai-hulud/mini-shai-hulud-audit.js [--root ] [--output ]\n */\n\nconst fs = require('node:fs');\nconst os = require('node:os');\nconst path = require('node:path');\nconst { execSync } = require('node:child_process');\n\nconst { MINI_SHAI_HULUD_PATTERNS } = require('./incident-patterns-mini-shai-hulud');\nconst { createLogger, dedupeByKey, makeProgress, readTextIfExists, scanTextForPatterns, walkFiles, writeReportFile } = require('../incident-utils');\n\nconst REPORT_FILE = 'mini-shai-hulud-audit-report.csv';\nconst ADVISORY_REF = 'CTI Advisory #002 \u2014 CVE-2026-45321 \u2014 TLP:AMBER';\n\nconst CSV_COLUMNS = [\n  'recordType',\n  'auditDate',\n  'host',\n  'platform',\n  'root',\n  'checkType',\n  'targetedFiles',\n  'scannedFiles',\n  'filesWithHits',\n  'totalHits',\n  'criticalHits',\n  'categories',\n  'path',\n  'size',\n  'mtime',\n  'id',\n  'label',\n  'category',\n  'severity',\n  'match',\n  'snippet',\n];\n\n// \u2500\u2500 CLI argument parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction parseArgs(argv) {\n  const args = argv.slice(2);\n  const opts = {\n    output: REPORT_FILE,\n    root: null,\n    help: false,\n    maxDepth: 4,\n    lockfiles: false, // opt-in: scan lockfiles for IOCs (slow on large repos)\n  };\n\n  for (let i = 0; i &amp;lt; args.length; i++) {\n    switch (args[i]) {\n      case '--output':\n      case '-o':\n        opts.output = args[++i];\n        break;\n      case '--root':\n      case '-r':\n        opts.root = args[++i];\n        break;\n      case '--max-depth':\n      case '--depth':\n        opts.maxDepth = Number(args[++i]);\n        break;\n      case '--lockfiles':\n        opts.lockfiles = true;\n        break;\n      case '--help':\n      case '-h':\n        opts.help = true;\n        break;\n    }\n  }\n\n  return opts;\n}\n\n// \u2500\u2500 Help \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction showHelp() {\n  const { C } = createLogger();\n  console.log(`\n${C.bold}${C.red}\u26a0\ufe0f  CRITICAL SAFETY WARNING${C.reset}\n  Do NOT revoke any tokens before isolating this machine.\n  The worm's watchdog triggers ${C.bold}rm -rf ~/${C.reset} on token revocation.\n\n${C.bold}${C.cyan}mini-shai-hulud-audit${C.reset}\nLocal evidence collector for the Mini Shai-Hulud campaign\n${C.dim}${ADVISORY_REF}${C.reset}\n\nUSAGE\n  node mini-shai-hulud/mini-shai-hulud-audit.js [options]\n\nOPTIONS\n  --root, -r      Extra folder to scan recursively for text evidence\n  --output, -o    Output file (default: ${REPORT_FILE})\n  --max-depth        Max recursion depth for --root (default: 4)\n  --lockfiles           Also scan lockfiles (package-lock.json, yarn.lock...)\n                        for IOC references \u2014 slow but thorough\n  --help, -h            Show this help\n\nDEFAULT SCAN TARGETS\n  Payload files   setup_bun.js, bun_environment.js, router_init.js,\n                  router_runtime.js, tanstack_runner.js (home + temp dirs)\n  Linux           /tmp/transformers.pyz (PyPI mistralai payload)\n  Persistence     ~/.claude/settings.json, .vscode/tasks.json, ~/.claude.json,\n                  */.claude/setup.mjs, */.vscode/setup.mjs\n  Daemons         ~/.config/**/gh-token-monitor*, ~/.local/bin/gh-token-monitor.sh\n  Shell history   bash_history, zsh_history, PSReadLine\n  npm logs        ~/.npm/_logs, AppData npm-cache logs\n\nINCIDENT RESPONSE ORDER (if infection confirmed)\n  1. ISOLATE  \u2014 disconnect machine from network\n  2. IMAGE    \u2014 forensic disk image before any cleanup\n  3. KILL     \u2014 disable gh-token-monitor daemon\n  4. REVOKE   \u2014 npm, GitHub PAT/OAuth, AWS, Azure, GCP, Kubernetes, SSH\n  5. ROTATE   \u2014 all credentials reachable from this host\n  6. AUDIT    \u2014 CloudTrail / Azure Activity / GCP Audit logs\n\nREFERENCES\n  CVE-2026-45321        https://tenable.com/cve/CVE-2026-45321\n  GHSA-g7cv-rxg3-hmpx   https://github.com/TanStack/router/security/advisories/GHSA-g7cv-rxg3-hmpx\n  TanStack postmortem   https://tanstack.com/blog/npm-supply-chain-compromise-postmortem\n`);\n}\n\n// \u2500\u2500 IOC-specific artefact paths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction joinIfPresent(...parts) {\n  if (parts.some((part) =&amp;gt; !part)) return null;\n  return path.join(...parts);\n}\n\nfunction getDefaultTargets() {\n  const home = os.homedir();\n  const tempDir = process.env.TEMP || process.env.TMP || os.tmpdir();\n  const appData = process.env.APPDATA;\n  const localAppData = process.env.LOCALAPPDATA;\n\n  // \u2500\u2500 Payload files to check for existence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  // These files should not exist on a clean machine \u2014 their presence is high-confidence.\n  const payloadFileNames = ['setup_bun.js', 'bun_environment.js', 'router_init.js', 'router_runtime.js', 'tanstack_runner.js'];\n\n  const payloadSearchRoots = [home, tempDir, os.tmpdir()].filter(Boolean);\n  const knownPayloadPaths = [];\n  for (const root of payloadSearchRoots) {\n    for (const name of payloadFileNames) {\n      knownPayloadPaths.push(path.join(root, name));\n    }\n  }\n\n  // Linux-specific PyPI payload\n  knownPayloadPaths.push('/tmp/transformers.pyz');\n\n  // \u2500\u2500 Persistence configs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const persistenceFiles = [\n    joinIfPresent(home, '.claude', 'settings.json'),\n    joinIfPresent(home, '.vscode', 'tasks.json'),\n    joinIfPresent(home, '.claude.json'),\n    joinIfPresent(home, '.claude', 'setup.mjs'),\n    joinIfPresent(home, '.vscode', 'setup.mjs'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 Daemon locations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const daemonFiles = [\n    joinIfPresent(home, '.local', 'bin', 'gh-token-monitor.sh'),\n    joinIfPresent(home, '.config', 'systemd', 'user', 'gh-token-monitor.service'),\n    joinIfPresent(home, 'Library', 'LaunchAgents', 'com.gh-token-monitor.plist'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 Shell history \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const historyFiles = [\n    joinIfPresent(home, '.bash_history'),\n    joinIfPresent(home, '.zsh_history'),\n    joinIfPresent(home, '.local', 'share', 'fish', 'fish_history'),\n    joinIfPresent(appData, 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt'),\n    joinIfPresent(appData, 'Microsoft', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt'),\n    joinIfPresent(home, 'AppData', 'Roaming', 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 npm debug logs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const logDirs = [\n    joinIfPresent(home, '.npm', '_logs'),\n    joinIfPresent(appData, 'npm-cache', '_logs'),\n    joinIfPresent(localAppData, 'npm-cache', '_logs'),\n    joinIfPresent(home, 'Library', 'Logs', 'npm'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 Daemon config dirs to scan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const daemonSearchDirs = [joinIfPresent(home, '.config'), joinIfPresent(home, '.local', 'bin'), joinIfPresent(home, 'Library', 'LaunchAgents')].filter(Boolean);\n\n  return {\n    files: [...knownPayloadPaths, ...persistenceFiles, ...daemonFiles, ...historyFiles],\n    dirs: [...logDirs, ...daemonSearchDirs],\n  };\n}\n\n// \u2500\u2500 File filter for directory walks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction isTextLikeEvidence(filePath, opts) {\n  const name = path.basename(filePath).toLowerCase();\n\n  if (!opts.lockfiles) {\n    // Lock files have high noise for most patterns \u2014 skip unless opted in.\n    // Exception: we DO want to check package-lock.json for the poisoned commit ref\n    // and optionalDependencies IOC; caller handles that via specific file checks.\n    if (name === 'yarn.lock' || name === 'pnpm-lock.yaml' || name === 'pnpm-lock.yml' || name === 'composer.lock' || name === 'gemfile.lock' || name === 'poetry.lock' || name === 'cargo.lock')\n      return false;\n  }\n\n  return (\n    name.endsWith('.log') ||\n    name.endsWith('.txt') ||\n    name.endsWith('.json') ||\n    name.endsWith('.yaml') ||\n    name.endsWith('.yml') ||\n    name.endsWith('.mjs') ||\n    name.endsWith('.js') ||\n    name.endsWith('.history') ||\n    name.endsWith('.ps1') ||\n    name.endsWith('.sh') ||\n    name.endsWith('.zsh') ||\n    name.endsWith('.bash') ||\n    name.endsWith('.py') ||\n    name.endsWith('.pyz') ||\n    name.endsWith('.plist') ||\n    name.endsWith('.service') ||\n    name === 'fish_history' ||\n    name === 'consolehost_history.txt' ||\n    name.startsWith('npm-debug') ||\n    name.startsWith('pnpm-debug') ||\n    name.startsWith('yarn-error') ||\n    name === 'gh-token-monitor'\n  );\n}\n\n// \u2500\u2500 File scanner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction scanFile(filePath) {\n  const file = readTextIfExists(filePath);\n  if (!file) return [];\n\n  const hits = scanTextForPatterns(file.body, MINI_SHAI_HULUD_PATTERNS);\n  return hits.map((hit) =&amp;gt; ({\n    path: file.path,\n    size: file.size,\n    mtime: file.mtime.toISOString(),\n    ...hit,\n  }));\n}\n\nfunction groupByFile(hits) {\n  const byFile = new Map();\n  for (const hit of hits) {\n    const bucket = byFile.get(hit.path) ?? [];\n    bucket.push(hit);\n    byFile.set(hit.path, bucket);\n  }\n  return [...byFile.entries()].map(([filePath, fileHits]) =&amp;gt; ({\n    path: filePath,\n    hits: fileHits,\n  }));\n}\n\n// \u2500\u2500 Payload file existence check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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// These files should not normally exist \u2014 their presence alone is a critical IOC.\n\nfunction checkPayloadFileExists(filePath) {\n  if (!fs.existsSync(filePath)) return null;\n  try {\n    const stats = fs.statSync(filePath);\n    return {\n      path: filePath,\n      size: stats.size,\n      mtime: stats.mtime.toISOString(),\n      id: 'file-exists-' + path.basename(filePath).replace(/\\W+/g, '-'),\n      label: `[FILE EXISTS] ${path.basename(filePath)} \u2014 worm payload detected`,\n      category: 'filesystem',\n      severity: 'critical',\n      match: path.basename(filePath),\n      snippet: `File exists: ${filePath} (${stats.size} bytes)`,\n    };\n  } catch {\n    return null;\n  }\n}\n\n// \u2500\u2500 Process list check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction checkSuspiciousProcesses(C) {\n  const processHits = [];\n  const suspicious = ['tanstack_runner', 'router_runtime', 'gh-token-monitor'];\n\n  try {\n    const cmd = process.platform === 'win32' ? 'tasklist /FO CSV /NH' : 'ps aux';\n    const output = execSync(cmd, { timeout: 5000, encoding: 'utf8' }).toLowerCase();\n\n    for (const proc of suspicious) {\n      if (output.includes(proc.toLowerCase())) {\n        processHits.push({\n          path: '[process list]',\n          size: 0,\n          mtime: new Date().toISOString(),\n          id: 'process-' + proc.replace(/\\W+/g, '-'),\n          label: `[PROCESS RUNNING] ${proc} \u2014 worm/daemon active`,\n          category: 'persistence',\n          severity: 'critical',\n          match: proc,\n          snippet: `Process \"${proc}\" found in running process list`,\n        });\n      }\n    }\n\n    // Unexpected bun execution (not in standard package manager positions)\n    if (/\\bbun\\b/.test(output) &amp;amp;&amp;amp; !output.includes('bun-as-installer')) {\n      processHits.push({\n        path: '[process list]',\n        size: 0,\n        mtime: new Date().toISOString(),\n        id: 'process-bun-unexpected',\n        label: '[PROCESS RUNNING] bun \u2014 unexpected Bun runtime (evasion technique)',\n        category: 'execution',\n        severity: 'high',\n        match: 'bun',\n        snippet: 'Bun runtime process found running \u2014 may indicate worm preinstall hook activity',\n      });\n    }\n  } catch {\n    // Process list not available \u2014 not a fatal error\n  }\n\n  return processHits;\n}\n\n// \u2500\u2500 Core scan orchestration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction buildScanTargets(opts) {\n  const defaults = getDefaultTargets();\n  const fileTargets = [...defaults.files];\n  const dirTargets = [...defaults.dirs];\n\n  if (opts.root) {\n    const resolvedRoot = path.resolve(opts.root);\n    if (fs.existsSync(resolvedRoot)) {\n      const stats = fs.statSync(resolvedRoot);\n      if (stats.isDirectory()) {\n        dirTargets.push(resolvedRoot);\n      } else if (stats.isFile()) {\n        fileTargets.push(resolvedRoot);\n      }\n    }\n  }\n\n  return { files: fileTargets, dirs: dirTargets };\n}\n\nfunction buildAudit(opts) {\n  const { C, log } = createLogger();\n\n  log.title('MINI SHAI-HULUD AUDIT \u2014 CVE-2026-45321');\n  console.log(`  ${C.red}${C.bold}\u26a0  DO NOT revoke tokens before isolating the machine  \u26a0${C.reset}`);\n  console.log(`  ${C.dim}${ADVISORY_REF}${C.reset}\\n`);\n\n  const targets = buildScanTargets(opts);\n\n  // \u2500\u2500 Step 1: Payload file existence (presence alone = critical) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  log.step('Checking for payload files (existence check)...');\n  const payloadNames = ['setup_bun.js', 'bun_environment.js', 'router_init.js', 'router_runtime.js', 'tanstack_runner.js', 'transformers.pyz'];\n  const payloadExistenceHits = targets.files\n    .filter((f) =&amp;gt; payloadNames.includes(path.basename(f)))\n    .map(checkPayloadFileExists)\n    .filter(Boolean);\n\n  // \u2500\u2500 Step 2: Process list check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  log.step('Checking running processes...');\n  const processHits = checkSuspiciousProcesses(C);\n\n  // \u2500\u2500 Step 3: Scan known files for IOC patterns \u2500\u2500\u2500\u2500\u2500\u2500\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  log.step('Scanning targeted files for IOC patterns...');\n  const knownFileHits = [];\n  const progress = makeProgress(targets.files.length, C);\n  for (const filePath of targets.files) {\n    progress.tick(path.basename(filePath));\n    knownFileHits.push(...scanFile(filePath));\n  }\n\n  // \u2500\u2500 Step 4: Walk evidence directories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  log.step('Walking evidence directories...');\n  const walkedFiles = walkFiles(targets.dirs, {\n    maxDepth: Number.isFinite(opts.maxDepth) ? opts.maxDepth : 4,\n    filter: (f) =&amp;gt; isTextLikeEvidence(f, opts),\n  });\n  const walkedHits = [];\n  const walkProgress = makeProgress(walkedFiles.length, C);\n  for (const filePath of walkedFiles) {\n    walkProgress.tick(path.basename(filePath));\n    walkedHits.push(...scanFile(filePath));\n  }\n\n  // \u2500\u2500 Aggregate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const allHits = dedupeByKey([...payloadExistenceHits, ...processHits, ...knownFileHits, ...walkedHits], (hit) =&amp;gt; `${hit.path}|${hit.id}|${hit.match}|${hit.snippet}`);\n  const filesWithHits = groupByFile(allHits);\n  const criticalHits = allHits.filter((hit) =&amp;gt; hit.severity === 'critical');\n  const scannedFiles = dedupeByKey([...targets.files, ...walkedFiles], (f) =&amp;gt; f);\n\n  const summary = {\n    targetedFiles: targets.files.length,\n    scannedFiles: scannedFiles.length,\n    filesWithHits: filesWithHits.length,\n    totalHits: allHits.length,\n    criticalHits: criticalHits.length,\n    categories: allHits.reduce((acc, hit) =&amp;gt; {\n      acc[hit.category] = (acc[hit.category] ?? 0) + 1;\n      return acc;\n    }, {}),\n  };\n\n  return {\n    C,\n    log,\n    summary,\n    filesWithHits,\n    criticalHits,\n    report: {\n      auditDate: new Date().toISOString(),\n      advisory: ADVISORY_REF,\n      scope: {\n        root: opts.root ? path.resolve(opts.root) : null,\n        host: os.hostname(),\n        platform: process.platform,\n      },\n      summary,\n      findings: allHits,\n      filesWithHits,\n    },\n  };\n}\n\n// \u2500\u2500 CSV serialisation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction buildCsvRows(result) {\n  const { summary, report, filesWithHits } = result;\n  const rows = [\n    {\n      recordType: 'summary',\n      auditDate: report.auditDate,\n      host: report.scope.host,\n      platform: report.scope.platform,\n      root: report.scope.root,\n      checkType: ADVISORY_REF,\n      targetedFiles: summary.targetedFiles,\n      scannedFiles: summary.scannedFiles,\n      filesWithHits: summary.filesWithHits,\n      totalHits: summary.totalHits,\n      criticalHits: summary.criticalHits,\n      categories: JSON.stringify(summary.categories),\n    },\n  ];\n\n  for (const file of filesWithHits) {\n    for (const hit of file.hits) {\n      rows.push({\n        recordType: 'finding',\n        auditDate: report.auditDate,\n        host: report.scope.host,\n        platform: report.scope.platform,\n        root: report.scope.root,\n        checkType: ADVISORY_REF,\n        path: file.path,\n        size: hit.size,\n        mtime: hit.mtime,\n        id: hit.id,\n        label: hit.label,\n        category: hit.category,\n        severity: hit.severity,\n        match: hit.match,\n        snippet: hit.snippet,\n      });\n    }\n  }\n\n  return rows;\n}\n\n// \u2500\u2500 Terminal report \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction printReport(result) {\n  const { C, log, summary, filesWithHits } = result;\n\n  log.step('Results:');\n\n  if (summary.criticalHits === 0) {\n    log.ok(\n      'CLEAR \u2014 no critical Mini Shai-Hulud IOCs detected.\\n' + '  Checked: payload files, persistence artefacts, C2 domains, package versions,\\n' + '           distinctive strings, daemon processes.',\n    );\n    console.log(`\\n  \ud83d\udcc4 ${result.reportPath}  (${summary.scannedFiles} files scanned)\\n`);\n    return;\n  }\n\n  // \u2500\u2500 Critical IOCs found \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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  const criticalFiles = filesWithHits.filter((f) =&amp;gt; f.hits.some((h) =&amp;gt; h.severity === 'critical'));\n\n  console.log(`\\n${C.red}${C.bold}\u2501\u2501\u2501 CRITICAL IOCs DETECTED \u2014 Mini Shai-Hulud \u2501\u2501\u2501${C.reset}`);\n  console.log(`${C.red}${C.bold}  CVE-2026-45321 / GHSA-g7cv-rxg3-hmpx \u2014 CVSS 9.6${C.reset}\\n`);\n\n  for (const file of criticalFiles) {\n    console.log(`  ${C.bold}${file.path}${C.reset}`);\n    for (const hit of file.hits.filter((h) =&amp;gt; h.severity === 'critical')) {\n      console.log(`    ${C.red}${hit.label}${C.reset}  ${C.dim}(${hit.category})${C.reset}`);\n      if (hit.snippet) {\n        console.log(`    ${C.dim}${hit.snippet}${C.reset}`);\n      }\n    }\n    console.log('');\n  }\n\n  console.log(`${C.red}${C.bold}\u2501\u2501\u2501 INCIDENT RESPONSE \u2014 MANDATORY ORDER \u2501\u2501\u2501${C.reset}`);\n  console.log(`\\n  ${C.red}${C.bold}\u26a0  DO NOT revoke tokens before step 3  \u26a0${C.reset}`);\n  console.log(`  ${C.dim}The worm watchdog triggers rm -rf ~/ on token revocation (HTTP 40X).${C.reset}\\n`);\n  console.log(`  ${C.red}1.${C.reset} ISOLATE  \u2014 disconnect machine from network immediately`);\n  console.log(`  ${C.red}2.${C.reset} IMAGE    \u2014 forensic disk image before any cleanup`);\n  console.log(`  ${C.red}3.${C.reset} KILL     \u2014 disable gh-token-monitor daemon and worm processes`);\n  console.log(`  ${C.red}4.${C.reset} REVOKE   \u2014 npm, GitHub PAT/OAuth, AWS, Azure, GCP, Kubernetes, SSH`);\n  console.log(`  ${C.red}5.${C.reset} ROTATE   \u2014 all credentials reachable from this host`);\n  console.log(`  ${C.red}6.${C.reset} AUDIT    \u2014 AWS CloudTrail, Azure Activity Logs, GCP Audit Logs`);\n  console.log(`  ${C.red}7.${C.reset} VERIFY   \u2014 GitHub account: recent Dune-themed public repos, new PATs\\n`);\n  console.log(`  \ud83d\udcc4 ${result.reportPath}  (full execution trace inside)\\n`);\n}\n\n// \u2500\u2500 Entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nasync function main() {\n  const opts = parseArgs(process.argv);\n\n  if (opts.help) {\n    showHelp();\n    process.exit(0);\n  }\n\n  const result = buildAudit(opts);\n  result.reportPath = opts.output;\n  writeReportFile(opts.output, result.report, buildCsvRows(result), CSV_COLUMNS);\n  printReport(result);\n}\n\nmain().catch((err) =&amp;gt; {\n  const { log } = createLogger();\n  log.alert(`Fatal: ${err.message}`);\n  process.exit(1);\n});\n", "creation_timestamp": "2026-05-18T20:22:18.000000Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/6397bdc2-5791-4276-8c4f-1b9d52b87c4c/export"/>
    <published>2026-05-18T20:22:18+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/ceec749b-fe82-4723-a621-ad3eca67bda1/export</id>
    <title>ceec749b-fe82-4723-a621-ad3eca67bda1</title>
    <updated>2026-05-18T21:15:38.638104+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "ceec749b-fe82-4723-a621-ad3eca67bda1", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-42899", "type": "seen", "source": "https://gist.github.com/alon710/858f4c780c5ed9bd0f94d013b01935b8", "content": "# CVE-2026-42899: CVE-2026-42899: Denial of Service via Infinite Loops in ASP.NET Core Subsystems\n\n&amp;gt; **CVSS Score:** 7.5\n&amp;gt; **Published:** 2026-05-18\n&amp;gt; **Full Report:** https://cvereports.com/reports/CVE-2026-42899\n\n## Summary\nCVE-2026-42899 is a high-severity Denial of Service (DoS) vulnerability in the Microsoft ASP.NET Core framework, characterized by multiple instances of a 'Loop with Unreachable Exit Condition' (CWE-835). An unauthenticated remote attacker can trigger 100% CPU utilization by supplying specially crafted requests that exploit logic errors in request parsing, data protection, minimal APIs, and caching subsystems.\n\n## TL;DR\nUnauthenticated remote Denial of Service in ASP.NET Core due to infinite loops in core subsystems, remediated in .NET 8.0.27, 9.0.16, and 10.0.8.\n\n## Technical Details\n\n- **CWE ID**: CWE-835\n- **Attack Vector**: Network\n- **CVSS v3.1**: 7.5 (High)\n- **EPSS**: 0.00047 (0.05%)\n- **Impact**: High Availability (Denial of Service)\n- **Exploit Status**: None Public\n- **CISA KEV**: Not Listed\n\n## Affected Systems\n\n- ASP.NET Core on .NET 8.0\n- ASP.NET Core on .NET 9.0\n- ASP.NET Core on .NET 10.0\n- **.NET 8.0**: 8.0.0 &amp;lt;= version &amp;lt; 8.0.27 (Fixed in: `8.0.27`)\n- **.NET 9.0**: 9.0.0 &amp;lt;= version &amp;lt; 9.0.16 (Fixed in: `9.0.16`)\n- **.NET 10.0**: 10.0.0 &amp;lt;= version &amp;lt; 10.0.8 (Fixed in: `10.0.8`)\n\n## Mitigation\n\n- Update .NET runtime and SDK to patched versions\n- Update JavaScript dependencies (lodash, serialize-javascript) for Blazor/SPA applications\n- Implement WAF rules to pre-validate and drop malformed API parameters\n- Enforce connection rate limits and strict request timeouts\n\n**Remediation Steps:**\n1. Identify all systems running .NET 8.0, 9.0, or 10.0\n2. Download and install .NET updates 8.0.27, 9.0.16, or 10.0.8\n3. Rebuild self-contained applications with the updated .NET SDK\n4. Update package.json dependencies to lodash &amp;gt;=4.18.0 and serialize-javascript &amp;gt;=7.0.5\n5. Deploy updated application artifacts to production environments\n6. Monitor application worker process CPU utilization to verify vulnerability resolution\n\n## References\n\n- [Microsoft Security Response Center (MSRC) Advisory](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-42899)\n- [CVE Org Record for CVE-2026-42899](https://www.cve.org/CVERecord?id=CVE-2026-42899)\n- [GitHub Patch (DataProtection)](https://github.com/dotnet/aspnetcore/commit/c5fa707d1dd8a67dc1392fa9c3561d8d353577e3)\n- [GitHub Patch (RequestDelegateFactory)](https://github.com/dotnet/aspnetcore/commit/31515a42d423dcfe2c646801f8b4a35350705c25)\n- [GitHub Patch (HybridCache)](https://github.com/dotnet/aspnetcore/commit/3ec3980cc353d6b9fff9fb6fef1f655f8d9f2158)\n\n\n---\n*Generated by [CVEReports](https://cvereports.com/reports/CVE-2026-42899) - Automated Vulnerability Intelligence*", "creation_timestamp": "2026-05-18T20:40:49.000000Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/ceec749b-fe82-4723-a621-ad3eca67bda1/export"/>
    <published>2026-05-18T20:40:49+00:00</published>
  </entry>
</feed>
