From edf6a1694f93503b000b359f3e6b7d8aac662c83 Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Fri, 27 Mar 2026 10:35:41 -0700 Subject: [PATCH] Some cleanups --- packages/safe-chain/src/api/aikido.js | 4 +- packages/safe-chain/src/api/aikido.spec.js | 11 ++ .../interceptors/npm/npmInterceptor.js | 2 +- ...imum-package-age-request-block.e2e.spec.js | 161 ------------------ 4 files changed, 14 insertions(+), 164 deletions(-) delete mode 100644 test/e2e/minimum-package-age-request-block.e2e.spec.js diff --git a/packages/safe-chain/src/api/aikido.js b/packages/safe-chain/src/api/aikido.js index 5248e0f..0ceec21 100644 --- a/packages/safe-chain/src/api/aikido.js +++ b/packages/safe-chain/src/api/aikido.js @@ -12,8 +12,8 @@ const malwareDatabaseUrls = { }; const newPackagesListUrls = { - [ECOSYSTEM_JS]: "https://malware-list.aikido.dev/releases_npm.json", - [ECOSYSTEM_PY]: "https://malware-list.aikido.dev/releases_pypi.json", + [ECOSYSTEM_JS]: "https://malware-list.aikido.dev/releases/npm.json", + [ECOSYSTEM_PY]: "https://malware-list.aikido.dev/releases/pypi.json", }; const DEFAULT_FETCH_RETRY_ATTEMPTS = 4; diff --git a/packages/safe-chain/src/api/aikido.spec.js b/packages/safe-chain/src/api/aikido.spec.js index d70f7e2..0d3a964 100644 --- a/packages/safe-chain/src/api/aikido.spec.js +++ b/packages/safe-chain/src/api/aikido.spec.js @@ -156,6 +156,10 @@ describe("aikido API", async () => { const result = await fetchNewPackagesList(); assert.strictEqual(mockFetch.mock.calls.length, 1); + assert.strictEqual( + mockFetch.mock.calls[0].arguments[0], + "https://malware-list.aikido.dev/releases/npm.json" + ); assert.deepStrictEqual(result.newPackagesList, releases); assert.strictEqual(result.version, '"etag-new-packages"'); }); @@ -193,6 +197,13 @@ describe("aikido API", async () => { const result = await fetchNewPackagesListVersion(); assert.strictEqual(mockFetch.mock.calls.length, 1); + assert.strictEqual( + mockFetch.mock.calls[0].arguments[0], + "https://malware-list.aikido.dev/releases/npm.json" + ); + assert.deepStrictEqual(mockFetch.mock.calls[0].arguments[1], { + method: "HEAD", + }); assert.strictEqual(result, '"new-packages-etag"'); }); diff --git a/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.js b/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.js index b912977..c1310bd 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.js +++ b/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.js @@ -73,7 +73,7 @@ function buildNpmInterceptor(registry) { reqContext.blockMinimumAgeRequest( packageName, version, - `Forbidden - blocked by safe-chain minimum package age (${packageName}@${version})` + `Forbidden - blocked by safe-chain direct download minimum package age (${packageName}@${version})` ); } } diff --git a/test/e2e/minimum-package-age-request-block.e2e.spec.js b/test/e2e/minimum-package-age-request-block.e2e.spec.js deleted file mode 100644 index 5dd147c..0000000 --- a/test/e2e/minimum-package-age-request-block.e2e.spec.js +++ /dev/null @@ -1,161 +0,0 @@ -import { describe, it, before, beforeEach, afterEach } from "node:test"; -import { DockerTestContainer } from "./DockerTestContainer.js"; -import assert from "node:assert"; - -describe.skip( - "E2E: minimum package age direct request fallback", - () => { - let container; - - before(async () => { - DockerTestContainer.buildImage(); - }); - - beforeEach(async () => { - container = new DockerTestContainer(); - await container.start(); - - const installationShell = await container.openShell("zsh"); - await installationShell.runCommand("safe-chain setup-ci"); - await installationShell.runCommand( - "echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc" - ); - }); - - afterEach(async () => { - if (container) { - await container.stop(); - container = null; - } - }); - - it("blocks npm ci when a lockfile resolves to a recently released package", async () => { - const shell = await container.openShell("zsh"); - - await shell.runCommand( - "npm init -y && npm pkg set dependencies.axios=1.8.4" - ); - await shell.runCommand("npm install --package-lock-only"); - await shell.runCommand("rm -rf node_modules"); - await seedNewPackagesListCache(shell, [ - { - package_name: "axios", - version: "1.8.4", - released_on: unixHoursAgo(1), - scraped_on: unixHoursAgo(1), - }, - ]); - - const result = await shell.runCommand( - "npm ci --safe-chain-minimum-package-age-hours=168 --safe-chain-logging=verbose" - ); - - assert.ok( - result.output.includes( - "blocked 1 direct package download request(s) due to minimum package age" - ), - `Output did not include expected text. Output was:\n${result.output}` - ); - assert.ok( - result.output.includes("- axios@1.8.4"), - `Output did not include expected text. Output was:\n${result.output}` - ); - assert.ok( - result.output.includes( - "Exiting without installing packages blocked by the direct download minimum package age check." - ), - `Output did not include expected text. Output was:\n${result.output}` - ); - }); - - it("blocks yarn frozen-lockfile installs when the cached recent releases list marks the tarball as too young", async () => { - const shell = await container.openShell("zsh"); - - await shell.runCommand( - "npm init -y && npm pkg set dependencies.axios=1.8.4" - ); - await shell.runCommand("yarn install"); - await shell.runCommand("rm -rf node_modules"); - await seedNewPackagesListCache(shell, [ - { - package_name: "axios", - version: "1.8.4", - released_on: unixHoursAgo(1), - scraped_on: unixHoursAgo(1), - }, - ]); - - const result = await shell.runCommand( - "yarn install --frozen-lockfile --safe-chain-minimum-package-age-hours=168 --safe-chain-logging=verbose" - ); - - assert.ok( - result.output.includes( - "blocked 1 direct package download request(s) due to minimum package age" - ), - `Output did not include expected text. Output was:\n${result.output}` - ); - assert.ok( - result.output.includes("- axios@1.8.4"), - `Output did not include expected text. Output was:\n${result.output}` - ); - }); - - it("allows the same lockfile-driven install when minimum age checks are skipped", async () => { - const shell = await container.openShell("zsh"); - - await shell.runCommand( - "npm init -y && npm pkg set dependencies.axios=1.8.4" - ); - await shell.runCommand("npm install --package-lock-only"); - await shell.runCommand("rm -rf node_modules"); - await seedNewPackagesListCache(shell, [ - { - package_name: "axios", - version: "1.8.4", - released_on: unixHoursAgo(1), - scraped_on: unixHoursAgo(1), - }, - ]); - - const result = await shell.runCommand( - "npm ci --safe-chain-minimum-package-age-hours=168 --safe-chain-skip-minimum-package-age --safe-chain-logging=verbose" - ); - - assert.ok( - result.output.includes("no malware found."), - `Output did not include expected text. Output was:\n${result.output}` - ); - assert.ok( - !result.output.includes( - "direct package download request(s) due to minimum package age" - ), - `Output unexpectedly contained a direct request block. Output was:\n${result.output}` - ); - }); - } -); - -/** - * @param {{ runCommand: (command: string) => Promise<{output: string}> }} shell - * @param {Array<{package_name: string, version: string, released_on: number, scraped_on: number}>} entries - */ -async function seedNewPackagesListCache(shell, entries) { - const payload = JSON.stringify(entries).replace(/"/g, '\\"'); - - await shell.runCommand("mkdir -p ~/.safe-chain"); - await shell.runCommand( - `printf "%s" "${payload}" > ~/.safe-chain/newPackagesList_js.json` - ); - await shell.runCommand( - 'printf "%s" "test-etag" > ~/.safe-chain/newPackagesList_version_js.txt' - ); -} - -/** - * @param {number} hours - * @returns {number} - */ -function unixHoursAgo(hours) { - return Math.floor((Date.now() - hours * 3600 * 1000) / 1000); -}