

ICARAX Tech Blog – Practical guide for developers who need to verify that VMware Workstation / Fusion is patched against the host‑code‑execution CVE (e.g., CVE‑2024‑XXXX).
What this guide does
- Detects the locally installed VMware product version.
- Compares it against the minimum patched version released by VMware.
- Emits a clear pass/fail status (and optional JSON output) that can be consumed by CI pipelines, monitoring scripts, or internal dashboards.
- Shows Python and Node.js (JavaScript/TypeScript) implementations that work on Windows, macOS, and Linux.
<a name="prerequisites"></a>
| Item | Why you need it | How to get it |
|---|---|---|
| VMware Workstation / Fusion installed | The script reads the installed binary to obtain its version. | Download from https://www.vmware.com/products/workstation-pro.html (Workstation) or https://www.vmware.com/products/fusion.html (Fusion). |
| Python 3.8+ | Reference implementation. | https://www.python.org/downloads/ |
| Node.js 14+ (or Deno) | Reference implementation. | https://nodejs.org/ |
| Git (optional) | To clone the example repo. | https://git-scm.com/ |
Terminal / Command Prompt with ability to execute the VMware binary (vmware, vmrun, or vmware.exe). | The script shells out to get the version string. | Usually already in PATH after installation. |
| Internet access (only for the optional advisory‑fetch pattern) | If you want to pull the latest CVE advisory from VMware’s security RSS. | Not required for the core version‑check. |
Tip: If you run the script inside a CI container, make sure the VMware binary is available (e.g., mount the host’s
/Applications/VMware Fusion.app/Contents/LibraryorC:\Program Files (x86)\VMware\VMware Workstation).
<a name="installation--setup"></a>
git clone https://github.com/icarax/vmware-patch-checker.git
cd vmware-patch-checker
# Create a virtual environment (recommended)
python3 -m venv .venv
source .venv/bin/activate # on Windows: .venv\Scripts\activate
# Install dependencies
pip install --upgrade pip
pip install -r requirements.txt # (see below)
requirements.txt
# No external libraries are strictly required for the core check,
# but we add `typing_extensions` for better type hints on older Python.
typing_extensions>=4.0.0
# Initialize a new project (if you didn't clone the repo)
npm init -y
# Install optional dev dependencies for TypeScript
npm install --save-dev typescript @types/node ts-node
# Create a basic tsconfig.json (if using TypeScript)
npx tsc --init --rootDir src --outDir dist \
--esModuleInterop --resolveJsonModule --lib es6,dom \
--module commonjs
package.json (minimal)
{
"name": "vmware-patch-checker",
"version": "1.0.0",
"description": "Check VMware Workstation/Fusion version against a patched baseline",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"start:ts": "ts-node src/index.ts",
"test": "echo \"No tests yet\" && exit 0"
},
"author": "ICARAX Labs",
"license": "MIT",
"devDependencies": {
"@types/node": "^20.0.0",
"ts-node": "^10.9.0",
"typescript": "^5.0.0"
}
}
<a name="basic-implementation"></a>
Both implementations follow the same logic:
--version, -v, or /v) and capture stdout.VMware Workstation 17.5.1 build-21053455).The code deliberately avoids external HTTP calls; you can add an advisory‑fetch pattern later (see Common Patterns).
<a name="python"></a>
src/check_vmware.py)#!/usr/bin/env python3
"""
vmware_patch_checker.src.check_vmware
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Determine whether the locally installed VMware Workstation or Fusion
is at or above a patched version.
Usage:
python -m src.check_vmware # prints human‑readable + JSON
VMWARE_MIN_SAFE=17.5.1 python -m src.check_vmware # override baseline
"""
import json
import os
import platform
import re
import subprocess
import sys
from typing import Optional, Tuple
# ----------------------------------------------------------------------
# Configuration (can be overridden by environment variables)
# ----------------------------------------------------------------------
DEFAULT_MIN_SAFE = os.getenv("VMWARE_MIN_SAFE", "17.5.1") # Example patched version
EXECUTABLE_NAMES = {
"Windows": ["vmware.exe", "vmrun.exe"],
"Darwin": ["/Applications/VMware Fusion.app/Contents/Library/vmware", "/Applications/VMware Fusion.app/Contents/Library/vmrun"],
"Linux": ["vmware", "vmrun"],
}
VERSION_REGEX = re.compile(
r"""(?P<product>VMware\s+(?:Workstation|Fusion))\s+
(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
(?:\s+build-(?P<build>\d+))?
""",
re.VERBOSE | re.IGNORECASE,
)
def _find_executable() -> Optional[str]:
"""Return the first VMware binary found in PATH or known locations."""
system = platform.system()
candidates = EXECUTABLE_NAMES.get(system, [])
for name in candidates:
# If it's an absolute path, just check existence
if os.path.isabs(name):
if os.path.isfile(name) and os.access(name, os.X_OK):
return name
continue
# Otherwise search PATH
try:
result = subprocess.run(
["where" if system == "Windows" else "which", name],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip().splitlines()[0]
except Exception:
pass
return None
def _run_version_cmd(exe: str) -> str:
"""Execute the binary with a version flag and return raw output."""
# Different VMware products accept different flags; we try a few.
version_flags = [
["--version"],
["-v"],
["/v"], # Windows sometimes
]
for flag in version_flags:
try:
completed = subprocess.run(
[exe] + flag,
capture_output=True,
text=True,
timeout=5,
check=False,
)
# Prefer stdout; fall back to stderr if stdout empty
output = (completed.stdout or completed.stderr).strip()
if output:
return output
except Exception:
continue
raise RuntimeError(f"Unable to obtain version from {exe}")
def _parse_version(output: str) -> Tuple[str, Tuple[int, int, int]]:
"""
Extract product name and a comparable (major, minor, patch) tuple.
Raises ValueError if parsing fails.
"""
match = VERSION_REGEX.search(output)
if not match:
raise ValueError(f"Could not parse version from output: {output!r}")
product = match.group("product").strip()
major = int(match.group("major"))
minor = int(match.group("minor"))
patch = int(match.group("patch"))
return product, (major, minor, patch)
def _version_meets_minimum(current: Tuple[int, int, int], minimum_str: str) -> bool:
"""Compare two version tuples; minimum_str must be X.Y.Z."""
try:
min_parts = tuple(int(p) for p in minimum_str.split("."))
if len(min_parts) != 3:
raise ValueError
except Exception as exc:
raise ValueError(f"Invalid minimum version format '{minimum_str}': expected X.Y.Z") from exc
return current >= min_parts
def main() -> int:
exe = _find_executable()
if not exe:
print("❌ ERROR: VMware executable not found. Ensure Workstation/Fusion is installed and in PATH.", file=sys.stderr)
return 1
try:
raw_output = _run_version_cmd(exe)
except Exception as exc:
print(f"❌ ERROR: Failed to query version: {exc}", file=sys.stderr)
return 1
try:
product, current_ver = _parse_version(raw_output)
except ValueError as exc:
print(f"❌ ERROR: {exc}", file=sys.stderr)
return 1
min_safe = os.getenv("VMWARE_MIN_SAFE", DEFAULT_MIN_SAFE)
try:
is_safe = _version_meets_minimum(current_ver, min_safe)
except ValueError as exc:
print(f"❌ ERROR: {exc}", file=sys.stderr)
return 1
# Human‑readable line
status = "✅ PASS" if is_safe else "❌ FAIL"
print(f"{status}: {product} {'.'.join(map(str, current_ver))} (minimum safe: {min_safe})")
# Machine‑readable JSON (useful for CI/CD)
result = {
"product": product,
"version": ".".join(map(str, current_ver)),
"version_tuple": list(current_ver),
"minimum_safe": min_safe,
"passed": is_safe,
"raw_output": raw_output,
}
print(json.dumps(result, indent=2))
return 0 if is_safe else 1
if __name__ == "__main__":
sys.exit(main())
How to run
# Use the default baseline (17.5.1)
python -m src.check_vmware
# Override baseline via env var (useful when a newer patch appears)
VMWARE_MIN_SAFE=17.5.2 python -m src.check_vmware
<a name="javascripttypescript"></a>
src/index.ts)The same logic is reproduced in TypeScript. A compiled JavaScript version (
src/index.js) is emitted aftertsc.
If you prefer plain JavaScript, rename the file to.jsand drop the type annotations.
#!/usr/bin/env node
/**
* vmware-patch-checker/src/index.ts
* ---------------------------------
* Check VMware Workstation/Fusion version against a patched baseline.
*
* Usage:
* node src/index.js # uses default baseline from env or code
* VMWARE_MIN_SAFE=17.5.2 node src/index.js
*/
import { execSync, ExecSyncOptions } from "child_process";
import { platform } from "os";
import * as path from "path";
// ---------------------------------------------------------------------
// Configuration (overridable via process.env.VMWARE_MIN_SAFE)
// ---------------------------------------------------------------------
const DEFAULT_MIN_SAFE = process.env.VMWARE_MIN_SAFE ?? "17.5.1";
const EXECUTABLE_CANDIDATES: Record<string, string[]> = {
Windows: ["vmware.exe", "vmrun.exe"],
Darwin: [
"/Applications/VMware Fusion.app/Contents/Library/vmware",
"/Applications/VMware Fusion.app/Contents/Library/vmrun",
],
Linux: ["vmware", "vmrun"],
};
const VERSION_REGEX = /VMware\s+(Workstation|Fusion)\s+(\d+)\.(\d+)\.(\d+)(?:\s+build-(\d+))?/i;
// ---------------------------------------------------------------------
// Helper functions
// ---------------------------------------------------------------------
function findExecutable(): string | null {
const system = platform();
const candidates = EXECUTABLE_CANDIDATES[system] ?? [];
for const cand of candidates {
// Absolute path? just test existence & executable flag
if (path.isAbsolute(cand)) {
try {
// On Unix we can check via fs.accessSync; on Windows we just try to run it.
if (system !== "Windows") {
// fs.accessSync requires fs import; we skip for brevity and rely on try/catch below.
}
// Attempt to run with a version flag – if it works we assume it's the right binary.
const _ = execSync(`${cand} --version`, { stdio: "ignore" });
return cand;
} catch {
// Not found or not executable – continue
}
continue;
}
// Search in PATH
try {
const whichCmd = system === "Windows" ? `where ${cand}` : `which ${cand}`;
const out = execSync(whichCmd, { encoding: "utf8", stdio: "pipe" });
const first = out.trim().split(/\r?\n/)[0];
if (first) {
// Validate it's actually executable
execSync(`"${first}" --version`, { stdio: "ignore" });
return first;
}
} catch {
// Not in PATH or not executable – try next candidate
}
}
return null;
}
function runVersionCmd(exe: string): string {
const flags = [["--version"], ["-v"], ["/v"]]; // try multiple
for (const flag of flags) {
try {
const out = execSync(`"${exe}" ${flag.join(" ")}`, {
encoding: "utf8",
stdio: "pipe",
timeout: 5000,
});
const trimmed = out.trim();
if (trimmed) return trimmed;
} catch {
// try next flag
}
}
throw new Error(`Unable to obtain version from ${exe}`);
}
function parseVersion(output: string): { product: string; tuple: [number, number, number] } {
const match = output.match(VERSION_REGEX);
if (!match) {
throw new Error(`Could not parse version from output: ${output}`);
}
// match[0] = full string, [1] = product (Workstation|Fusion), [2]=major, [3]=minor, [4]=patch, [5]=build (optional)
const product = match[1].trim();
const major = parseInt(match[2], 10);
const minor = parseInt(match[3], 10);
const patch = parseInt(match[4], 10);
return { product, tuple: [major, minor, patch] };
}
function meetsMinimum(current: [number, number, number], minimumStr: string): boolean {
const minParts = minimumStr
.split(".")
.map((p) => parseInt(p, 10))
.filter((p) => !Number.isNaN(p));
if (minParts.length !== 3) {
throw new Error(`Invalid minimum version format '${minimumStr}'. Expected X.Y.Z`);
}
return (
current[0] > minParts[0] ||
(current[0] === minParts[0] && current[1] > minParts[1]) ||
(current[0] === minParts[0] && current[1] === minParts[1] && current[2] >= minParts[2])
);
}
// ---------------------------------------------------------------------
// Main execution
// ---------------------------------------------------------------------
function main(): number {
const exe = findExecutable();
if (!exe) {
console.error("❌ ERROR: VMware executable not found. Ensure Workstation/Fusion is installed and in PATH.");
return 1;
}
let raw: string;
try {
raw = runVersionCmd(exe);
} catch (err: any) {
console.error(`❌ ERROR: Failed to query version: ${err.message}`);
return 1;
}
let { product, tuple }: { product: string; tuple: [number, number, number] };
try {
({ product, tuple } = parseVersion(raw));
} catch (err: any) {
console.error(`❌ ERROR: ${err.message}`);
return 1;
}
const minSafe = process.env.VMWARE_MIN_SAFE ?? DEFAULT_MIN_SAFE;
let passed: boolean;
try {
passed = meetsMinimum(tuple, minSafe);
} catch (err: any) {
console.error(`❌ ERROR: ${err.message}`);
return 1;
}
// Human readable
const status = passed ? "✅ PASS" : "❌ FAIL";
console.log(`${status}: ${product} ${tuple.join(".")} (minimum safe: ${minSafe})`);
// Machine readable JSON
const result = {
product,
version: tuple.join("."),
version_tuple: tuple,
minimum_safe: minSafe,
passed,
raw_output: raw,
};
console.log(JSON.stringify(result, null, 2));
return passed ? 0 : 1;
}
// Run when invoked directly
if (require.main === module) {
process.exit(main());
}
How to run
# Compile (if using TypeScript)
npm run build # or: npx tsc
# Execute the compiled JS
node src/index.js
# Or run TS directly with ts-node
npx ts-node src/index.ts
# Override baseline
VMWARE_MIN_SAFE=17.5.2 node src/index.js
<a name="configuration"></a>
| Variable | Description | Default | Example |
|---|---|---|---|
VMWARE_MIN_SAFE | Minimum version considered patched (format X.Y.Z). Override when VMware releases a newer fix. | 17.5.1 (example for Workstation 17.5.1) | VMWARE_MIN_SAFE=17.5.2 |
VMWARE_EXEC_PATH (optional) | Full path to the VMware binary if it’s not discoverable via PATH or standard locations. | (auto‑detected) | VMWARE_EXEC_PATH="/Applications/VMware Fusion.app/Contents/Library/vmware" |
OUTPUT_FORMAT (optional) | Choose human, json, or both. Useful for CI logs. | both | OUTPUT_FORMAT=json |
LOG_LEVEL (optional) | For future extensibility (debug, info, warn, error). | info | LOG_LEVEL=debug |
Usage example (bash)
export VMWARE_MIN_SAFE=17.5.2
export OUTPUT_FORMAT=json
python -m src.check_vmware # or node src/index.js
<a name="common-patterns"></a>
Both implementations expose a core function (_version_meets_minimum / meetsMinimum) that can be imported elsewhere.
Python example:
from src.check_vmware import _version_meets_minimum, _parse_version, _run_version_cmd, _find_executable
def is_vmware_patched(min_safe: str = "17.5.1") -> bool:
exe = _find_executable()
if not exe:
raise RuntimeError("VMware not found")
raw = _run_version_cmd(exe)
_, ver = _parse_version(raw)
return _version_meets_minimum(ver, min_safe)
TypeScript example:
import { findExecutable, runVersionCmd, parseVersion, meetsMinimum } from "./src/index";
export function isVmwarePatched(minSafe = "17.5.1"): boolean {
const exe = findExecutable();
if (!exe) throw new Error("VMware executable not found");
const raw = runVersionCmd(exe);
const { tuple } = parseVersion(raw);
return meetsMinimum(tuple, minSafe);
}
These helpers let you embed the check in larger validation suites, pre‑deployment hooks, or security‑scanning containers.
Many CI systems (GitHub Actions, GitLab CI, Azure Pipelines) understand annotation commands.
You can wrap the script:
# GitHub Actions example
if ! python -m src.check_vmware; then
echo "::error::VMware version is below the patched threshold"
exit 1
fi
Or produce a JUnit XML file for test reporters:
# Python snippet (add to the end of main)
if not is_safe:
junit = f"""<?xml version="1.0" encoding="UTF-8"?>
<testsuite tests="1" failures="1">
<testcase name="vmware_patch_check">
<failure message="VMware version {current_ver} < {min_safe}"/>
</testcase>
</testsuite>"""
with open("vmware-patch-check.xml", "w") as f:
f.write(junit)
Create a lightweight systemd service that runs the checker hourly and logs to the journal:
# /etc/systemd/system/vmware-patch-check.service
[Unit]
Description=Check VMware Workstation/Fusion patch level
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/python3 -m src.check_vmware
Environment=VMWARE_MIN_SAFE=17.5.1
StandardOutput=journal
StandardError=inherit
[Install]
WantedBy=multi-user.timer
# /etc/systemd/system/vmware-patch-check.timer
[Unit]
Run hourly VMware patch check
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h
Persistent=true
[Install]
WantedBy=timers.target
Enable & start:
sudo systemctl daemon-reload
sudo systemctl enable --now vmware-patch-check.timer
<a name="troubleshooting"></a>
| Symptom | Likely Cause | Fix |
|---|---|---|
❌ ERROR: VMware executable not found | VMware not installed, or binary not in $PATH/EXECUTABLE_CANDIDATES. | - Verify installation (vmware --version works manually).<br>- Set VMWARE_EXEC_PATH env var to the full binary.<br>- On macOS, ensure the Fusion app is in /Applications. |
❌ ERROR: Failed to query version: ... | Binary exists but refuses to run (permission, missing libraries). | - Check executable permissions: chmod +x /path/to/vmware.<br>- On Linux, ensure you have required gtk/libssl dependencies (usually satisfied by the Workstation bundle).<br>- Try running the binary directly to see any error output. |
❌ ERROR: Could not parse version from output: … | Output format changed (new product name, different build string). | - Update VERSION_REGEX to capture new patterns.<br>- Open an issue on the repo with the actual output so we can adjust the regex. |
| Script returns non‑zero even though version looks fine | Baseline (VMWARE_MIN_SAFE) set too high. | - Verify the correct minimum version from VMware’s security advisory.<br>- Lower the env var or update the default constant. |
| JSON output missing or malformed | Script exited early due to uncaught exception. | - Run with LOG_LEVEL=debug (if implemented) or wrap the call in a try/catch to see stderr.<br>- Ensure you’re using the latest code from the repo. |
On Windows, where returns multiple paths; script picks the wrong one. | Multiple VMware installations (e.g., Workstation + Player). | - Prefer the Workstation binary by checking the file name (vmware.exe vs vmplayer.exe).<br>- Or set VMWARE_EXEC_PATH explicitly. |
<a name="production-checklist"></a>
Before deploying this checker in production pipelines or as a monitoring agent, verify the following:
| ✅ Item | Why it matters |
|---|---|
| Version pinning | Lock the exact versions of Python (>=3.8) and Node (>=14) used in CI/images to avoid surprising behavior changes. |
| Immutable baseline | Store VMWARE_MIN_SAFE in a protected configuration store (e.g., HashiCorp Vault, AWS Parameter Store) rather than hard‑coding; update only after verifying the advisory. |
| Least‑privilege execution | Run the checker as a non‑root user that only needs read/execute access to the VMware binary. Avoid giving it unnecessary sudo rights. |
| Output sanitization | If you ingest the JSON into external systems, validate schema (e.g., using jsonschema or zod) to prevent injection attacks. |
| Timeouts & retries | The subprocess calls include a 5‑second timeout; in flaky CI environments you may want to add a retry loop with exponential back‑off. |
| Logging | Send both human‑readable and JSON logs to a central log aggregator (ELK, Splunk, Loki). Include host identifier, timestamp, and exit code. |
| Alerting | Hook the exit code (0 = patched, 1 = unpatched) into your alerting system (PagerDuty, Opsgenie, email). |
| Testing | Add unit tests that mock subprocess.run to cover: <br>• found / not found binaries <br>• version parsing success/failure <br>• baseline comparison edge cases. |
| Documentation | Keep a README.md in the repo that mirrors this guide, with clear upgrade steps when VMware releases a new patch. |
| Container compatibility | If you ship the checker as a Docker image, ensure the VMware binary is bind‑mounted from the host (you cannot redistribute VMware binaries). Example: <br>docker run --rm -v /usr/bin/vmware:/usr/bin/vmware:ro -e VMWARE_MIN_SAFE=17.5.1 myorg/vmware-patch-checker |
| Version‑bump process | When VMware publishes a new patched version:<br>1. Update DEFAULT_MIN_SAFE (or the value in your secret store).<br>2. Bump the minor version of the checker itself (e.g., v1.2.0 → v1.3.0).<br>3. Run the checker against a known‑good host to confirm it returns PASS. |
| Backup plan | Keep a copy of the previous checker version in case a false positive triggers an unwanted rollback. |
| Legal / licensing | The checker itself is MIT‑licensed, but it calls VMware binaries—ensure your organization has the appropriate license to run Workstation/Fusion in the target environment. |
You now have:
src/check_vmware.py)src/index.ts / src/index.js)Copy the code snippets into your repo, adjust the VMWARE_MIN_SAFE to match the latest VMware security advisory, and integrate the checker into your CI/CD pipelines, monitoring agents, or pre‑deployment validation scripts.
Stay safe, and keep those VMware hosts patched! 🚀
ICARAX Labs – Turning security advisories into actionable code.
Source: Security Week AI
Follow ICARAX for more AI insights and tutorials.
