diff --git a/docs/security/supply-chain-incident-response.md b/docs/security/supply-chain-incident-response.md index 00459a3e..99d603af 100644 --- a/docs/security/supply-chain-incident-response.md +++ b/docs/security/supply-chain-incident-response.md @@ -27,8 +27,8 @@ credentials: they carried destructive or unauthorized file-writing behavior. - The live IOC set includes persistence through Claude Code `.claude/settings.json`, VS Code `.vscode/tasks.json`, and OS-level - `gh-token-monitor` LaunchAgent/systemd services. Some variants add a - dead-man-switch token description + `gh-token-monitor` LaunchAgent/systemd services. Some variants add + `~/.config/gh-token-monitor/token` plus a dead-man-switch token description `IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner`, malicious workflow files such as `.github/workflows/codeql_analysis.yml`, and Python runtime payloads such as `transformers.pyz` / `pgmonitor.py`. Remove those @@ -124,6 +124,7 @@ If ECC or a maintainer machine installed a known-bad package version: - `~/Library/LaunchAgents/com.user.gh-token-monitor.plist`; - `~/.config/systemd/user/gh-token-monitor.service`; - `~/.config/systemd/user/pgsql-monitor.service`; + - `~/.config/gh-token-monitor/token`; - `~/.local/bin/gh-token-monitor.sh`; - `~/.local/bin/pgmonitor.py`; - `/tmp/transformers.pyz`, `/tmp/pgmonitor.py`, and their diff --git a/scripts/ci/scan-supply-chain-iocs.js b/scripts/ci/scan-supply-chain-iocs.js index 0eb4a629..4604909d 100755 --- a/scripts/ci/scan-supply-chain-iocs.js +++ b/scripts/ci/scan-supply-chain-iocs.js @@ -387,6 +387,14 @@ const PAYLOAD_FILENAMES = new Set([ 'shai-hulud-workflow.yml', ]); +function normalizedPath(filePath) { + return filePath.split(path.sep).join('/'); +} + +function isGhTokenMonitorTokenPath(filePath) { + return /\/\.config\/gh-token-monitor\/token$/.test(normalizedPath(filePath)); +} + const IGNORED_DIRS = new Set([ '.git', '.next', @@ -404,7 +412,7 @@ function normalizeForMatch(value) { } function isInSpecialConfigPath(filePath) { - const normalized = filePath.split(path.sep).join('/'); + const normalized = normalizedPath(filePath); return /\/\.claude\//.test(normalized) || /\/\.vscode\//.test(normalized) || /\/\.kiro\/settings\//.test(normalized) @@ -416,6 +424,7 @@ function isInSpecialConfigPath(filePath) { function shouldInspectFile(filePath) { const base = path.basename(filePath); + if (isGhTokenMonitorTokenPath(filePath)) return true; if (DEPENDENCY_FILENAMES.has(base)) return true; if (PERSISTENCE_FILENAMES.has(base) && isInSpecialConfigPath(filePath)) return true; if (PAYLOAD_FILENAMES.has(base) && filePath.includes(`${path.sep}node_modules${path.sep}`)) return true; @@ -600,6 +609,17 @@ function scanFile(filePath, rootDir, findings) { ); } + if (isGhTokenMonitorTokenPath(filePath)) { + addFinding( + findings, + 'critical', + relativePath, + 1, + '~/.config/gh-token-monitor/token', + 'Known Mini Shai-Hulud dead-man switch token store is present', + ); + } + for (const indicator of CRITICAL_TEXT_INDICATORS) { const index = lowerText.indexOf(normalizeForMatch(indicator)); if (index !== -1) { @@ -651,6 +671,7 @@ function homeTargets(homeDir) { 'Library/LaunchAgents/com.user.gh-token-monitor.plist', '.config/systemd/user/gh-token-monitor.service', '.config/systemd/user/pgsql-monitor.service', + '.config/gh-token-monitor/token', '.local/bin/gh-token-monitor.sh', '.local/bin/pgmonitor.py', ].map(relativePath => path.join(homeDir, relativePath)); diff --git a/tests/ci/scan-supply-chain-iocs.test.js b/tests/ci/scan-supply-chain-iocs.test.js index a4ed8d23..f4efcbba 100755 --- a/tests/ci/scan-supply-chain-iocs.test.js +++ b/tests/ci/scan-supply-chain-iocs.test.js @@ -365,6 +365,18 @@ function run() { }); })) passed++; else failed++; + if (test('rejects Mini Shai-Hulud gh-token-monitor token store when home scan is enabled', () => { + withFixture({ + 'home/.config/gh-token-monitor/token': 'redacted-token-placeholder', + }, rootDir => { + const homeDir = path.join(rootDir, 'home'); + const result = scanSupplyChainIocs({ rootDir, home: true, homeDir }); + assert.ok(result.findings.some( + finding => finding.indicator === '~/.config/gh-token-monitor/token', + )); + }); + })) passed++; else failed++; + if (test('rejects installed payload filenames in node_modules', () => { withFixture({ 'node_modules/@tanstack/react-router/router_init.js': '/* payload */',