security: cover gh-token-monitor token persistence

This commit is contained in:
Affaan Mustafa
2026-05-17 17:46:35 -04:00
parent 6b282aaa43
commit 36d390aa7d
3 changed files with 37 additions and 3 deletions

View File

@@ -27,8 +27,8 @@ credentials:
they carried destructive or unauthorized file-writing behavior. they carried destructive or unauthorized file-writing behavior.
- The live IOC set includes persistence through Claude Code - The live IOC set includes persistence through Claude Code
`.claude/settings.json`, VS Code `.vscode/tasks.json`, and OS-level `.claude/settings.json`, VS Code `.vscode/tasks.json`, and OS-level
`gh-token-monitor` LaunchAgent/systemd services. Some variants add a `gh-token-monitor` LaunchAgent/systemd services. Some variants add
dead-man-switch token description `~/.config/gh-token-monitor/token` plus a dead-man-switch token description
`IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner`, malicious workflow `IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner`, malicious workflow
files such as `.github/workflows/codeql_analysis.yml`, and Python runtime files such as `.github/workflows/codeql_analysis.yml`, and Python runtime
payloads such as `transformers.pyz` / `pgmonitor.py`. Remove those 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`; - `~/Library/LaunchAgents/com.user.gh-token-monitor.plist`;
- `~/.config/systemd/user/gh-token-monitor.service`; - `~/.config/systemd/user/gh-token-monitor.service`;
- `~/.config/systemd/user/pgsql-monitor.service`; - `~/.config/systemd/user/pgsql-monitor.service`;
- `~/.config/gh-token-monitor/token`;
- `~/.local/bin/gh-token-monitor.sh`; - `~/.local/bin/gh-token-monitor.sh`;
- `~/.local/bin/pgmonitor.py`; - `~/.local/bin/pgmonitor.py`;
- `/tmp/transformers.pyz`, `/tmp/pgmonitor.py`, and their - `/tmp/transformers.pyz`, `/tmp/pgmonitor.py`, and their

View File

@@ -387,6 +387,14 @@ const PAYLOAD_FILENAMES = new Set([
'shai-hulud-workflow.yml', '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([ const IGNORED_DIRS = new Set([
'.git', '.git',
'.next', '.next',
@@ -404,7 +412,7 @@ function normalizeForMatch(value) {
} }
function isInSpecialConfigPath(filePath) { function isInSpecialConfigPath(filePath) {
const normalized = filePath.split(path.sep).join('/'); const normalized = normalizedPath(filePath);
return /\/\.claude\//.test(normalized) return /\/\.claude\//.test(normalized)
|| /\/\.vscode\//.test(normalized) || /\/\.vscode\//.test(normalized)
|| /\/\.kiro\/settings\//.test(normalized) || /\/\.kiro\/settings\//.test(normalized)
@@ -416,6 +424,7 @@ function isInSpecialConfigPath(filePath) {
function shouldInspectFile(filePath) { function shouldInspectFile(filePath) {
const base = path.basename(filePath); const base = path.basename(filePath);
if (isGhTokenMonitorTokenPath(filePath)) return true;
if (DEPENDENCY_FILENAMES.has(base)) return true; if (DEPENDENCY_FILENAMES.has(base)) return true;
if (PERSISTENCE_FILENAMES.has(base) && isInSpecialConfigPath(filePath)) 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; 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) { for (const indicator of CRITICAL_TEXT_INDICATORS) {
const index = lowerText.indexOf(normalizeForMatch(indicator)); const index = lowerText.indexOf(normalizeForMatch(indicator));
if (index !== -1) { if (index !== -1) {
@@ -651,6 +671,7 @@ function homeTargets(homeDir) {
'Library/LaunchAgents/com.user.gh-token-monitor.plist', 'Library/LaunchAgents/com.user.gh-token-monitor.plist',
'.config/systemd/user/gh-token-monitor.service', '.config/systemd/user/gh-token-monitor.service',
'.config/systemd/user/pgsql-monitor.service', '.config/systemd/user/pgsql-monitor.service',
'.config/gh-token-monitor/token',
'.local/bin/gh-token-monitor.sh', '.local/bin/gh-token-monitor.sh',
'.local/bin/pgmonitor.py', '.local/bin/pgmonitor.py',
].map(relativePath => path.join(homeDir, relativePath)); ].map(relativePath => path.join(homeDir, relativePath));

View File

@@ -365,6 +365,18 @@ function run() {
}); });
})) passed++; else failed++; })) 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', () => { if (test('rejects installed payload filenames in node_modules', () => {
withFixture({ withFixture({
'node_modules/@tanstack/react-router/router_init.js': '/* payload */', 'node_modules/@tanstack/react-router/router_init.js': '/* payload */',