Last active
September 9, 2025 15:14
-
-
Save hungdoansy/f5c9b886094f38a57d8384e4bf48e10a to your computer and use it in GitHub Desktop.
A Node.js CLI utility for scanning pnpm projects (and workspaces) against a known list of vulnerable package versions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| /* | |
| * Scan pnpm workspace/project for known bad package versions. | |
| */ | |
| const fs = require("fs"); | |
| const path = require("path"); | |
| const { execFile } = require("child_process"); | |
| // 👇 paste your affected list here | |
| const AFFECTED_PACKAGES = [ | |
| ["backslash", "0.2.1"], | |
| ["chalk-template", "1.1.1"], | |
| ["supports-hyperlinks", "4.1.1"], | |
| ["has-ansi", "6.0.1"], | |
| ["simple-swizzle", "0.2.3"], | |
| ["color-string", "2.1.1"], | |
| ["error-ex", "1.3.3"], | |
| ["color-name", "2.0.1"], | |
| ["is-arrayish", "0.3.3"], | |
| ["slice-ansi", "7.1.1"], | |
| ["color-convert", "3.1.1"], | |
| ["wrap-ansi", "9.0.1"], | |
| ["ansi-regex", "6.2.1"], | |
| ["supports-color", "10.2.1"], | |
| ["strip-ansi", "7.1.1"], | |
| ["chalk", "5.6.1"], | |
| ["debug", "4.4.2"], | |
| ["ansi-styles", "6.2.2"], | |
| ] | |
| const stripAnsi = (s) => s.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, ""); | |
| const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | |
| function runPnpmWhy(name) { | |
| return new Promise((resolve) => { | |
| const inWorkspace = fs.existsSync(path.join(process.cwd(), "pnpm-workspace.yaml")); | |
| const args = []; | |
| if (inWorkspace) args.push("-r"); | |
| args.push("why", name); | |
| execFile("pnpm", args, { encoding: "utf8", maxBuffer: 20 * 1024 * 1024 }, (err, stdout, stderr) => { | |
| const output = stripAnsi((stdout || "") + (stderr || "")); | |
| resolve(output); | |
| }); | |
| }); | |
| } | |
| async function checkPackage(name, badVersions) { | |
| const output = await runPnpmWhy(name); | |
| if (!output || !output.includes(name)) { | |
| return { name, status: "not-installed" }; | |
| } | |
| const hits = []; | |
| for (const v of Array.isArray(badVersions) ? badVersions : [badVersions]) { | |
| const regex = new RegExp(`\\s*${esc(name)}\\s+${esc(v)}(\\s|$)`, "g"); | |
| let match; | |
| while ((match = regex.exec(output)) !== null) { | |
| hits.push(v); | |
| } | |
| } | |
| if (hits.length > 0) return { name, status: "vulnerable", hits }; | |
| return { name, status: "safe" }; | |
| } | |
| async function main() { | |
| const results = []; | |
| for (const [name, versions] of AFFECTED_PACKAGES) { | |
| results.push(await checkPackage(name, versions)); | |
| } | |
| console.log("\nScan results:\n"); | |
| for (const r of results) { | |
| if (r.status === "vulnerable") { | |
| console.log(`❌ ${r.name}: affected version(s) found → ${r.hits.join(", ")}`); | |
| } else if (r.status === "safe") { | |
| console.log(`✅ ${r.name}: installed, but at safe version(s)`); | |
| } else { | |
| console.log(`⚪ ${r.name}: not installed`); | |
| } | |
| } | |
| const vulnerable = results.filter((r) => r.status === "vulnerable"); | |
| console.log("\nSummary:"); | |
| console.log(` Vulnerable : ${vulnerable.length}`); | |
| console.log(` Safe : ${results.filter((r) => r.status === "safe").length}`); | |
| console.log(` Not found : ${results.filter((r) => r.status === "not-installed").length}`); | |
| process.exit(vulnerable.length > 0 ? 1 : 0); | |
| } | |
| main().catch((e) => { | |
| console.error(e); | |
| process.exit(2); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| /* | |
| * Scan pnpm workspace/project for known bad package versions. | |
| */ | |
| const fs = require("fs"); | |
| const path = require("path"); | |
| const { execFile } = require("child_process"); | |
| // 👇 paste your affected list here | |
| const AFFECTED_PACKAGES = [ | |
| ["backslash", "0.2.1"], | |
| ["chalk-template", "1.1.1"], | |
| ["supports-hyperlinks", "4.1.1"], | |
| ["has-ansi", "6.0.1"], | |
| ["simple-swizzle", "0.2.3"], | |
| ["color-string", "2.1.1"], | |
| ["error-ex", "1.3.3"], | |
| ["color-name", "2.0.1"], | |
| ["is-arrayish", "0.3.3"], | |
| ["slice-ansi", "7.1.1"], | |
| ["color-convert", "3.1.1"], | |
| ["wrap-ansi", "9.0.1"], | |
| ["ansi-regex", "6.2.1"], | |
| ["supports-color", "10.2.1"], | |
| ["strip-ansi", "7.1.1"], | |
| ["chalk", "5.6.1"], | |
| ["debug", "4.4.2"], | |
| ["ansi-styles", "6.2.2"], | |
| ] | |
| const stripAnsi = (s) => s.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, ""); | |
| const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | |
| function runYarnWhy(name) { | |
| return new Promise((resolve) => { | |
| const inWorkspace = fs.existsSync(path.join(process.cwd(), "yarn.lock")); | |
| const args = []; | |
| args.push("why", name); | |
| if (inWorkspace) args.push("-r"); | |
| execFile("yarn", args, { encoding: "utf8", maxBuffer: 20 * 1024 * 1024 }, (err, stdout, stderr) => { | |
| const output = stripAnsi((stdout || "") + (stderr || "")); | |
| resolve(output); | |
| }); | |
| }); | |
| } | |
| async function checkPackage(name, badVersions) { | |
| const output = await runYarnWhy(name); | |
| if (!output || !output.includes(name)) { | |
| return { name, status: "not-installed" }; | |
| } | |
| const hits = []; | |
| for (const v of Array.isArray(badVersions) ? badVersions : [badVersions]) { | |
| const regex = new RegExp(`${esc(name)}@${esc(v)}`, "g"); | |
| let match; | |
| while ((match = regex.exec(output)) !== null) { | |
| hits.push(v); | |
| } | |
| } | |
| if (hits.length > 0) return { name, status: "vulnerable", hits }; | |
| return { name, status: "safe" }; | |
| } | |
| async function main() { | |
| const results = []; | |
| for (const [name, versions] of AFFECTED_PACKAGES) { | |
| results.push(await checkPackage(name, versions)); | |
| } | |
| console.log("\nScan results:\n"); | |
| for (const r of results) { | |
| if (r.status === "vulnerable") { | |
| console.log(`❌ ${r.name}: affected version(s) found → ${r.hits.join(", ")}`); | |
| } else if (r.status === "safe") { | |
| console.log(`✅ ${r.name}: installed, but at safe version(s)`); | |
| } else { | |
| console.log(`⚪ ${r.name}: not installed`); | |
| } | |
| } | |
| const vulnerable = results.filter((r) => r.status === "vulnerable"); | |
| console.log("\nSummary:"); | |
| console.log(` Vulnerable : ${vulnerable.length}`); | |
| console.log(` Safe : ${results.filter((r) => r.status === "safe").length}`); | |
| console.log(` Not found : ${results.filter((r) => r.status === "not-installed").length}`); | |
| process.exit(vulnerable.length > 0 ? 1 : 0); | |
| } | |
| main().catch((e) => { | |
| console.error(e); | |
| process.exit(2); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment