diff --git a/packages/safe-chain/bin/aikido-bun.js b/packages/safe-chain/bin/aikido-bun.js index 2467512..01e3972 100755 --- a/packages/safe-chain/bin/aikido-bun.js +++ b/packages/safe-chain/bin/aikido-bun.js @@ -7,5 +7,4 @@ const packageManagerName = "bun"; initializePackageManager(packageManagerName); var exitCode = await main(process.argv.slice(2)); -// @ts-expect-error scanCommand can return an empty array in main process.exit(exitCode); diff --git a/packages/safe-chain/bin/aikido-bunx.js b/packages/safe-chain/bin/aikido-bunx.js index 6a84ff8..fb378e5 100755 --- a/packages/safe-chain/bin/aikido-bunx.js +++ b/packages/safe-chain/bin/aikido-bunx.js @@ -7,5 +7,4 @@ const packageManagerName = "bunx"; initializePackageManager(packageManagerName); var exitCode = await main(process.argv.slice(2)); -// @ts-expect-error scanCommand can return an empty array in main process.exit(exitCode); diff --git a/packages/safe-chain/bin/aikido-npm.js b/packages/safe-chain/bin/aikido-npm.js index 4a82c34..0e9f302 100755 --- a/packages/safe-chain/bin/aikido-npm.js +++ b/packages/safe-chain/bin/aikido-npm.js @@ -7,5 +7,4 @@ const packageManagerName = "npm"; initializePackageManager(packageManagerName); var exitCode = await main(process.argv.slice(2)); -// @ts-expect-error scanCommand can return an empty array in main process.exit(exitCode); diff --git a/packages/safe-chain/bin/aikido-npx.js b/packages/safe-chain/bin/aikido-npx.js index 40118d4..d3dfdd6 100755 --- a/packages/safe-chain/bin/aikido-npx.js +++ b/packages/safe-chain/bin/aikido-npx.js @@ -7,5 +7,4 @@ const packageManagerName = "npx"; initializePackageManager(packageManagerName); var exitCode = await main(process.argv.slice(2)); -// @ts-expect-error scanCommand can return an empty array in main process.exit(exitCode); diff --git a/packages/safe-chain/bin/aikido-pnpm.js b/packages/safe-chain/bin/aikido-pnpm.js index e495568..0a06217 100755 --- a/packages/safe-chain/bin/aikido-pnpm.js +++ b/packages/safe-chain/bin/aikido-pnpm.js @@ -7,5 +7,4 @@ const packageManagerName = "pnpm"; initializePackageManager(packageManagerName); var exitCode = await main(process.argv.slice(2)); -// @ts-expect-error scanCommand can return an empty array in main process.exit(exitCode); diff --git a/packages/safe-chain/bin/aikido-pnpx.js b/packages/safe-chain/bin/aikido-pnpx.js index 75f093d..cdb6504 100755 --- a/packages/safe-chain/bin/aikido-pnpx.js +++ b/packages/safe-chain/bin/aikido-pnpx.js @@ -7,5 +7,4 @@ const packageManagerName = "pnpx"; initializePackageManager(packageManagerName); var exitCode = await main(process.argv.slice(2)); -// @ts-expect-error scanCommand can return an empty array in main process.exit(exitCode); diff --git a/packages/safe-chain/bin/aikido-yarn.js b/packages/safe-chain/bin/aikido-yarn.js index 3ca5b94..fd87606 100755 --- a/packages/safe-chain/bin/aikido-yarn.js +++ b/packages/safe-chain/bin/aikido-yarn.js @@ -7,5 +7,4 @@ const packageManagerName = "yarn"; initializePackageManager(packageManagerName); var exitCode = await main(process.argv.slice(2)); -// @ts-expect-error scanCommand can return an empty array in main process.exit(exitCode); diff --git a/packages/safe-chain/src/main.js b/packages/safe-chain/src/main.js index eea9257..3fba24f 100644 --- a/packages/safe-chain/src/main.js +++ b/packages/safe-chain/src/main.js @@ -9,7 +9,7 @@ import chalk from "chalk"; /** * @param {string[]} args - * @returns {Promise} + * @returns {Promise} */ export async function main(args) { process.on("SIGINT", handleProcessTermination); diff --git a/packages/safe-chain/src/packagemanager/bun/createBunPackageManager.js b/packages/safe-chain/src/packagemanager/bun/createBunPackageManager.js index 9716261..037a512 100644 --- a/packages/safe-chain/src/packagemanager/bun/createBunPackageManager.js +++ b/packages/safe-chain/src/packagemanager/bun/createBunPackageManager.js @@ -39,7 +39,6 @@ async function runBunCommand(command, args) { try { const result = await safeSpawn(command, args, { stdio: "inherit", - // @ts-expect-error values of process.env can be string | undefined env: mergeSafeChainProxyEnvironmentVariables(process.env), }); return { status: result.status }; diff --git a/packages/safe-chain/src/packagemanager/npm/runNpmCommand.js b/packages/safe-chain/src/packagemanager/npm/runNpmCommand.js index c068240..af57fad 100644 --- a/packages/safe-chain/src/packagemanager/npm/runNpmCommand.js +++ b/packages/safe-chain/src/packagemanager/npm/runNpmCommand.js @@ -11,7 +11,6 @@ export async function runNpm(args) { try { const result = await safeSpawn("npm", args, { stdio: "inherit", - // @ts-expect-error values of process.env can be string | undefined env: mergeSafeChainProxyEnvironmentVariables(process.env), }); return { status: result.status }; @@ -24,37 +23,3 @@ export async function runNpm(args) { } } } - -/** - * @param {string[]} args - * @returns {Promise<{status: number, output?: string}>} - */ -export async function dryRunNpmCommandAndOutput(args) { - try { - const result = await safeSpawn( - "npm", - [...args, "--ignore-scripts", "--dry-run"], - { - stdio: "pipe", - // @ts-expect-error values of process.env can be string | undefined - env: mergeSafeChainProxyEnvironmentVariables(process.env), - } - ); - return { - status: result.status, - output: result.status === 0 ? result.stdout : result.stderr, - }; - } catch (/** @type any */ error) { - if (error.status) { - const output = - error.stdout?.toString() ?? - error.stderr?.toString() ?? - error.message ?? - ""; - return { status: error.status, output }; - } else { - ui.writeError("Error executing command:", error.message); - return { status: 1 }; - } - } -} diff --git a/packages/safe-chain/src/packagemanager/npx/runNpxCommand.js b/packages/safe-chain/src/packagemanager/npx/runNpxCommand.js index 61adcaa..2501b79 100644 --- a/packages/safe-chain/src/packagemanager/npx/runNpxCommand.js +++ b/packages/safe-chain/src/packagemanager/npx/runNpxCommand.js @@ -11,7 +11,6 @@ export async function runNpx(args) { try { const result = await safeSpawn("npx", args, { stdio: "inherit", - // @ts-expect-error values of process.env can be string | undefined env: mergeSafeChainProxyEnvironmentVariables(process.env), }); return { status: result.status }; diff --git a/packages/safe-chain/src/packagemanager/pnpm/runPnpmCommand.js b/packages/safe-chain/src/packagemanager/pnpm/runPnpmCommand.js index db4ffa9..d958fb8 100644 --- a/packages/safe-chain/src/packagemanager/pnpm/runPnpmCommand.js +++ b/packages/safe-chain/src/packagemanager/pnpm/runPnpmCommand.js @@ -13,13 +13,11 @@ export async function runPnpmCommand(args, toolName = "pnpm") { if (toolName === "pnpm") { result = await safeSpawn("pnpm", args, { stdio: "inherit", - // @ts-expect-error values of process.env can be string | undefined env: mergeSafeChainProxyEnvironmentVariables(process.env), }); } else if (toolName === "pnpx") { result = await safeSpawn("pnpx", args, { stdio: "inherit", - // @ts-expect-error values of process.env can be string | undefined env: mergeSafeChainProxyEnvironmentVariables(process.env), }); } else { diff --git a/packages/safe-chain/src/packagemanager/yarn/runYarnCommand.js b/packages/safe-chain/src/packagemanager/yarn/runYarnCommand.js index 1ba0f5c..04650f7 100644 --- a/packages/safe-chain/src/packagemanager/yarn/runYarnCommand.js +++ b/packages/safe-chain/src/packagemanager/yarn/runYarnCommand.js @@ -9,7 +9,6 @@ import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/reg */ export async function runYarnCommand(args) { try { - // @ts-expect-error values of process.env can be string | undefined const env = mergeSafeChainProxyEnvironmentVariables(process.env); await fixYarnProxyEnvironmentVariables(env); diff --git a/packages/safe-chain/src/registryProxy/mitmRequestHandler.js b/packages/safe-chain/src/registryProxy/mitmRequestHandler.js index c990a98..6f7b20e 100644 --- a/packages/safe-chain/src/registryProxy/mitmRequestHandler.js +++ b/packages/safe-chain/src/registryProxy/mitmRequestHandler.js @@ -5,7 +5,7 @@ import { ui } from "../environment/userInteraction.js"; /** * @param {import("http").IncomingMessage} req - * @param {import("net").Socket} clientSocket + * @param {import("http").ServerResponse} clientSocket * @param {(target: string) => Promise} isAllowed */ export function mitmConnect(req, clientSocket, isAllowed) { @@ -25,7 +25,6 @@ export function mitmConnect(req, clientSocket, isAllowed) { server.on("error", (err) => { ui.writeError(`Safe-chain: HTTPS server error: ${err.message}`); - // @ts-expect-error Property 'headersSent' does not exist on type 'Socket' if (!clientSocket.headersSent) { clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n"); } else if (clientSocket.writable) { @@ -55,7 +54,13 @@ function createHttpsServer(hostname, isAllowed) { * @returns {Promise} */ async function handleRequest(req, res) { - // @ts-expect-error req.url might be undefined + if (!req.url) { + ui.writeError("Safe-chain: Request missing URL"); + res.writeHead(400, "Bad Request"); + res.end("Bad Request: Missing URL"); + return; + } + const pathAndQuery = getRequestPathAndQuery(req.url); const targetUrl = `https://${hostname}${pathAndQuery}`; @@ -163,7 +168,13 @@ function createProxyRequest(hostname, req, res) { } }); - // @ts-expect-error statusCode might be undefined + if (!proxyRes.statusCode) { + ui.writeError("Safe-chain: Proxy response missing status code"); + res.writeHead(500); + res.end("Internal Server Error"); + return; + } + res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); }); diff --git a/packages/safe-chain/src/registryProxy/plainHttpProxy.js b/packages/safe-chain/src/registryProxy/plainHttpProxy.js index 16b305a..75b9d77 100644 --- a/packages/safe-chain/src/registryProxy/plainHttpProxy.js +++ b/packages/safe-chain/src/registryProxy/plainHttpProxy.js @@ -1,5 +1,6 @@ import * as http from "http"; import * as https from "https"; +import { ui } from "../environment/userInteraction.js"; /** * @param {import("http").IncomingMessage} req @@ -8,7 +9,13 @@ import * as https from "https"; * @returns {void} */ export function handleHttpProxyRequest(req, res) { - // @ts-expect-error req.url might be undefined + if (!req.url) { + ui.writeError("Safe-chain: Request missing URL"); + res.writeHead(400, "Bad Request"); + res.end("Bad Request: Missing URL"); + return; + } + const url = new URL(req.url); // The protocol for the plainHttpProxy should usually only be http: @@ -27,11 +34,16 @@ export function handleHttpProxyRequest(req, res) { const proxyRequest = protocol .request( - // @ts-expect-error req.url might be undefined req.url, { method: req.method, headers: req.headers }, (proxyRes) => { - // @ts-expect-error statusCode might be undefined + if (!proxyRes.statusCode) { + ui.writeError("Safe-chain: Proxy response missing status code"); + res.writeHead(500); + res.end("Internal Server Error"); + return; + } + res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); diff --git a/packages/safe-chain/src/registryProxy/registryProxy.js b/packages/safe-chain/src/registryProxy/registryProxy.js index ed103b2..9e7bb7b 100644 --- a/packages/safe-chain/src/registryProxy/registryProxy.js +++ b/packages/safe-chain/src/registryProxy/registryProxy.js @@ -43,7 +43,7 @@ function getSafeChainProxyEnvironmentVariables() { } /** - * @param {Record} env + * @param {Record} env * * @returns {Record} */ @@ -56,7 +56,7 @@ export function mergeSafeChainProxyEnvironmentVariables(env) { // So we only copy the variable if it's not already set in a different case const upperKey = key.toUpperCase(); - if (!proxyEnv[upperKey]) { + if (!proxyEnv[upperKey] && env[key]) { proxyEnv[key] = env[key]; } } @@ -122,7 +122,7 @@ function stopServer(server) { /** * @param {import("http").IncomingMessage} req - * @param {import("net").Socket} clientSocket + * @param {import("http").ServerResponse} clientSocket * @param {Buffer} head * * @returns {void} @@ -130,9 +130,9 @@ function stopServer(server) { function handleConnect(req, clientSocket, head) { // CONNECT method is used for HTTPS requests // It establishes a tunnel to the server identified by the request URL + const url = req.url; - // @ts-expect-error req.url might be undefined - if (knownRegistries.some((reg) => req.url.includes(reg))) { + if (url && knownRegistries.some((reg) => url.includes(reg))) { // For npm and yarn registries, we want to intercept and inspect the traffic // so we can block packages with malware mitmConnect(req, clientSocket, isAllowedUrl); diff --git a/packages/safe-chain/src/registryProxy/tunnelRequestHandler.js b/packages/safe-chain/src/registryProxy/tunnelRequestHandler.js index 452ef15..4b756d7 100644 --- a/packages/safe-chain/src/registryProxy/tunnelRequestHandler.js +++ b/packages/safe-chain/src/registryProxy/tunnelRequestHandler.js @@ -3,7 +3,7 @@ import { ui } from "../environment/userInteraction.js"; /** * @param {import("http").IncomingMessage} req - * @param {import("net").Socket} clientSocket + * @param {import("http").ServerResponse} clientSocket * @param {Buffer} head * * @returns {void} @@ -30,7 +30,7 @@ export function tunnelRequest(req, clientSocket, head) { /** * @param {import("http").IncomingMessage} req - * @param {import("net").Socket} clientSocket + * @param {import("http").ServerResponse} clientSocket * @param {Buffer} head * * @returns {void} @@ -38,13 +38,16 @@ export function tunnelRequest(req, clientSocket, head) { function tunnelRequestToDestination(req, clientSocket, head) { const { port, hostname } = new URL(`http://${req.url}`); - // @ts-expect-error port from URL is a string but net.connect accepts number - const serverSocket = net.connect(port || 443, hostname, () => { - clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); - serverSocket.write(head); - serverSocket.pipe(clientSocket); - clientSocket.pipe(serverSocket); - }); + const serverSocket = net.connect( + Number.parseInt(port) || 443, + hostname, + () => { + clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); + serverSocket.write(head); + serverSocket.pipe(clientSocket); + clientSocket.pipe(serverSocket); + } + ); clientSocket.on("error", () => { // This can happen if the client TCP socket sends RST instead of FIN. @@ -66,7 +69,7 @@ function tunnelRequestToDestination(req, clientSocket, head) { /** * @param {import("http").IncomingMessage} req - * @param {import("net").Socket} clientSocket + * @param {import("http").ServerResponse} clientSocket * @param {Buffer} head * @param {string} proxyUrl */ @@ -75,10 +78,9 @@ function tunnelRequestViaProxy(req, clientSocket, head, proxyUrl) { const proxy = new URL(proxyUrl); // Connect to proxy server - // @ts-expect-error net.connect wants port as number but proxy.port is string const proxySocket = net.connect({ host: proxy.hostname, - port: proxy.port, + port: Number.parseInt(proxy.port) || 80, }); proxySocket.on("connect", () => { diff --git a/packages/safe-chain/src/scanning/index.js b/packages/safe-chain/src/scanning/index.js index d8e817e..44ff57c 100644 --- a/packages/safe-chain/src/scanning/index.js +++ b/packages/safe-chain/src/scanning/index.js @@ -21,11 +21,11 @@ export function shouldScanCommand(args) { /** * @param {string[]} args * - * @returns {Promise} + * @returns {Promise} */ export async function scanCommand(args) { if (!shouldScanCommand(args)) { - return []; + return 0; } let timedOut = false; diff --git a/packages/safe-chain/src/scanning/malwareDatabase.js b/packages/safe-chain/src/scanning/malwareDatabase.js index b54846e..03c7081 100644 --- a/packages/safe-chain/src/scanning/malwareDatabase.js +++ b/packages/safe-chain/src/scanning/malwareDatabase.js @@ -71,8 +71,11 @@ async function getMalwareDatabase() { } const { malwareDatabase, version } = await fetchMalwareDatabase(); - // @ts-expect-error version can be undefined - writeDatabaseToLocalCache(malwareDatabase, version); + + if (version) { + // Only cache the malware database when we have a version. + writeDatabaseToLocalCache(malwareDatabase, version); + } return malwareDatabase; } catch (/** @type any */ error) { diff --git a/packages/safe-chain/src/utils/safeSpawn.js b/packages/safe-chain/src/utils/safeSpawn.js index 489d070..e17bdb5 100644 --- a/packages/safe-chain/src/utils/safeSpawn.js +++ b/packages/safe-chain/src/utils/safeSpawn.js @@ -67,8 +67,6 @@ function resolveCommandPath(command) { // Use 'command -v' to find the full path const fullPath = execSync(`command -v ${command}`, { encoding: "utf8", - // @ts-expect-error shell is a string option - shell: true, }).trim(); if (!fullPath) { @@ -120,8 +118,12 @@ export async function safeSpawn(command, args, options = {}) { }); child.on("close", (code) => { + // Code is null if it terminated by a signal. This should never + // happen in our code. If this happens, return 1 error code. + + code = code ?? 1; + resolve({ - // @ts-expect-error code can be null status: code, stdout: stdout, stderr: stderr,