Implement a proxy blocking tarball requests for packages containing malware.

This commit is contained in:
Sander Declerck 2025-09-30 13:52:21 +02:00
parent 04cb001006
commit e2afcb16e3
No known key found for this signature in database
16 changed files with 633 additions and 33 deletions

View file

@ -8,7 +8,8 @@ export function dryRunScanner(scannerOptions) {
shouldScan: (args) => shouldScanDependencies(scannerOptions, args),
};
}
function scanDependencies(scannerOptions, args) {
async function scanDependencies(scannerOptions, args) {
let dryRunArgs = args;
if (scannerOptions?.dryRunCommand) {
@ -31,8 +32,8 @@ function shouldScanDependencies(scannerOptions, args) {
return true;
}
function checkChangesWithDryRun(args) {
const dryRunOutput = dryRunNpmCommandAndOutput(args);
async function checkChangesWithDryRun(args) {
const dryRunOutput = await dryRunNpmCommandAndOutput(args);
// Dry-run can return a non-zero status code in some cases
// e.g., when running "npm audit fix --dry-run", it returns exit code 1

View file

@ -36,7 +36,7 @@ describe("dryRunScanner", async () => {
}));
const scanner = dryRunScanner();
const result = scanner.scan(["audit", "fix"]);
const result = await scanner.scan(["audit", "fix"]);
// Should not throw an error for audit fix commands
assert.ok(Array.isArray(result));
@ -53,8 +53,8 @@ describe("dryRunScanner", async () => {
const scanner = dryRunScanner();
assert.throws(() => {
scanner.scan(["install", "lodash"]);
await assert.rejects(async () => {
await scanner.scan(["install", "lodash"]);
}, /Dry-run command failed with exit code 1/);
});
@ -67,7 +67,7 @@ describe("dryRunScanner", async () => {
}));
const scanner = dryRunScanner();
const result = scanner.scan(["install", "lodash"]);
const result = await scanner.scan(["install", "lodash"]);
assert.ok(Array.isArray(result));
assert.equal(mockWriteError.mock.callCount(), 0);
@ -83,8 +83,8 @@ describe("dryRunScanner", async () => {
const scanner = dryRunScanner();
assert.throws(() => {
scanner.scan(["audit", "fix"]);
await assert.rejects(async () => {
await scanner.scan(["audit", "fix"]);
}, /Dry-run command failed with exit code 1/);
});
});
@ -99,7 +99,7 @@ describe("dryRunScanner", async () => {
}));
const scanner = dryRunScanner({ dryRunCommand: "install" });
scanner.scan(["install-test", "lodash"]);
await scanner.scan(["install-test", "lodash"]);
// Should call with "install" instead of "install-test"
assert.equal(mockDryRunNpmCommandAndOutput.mock.callCount(), 1);

View file

@ -1,10 +1,14 @@
import { execSync } from "child_process";
import { ui } from "../../environment/userInteraction.js";
import { safeSpawn } from "../../utils/safeSpawn.js";
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
export function runNpm(args) {
export async function runNpm(args) {
try {
const npmCommand = `npm ${args.join(" ")}`;
execSync(npmCommand, { stdio: "inherit" });
const result = await safeSpawn("npm", args, {
stdio: "inherit",
env: mergeSafeChainProxyEnvironmentVariables(process.env),
});
return { status: result.status };
} catch (error) {
if (error.status) {
return { status: error.status };
@ -13,17 +17,29 @@ export function runNpm(args) {
return { status: 1 };
}
}
return { status: 0 };
}
export function dryRunNpmCommandAndOutput(args) {
export async function dryRunNpmCommandAndOutput(args) {
try {
const npmCommand = `npm ${args.join(" ")} --ignore-scripts --dry-run`;
const output = execSync(npmCommand, { stdio: "pipe" });
return { status: 0, output: output.toString() };
const result = await safeSpawn(
"npm",
[...args, "--ignore-scripts", "--dry-run"],
{
stdio: "pipe",
env: mergeSafeChainProxyEnvironmentVariables(process.env),
}
);
return {
status: result.status,
output: result.status === 0 ? result.stdout : result.stderr,
};
} catch (error) {
if (error.status) {
const output = error.stdout ? error.stdout.toString() : "";
const output =
error.stdout?.toString() ??
error.stderr?.toString() ??
error.message ??
"";
return { status: error.status, output };
} else {
ui.writeError("Error executing command:", error.message);