I was auditing src/version.ts when I hit this:
let comspecImpl: () => string | undefined = () => process.env.COMSPEC;No validation. No fallback logic worth trusting. Just the raw environment variable, read and used as the binary path passed to execFile().
That became CVE-2026–47092.
The Setup
claude-hud is a statusline plugin for Claude Code, showing your model, context usage, git branch, and token counts in real time. It has over 20,000 GitHub stars. A lot of developers are running this on every keystroke.
On Windows, when the Claude binary has a .cmd or .bat extension (the default when you install Claude Code via npm), claude-hud needs to invoke cmd.exe to run the version check. Instead of hardcoding the path to cmd.exe, it reads COMSPEC, the Windows environment variable that conventionally points to the command interpreter.
Here's the full vulnerable function:
export function _getClaudeVersionInvocation(
binaryPath: string,
platform: NodeJS.Platform = platformImpl(),
comspec: string | undefined = comspecImpl()
): ClaudeVersionInvocation {
const ext = path.extname(binaryPath).toLowerCase();
if (platform === 'win32' && (ext === '.cmd' || ext === '.bat')) {
return {
file: comspec || 'cmd.exe', // this is the problem
args: ['/d', '/s', '/c', `"${command}"`],
};
}
}The || 'cmd.exe' fallback only fires when COMSPEC is empty or unset. Set it to anything else and the fallback never runs. Your binary executes instead.
Three Conditions, All Met by Default
This fires when:
- Platform is
win32 - Claude binary has
.cmdor.batextension (the npm default) showClaudeCodeVersionis enabled (the config default)
Standard Windows + npm install + claude-hud. No custom setup needed.
The Attack
Any process that runs before claude-hud can set COMSPEC. On a developer machine that list is long: npm postinstall scripts, .env files, VSCode tasks, shell profiles.
A malicious package postinstall doing this is enough:
"scripts": {
"postinstall": "setx COMSPEC C:\\Users\\victim\\AppData\\Local\\Temp\\evil.cmd"
}Now every time the victim opens Claude Code, execFile() runs the attacker's binary with the real cmd.exe argument structure: /d /s /c "claude.cmd --version". The payload executes, chains to the real cmd.exe to suppress errors, and the HUD renders normally. The victim sees nothing.
Proof of Concept
The module exports test setters that let you mock the Windows environment. I used those to verify end-to-end from WSL without a Windows runtime:
_setVersionInvocationEnvForTests(
() => 'win32',
() => '/tmp/poc-comspec/evil_cmd.sh'
);
_setResolveClaudeBinaryForTests(() => ({
path: '/tmp/poc-comspec/fake-claude.cmd',
mtimeMs: 0
}));
_resetVersionCache();
await getClaudeCodeVersion();




The Fix
Don't use COMSPEC. Use the canonical path:
const SAFE_CMD = process.env.SystemRoot
? path.join(process.env.SystemRoot, 'System32', 'cmd.exe')
: 'cmd.exe';
return {
file: SAFE_CMD,
args: ['/d', '/s', '/c', `"${command}"`],
};%SystemRoot%\System32\cmd.exe is always right on Windows. It doesn't need to be configurable. Patched in commit 234d9aa.
Disclosure
Private disclosure failed. The email in SECURITY.md (jarrodwttsyt@gmail.com) bounced, the domain doesn't exist. I filed a LinkedIn connection request and eventually opened a public issue as a last resort. CVE-2026-47092 was assigned and the fix shipped shortly after.
If you're a maintainer: check that your security contact email actually works.
CVE: CVE-2026–47092 My CVE portfolio: github.com/KatrielMoses/CVEs
Katriel Moses, Independent Security Researcher