From c8df7566b52dc6a35abbbaad7693177a855dfab7 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 25 Nov 2025 14:22:31 +0100 Subject: [PATCH] Remove ora dependency --- package-lock.json | 226 +----------------- packages/safe-chain/package.json | 7 +- .../src/environment/userInteraction.js | 57 ----- packages/safe-chain/src/scanning/index.js | 41 +--- .../src/scanning/index.scanCommand.spec.js | 112 --------- 5 files changed, 17 insertions(+), 426 deletions(-) diff --git a/package-lock.json b/package-lock.json index 94b6921..73ed31e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -659,33 +659,6 @@ "node": ">=18" } }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -909,18 +882,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1128,30 +1089,6 @@ "node": ">=8" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1188,34 +1125,6 @@ ], "license": "MIT" }, - "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -1274,18 +1183,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -1515,79 +1412,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ora/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "license": "MIT" - }, - "node_modules/ora/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/oxlint": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.22.0.tgz", @@ -1687,37 +1511,6 @@ "node": ">=10" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -1835,18 +1628,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -2096,14 +1877,13 @@ "version": "1.0.0", "license": "AGPL-3.0-or-later", "dependencies": { - "certifi": "^14.5.15", + "certifi": "14.5.15", "chalk": "5.4.1", "https-proxy-agent": "7.0.6", "ini": "^6.0.0", "make-fetch-happen": "14.0.3", "node-forge": "1.3.1", "npm-registry-fetch": "18.0.2", - "ora": "8.2.0", "semver": "7.7.2" }, "bin": { @@ -2113,10 +1893,10 @@ "aikido-npx": "bin/aikido-npx.js", "aikido-pip": "bin/aikido-pip.js", "aikido-pip3": "bin/aikido-pip3.js", - "aikido-python": "bin/aikido-python.js", - "aikido-python3": "bin/aikido-python3.js", "aikido-pnpm": "bin/aikido-pnpm.js", "aikido-pnpx": "bin/aikido-pnpx.js", + "aikido-python": "bin/aikido-python.js", + "aikido-python3": "bin/aikido-python3.js", "aikido-yarn": "bin/aikido-yarn.js", "safe-chain": "bin/safe-chain.js" }, diff --git a/packages/safe-chain/package.json b/packages/safe-chain/package.json index 186d810..8bdad1f 100644 --- a/packages/safe-chain/package.json +++ b/packages/safe-chain/package.json @@ -35,23 +35,22 @@ "license": "AGPL-3.0-or-later", "description": "The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [npx](https://github.com/npm/cli/blob/latest/docs/content/commands/npx.md), [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), [pnpx](https://pnpm.io/cli/dlx), [bun](https://bun.sh/), and [bunx](https://bun.sh/docs/cli/bunx) to provide extra checks before installing new packages. This tool will detect when a package contains malware and prompt you to exit, preventing npm, npx, yarn, pnpm, pnpx, bun, or bunx from downloading or running the malware.", "dependencies": { - "certifi": "^14.5.15", + "certifi": "14.5.15", "chalk": "5.4.1", "https-proxy-agent": "7.0.6", - "ini": "^6.0.0", + "ini": "6.0.0", "make-fetch-happen": "14.0.3", "node-forge": "1.3.1", "npm-registry-fetch": "18.0.2", - "ora": "8.2.0", "semver": "7.7.2" }, "devDependencies": { "@types/ini": "^4.1.1", "@types/make-fetch-happen": "^10.0.4", "@types/node": "^18.19.130", + "@types/node-forge": "^1.3.14", "@types/npm-registry-fetch": "^8.0.9", "@types/semver": "^7.7.1", - "@types/node-forge": "^1.3.14", "typescript": "^5.9.3" }, "main": "src/main.js", diff --git a/packages/safe-chain/src/environment/userInteraction.js b/packages/safe-chain/src/environment/userInteraction.js index 3222874..9115b58 100644 --- a/packages/safe-chain/src/environment/userInteraction.js +++ b/packages/safe-chain/src/environment/userInteraction.js @@ -1,6 +1,5 @@ // oxlint-disable no-console import chalk from "chalk"; -import ora from "ora"; import { isCi } from "./environment.js"; import { getLoggingLevel, @@ -98,61 +97,6 @@ function writeOrBuffer(messageFunction) { } } -/** - * @typedef {Object} Spinner - * @property {(message: string) => void} succeed - * @property {(message: string) => void} fail - * @property {() => void} stop - * @property {(message: string) => void} setText - */ - -/** - * @param {string} message - * - * @returns {Spinner} - */ -function startProcess(message) { - if (isSilentMode()) { - return { - succeed: () => {}, - fail: () => {}, - stop: () => {}, - setText: () => {}, - }; - } - - if (isCi()) { - return { - succeed: (message) => { - writeInformation(message); - }, - fail: (message) => { - writeError(message); - }, - stop: () => {}, - setText: (message) => { - writeInformation(message); - }, - }; - } else { - const spinner = ora(message).start(); - return { - succeed: (message) => { - spinner.succeed(message); - }, - fail: (message) => { - spinner.fail(message); - }, - stop: () => { - spinner.stop(); - }, - setText: (message) => { - spinner.text = message; - }, - }; - } -} - function startBufferingLogs() { state.bufferOutput = true; state.bufferedMessages = []; @@ -173,7 +117,6 @@ export const ui = { writeError, writeExitWithoutInstallingMaliciousPackages, emptyLine, - startProcess, startBufferingLogs, writeBufferedLogsAndStopBuffering, }; diff --git a/packages/safe-chain/src/scanning/index.js b/packages/safe-chain/src/scanning/index.js index 44ff57c..abfc420 100644 --- a/packages/safe-chain/src/scanning/index.js +++ b/packages/safe-chain/src/scanning/index.js @@ -29,36 +29,19 @@ export async function scanCommand(args) { } let timedOut = false; - - const spinner = ui.startProcess( - "Safe-chain: Scanning for malicious packages..." - ); /** @type {import("./audit/index.js").AuditResult | undefined} */ let audit; await Promise.race([ (async () => { - try { - const packageManager = getPackageManager(); - const changes = await packageManager.getDependencyUpdatesForCommand( - args - ); + const packageManager = getPackageManager(); + const changes = await packageManager.getDependencyUpdatesForCommand(args); - if (timedOut) { - return; - } - - if (changes.length > 0) { - spinner.setText( - `Safe-chain: Scanning ${changes.length} package(s)...` - ); - } - - audit = await auditChanges(changes); - } catch (/** @type any */ error) { - spinner.fail(`Safe-chain: Error while scanning.`); - throw error; + if (timedOut) { + return; } + + audit = await auditChanges(changes); })(), setTimeout(getScanTimeout()).then(() => { timedOut = true; @@ -66,15 +49,13 @@ export async function scanCommand(args) { ]); if (timedOut) { - spinner.fail("Safe-chain: Timeout exceeded while scanning."); throw new Error("Timeout exceeded while scanning npm install command."); } if (!audit || audit.isAllowed) { - spinner.stop(); return 0; } else { - printMaliciousChanges(audit.disallowedChanges, spinner); + printMaliciousChanges(audit.disallowedChanges); onMalwareFound(); return 1; } @@ -82,12 +63,12 @@ export async function scanCommand(args) { /** * @param {import("./audit/index.js").PackageChange[]} changes - * @param spinner {import("../environment/userInteraction.js").Spinner} - * * @return {void} */ -function printMaliciousChanges(changes, spinner) { - spinner.fail("Safe-chain: " + chalk.bold("Malicious changes detected:")); +function printMaliciousChanges(changes) { + ui.writeInformation( + chalk.red("✖") + " Safe-chain: " + chalk.bold("Malicious changes detected:") + ); for (const change of changes) { ui.writeInformation(` - ${change.name}@${change.version}`); diff --git a/packages/safe-chain/src/scanning/index.scanCommand.spec.js b/packages/safe-chain/src/scanning/index.scanCommand.spec.js index c47555f..944cf11 100644 --- a/packages/safe-chain/src/scanning/index.scanCommand.spec.js +++ b/packages/safe-chain/src/scanning/index.scanCommand.spec.js @@ -5,12 +5,6 @@ import { setTimeout } from "node:timers/promises"; describe("scanCommand", async () => { const getScanTimeoutMock = mock.fn(() => 1000); const mockGetDependencyUpdatesForCommand = mock.fn(); - const mockStartProcess = mock.fn(() => ({ - setText: () => {}, - succeed: () => {}, - fail: () => {}, - stop: () => {}, - })); // import { getPackageManager } from "../packagemanager/currentPackageManager.js"; mock.module("../packagemanager/currentPackageManager.js", { @@ -36,7 +30,6 @@ describe("scanCommand", async () => { mock.module("../environment/userInteraction.js", { namedExports: { ui: { - startProcess: mockStartProcess, writeError: () => {}, writeInformation: () => {}, writeWarning: () => {}, @@ -75,51 +68,20 @@ describe("scanCommand", async () => { const { scanCommand } = await import("./index.js"); it("should succeed when there are no changes", async () => { - let progressWasStopped = false; - mockStartProcess.mock.mockImplementationOnce(() => ({ - setText: () => {}, - succeed: () => {}, - fail: () => {}, - stop: () => { - progressWasStopped = true; - }, - })); mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => []); await scanCommand(["install", "lodash"]); - - assert.equal(progressWasStopped, true); }); it("should succeed when changes are not malicious", async () => { - let progressWasStopped = false; - mockStartProcess.mock.mockImplementationOnce(() => ({ - setText: () => {}, - succeed: () => {}, - fail: () => {}, - stop: () => { - progressWasStopped = true; - }, - })); mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ { name: "lodash", version: "4.17.21" }, ]); await scanCommand(["install", "lodash"]); - - assert.equal(progressWasStopped, true); }); it("should throw an error when timing out", async () => { - let failureMessageWasSet = false; - mockStartProcess.mock.mockImplementationOnce(() => ({ - setText: () => {}, - succeed: () => {}, - fail: () => { - failureMessageWasSet = true; - }, - stop: () => {}, - })); getScanTimeoutMock.mock.mockImplementationOnce(() => 100); mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => { await setTimeout(150); @@ -127,83 +89,9 @@ describe("scanCommand", async () => { }); await assert.rejects(scanCommand(["install", "lodash"])); - - assert.equal(failureMessageWasSet, true); }); it("should fail and return 1 malicious changes are detected", async () => { - let failureMessageWasSet = false; - mockStartProcess.mock.mockImplementationOnce(() => ({ - setText: () => {}, - succeed: () => {}, - fail: () => { - failureMessageWasSet = true; - }, - stop: () => {}, - })); - mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ - { name: "malicious", version: "1.0.0" }, - ]); - - const result = await scanCommand(["install", "malicious"]); - - assert.equal(failureMessageWasSet, true); - assert.equal(result, 1); - }); - - it("should not report a timeout when the user takes a long time to respond (it should not affect the timeout)", async () => { - let failureMessages = []; - mockStartProcess.mock.mockImplementationOnce(() => ({ - setText: () => {}, - succeed: () => {}, - fail: (message) => { - failureMessages.push(message); - }, - stop: () => {}, - })); - getScanTimeoutMock.mock.mockImplementationOnce(() => 100); - mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => { - return [{ name: "malicious", version: "4.17.21" }]; - }); - - await scanCommand(["install", "malicious"]); - - assert.equal(failureMessages.length, 1); - const failureMessage = failureMessages[0]; - assert.equal(failureMessage.toLowerCase().includes("timeout"), false); - assert.equal(failureMessage.toLowerCase().includes("malicious"), true); - }); - - it("should exit immediately when malicious changes are detected in block mode", async () => { - let failureMessageWasSet = false; - - mockStartProcess.mock.mockImplementationOnce(() => ({ - setText: () => {}, - succeed: () => {}, - fail: () => { - failureMessageWasSet = true; - }, - stop: () => {}, - })); - - mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ - { name: "malicious", version: "1.0.0" }, - ]); - - const result = await scanCommand(["install", "malicious"]); - - assert.equal(failureMessageWasSet, true); - assert.equal(result, 1); - }); - - it("should exit immediately when malicious changes are detected in block mode without prompting", async () => { - mockStartProcess.mock.mockImplementationOnce(() => ({ - setText: () => {}, - succeed: () => {}, - fail: () => {}, - stop: () => {}, - })); - mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ { name: "malicious", version: "1.0.0" }, ]);