mask credentials in malwarelist urls and prevent log poisoning when using custom urls

This commit is contained in:
123Haynes 2026-04-03 07:02:46 +00:00
parent da9e3d475e
commit 8ee123c321
2 changed files with 75 additions and 4 deletions

View file

@ -200,6 +200,25 @@ export function getMinimumPackageAgeExclusions() {
return [...new Set(allExclusions)];
}
/**
* Masks credentials in a URL for logging purposes.
* @param {string} url
* @returns {string}
*/
function maskCredentialsInUrl(url) {
if (!url || typeof url !== "string") {
return url;
}
// Mask credentials https://username:password@abc.example or https://username@abc.example by replacing with https://***@abc.example
let masked = url.replace(/:\/\/([^@]+)@/, "://***@");
// Remove control characters to prevent log poisoning
masked = masked.replace(/[\x00-\x1F\x7F]/g, "");
return masked;
}
/**
* Gets the malware list base URL with priority: CLI argument > environment variable > config file > default
* @returns {string}
@ -209,7 +228,7 @@ export function getMalwareListBaseUrl() {
const cliValue = cliArguments.getMalwareListBaseUrl();
if (cliValue) {
const url = removeTrailingSlashes(cliValue);
ui.writeInformation(`Fetching malware lists from ${url} as defined by CLI argument --safe-chain-malware-list-base-url`);
ui.writeInformation(`Fetching malware lists from ${maskCredentialsInUrl(url)} as defined by CLI argument --safe-chain-malware-list-base-url`);
return url;
}
@ -217,7 +236,7 @@ export function getMalwareListBaseUrl() {
const envValue = environmentVariables.getMalwareListBaseUrl();
if (envValue) {
const url = removeTrailingSlashes(envValue);
ui.writeInformation(`Fetching malware lists from ${url} as defined by environment variable SAFE_CHAIN_MALWARE_LIST_BASE_URL`);
ui.writeInformation(`Fetching malware lists from ${maskCredentialsInUrl(url)} as defined by environment variable SAFE_CHAIN_MALWARE_LIST_BASE_URL`);
return url;
}
@ -225,13 +244,13 @@ export function getMalwareListBaseUrl() {
const configValue = configFile.getMalwareListBaseUrl();
if (configValue) {
const url = removeTrailingSlashes(configValue);
ui.writeInformation(`Fetching malware lists from ${url} as defined by config file (malwareListBaseUrl)`);
ui.writeInformation(`Fetching malware lists from ${maskCredentialsInUrl(url)} as defined by config file (malwareListBaseUrl)`);
return url;
}
// Default
const url = removeTrailingSlashes("https://malware-list.aikido.dev");
ui.writeInformation(`Fetching malware lists from ${url} (default)`);
ui.writeInformation(`Fetching malware lists from ${maskCredentialsInUrl(url)} (default)`);
return url;
}

View file

@ -2,6 +2,7 @@ import { describe, it, beforeEach, afterEach, mock } from "node:test";
import assert from "node:assert";
let configFileContent = undefined;
let loggedMessages = [];
mock.module("fs", {
namedExports: {
existsSync: () => configFileContent !== undefined,
@ -11,6 +12,14 @@ mock.module("fs", {
},
});
mock.module("../environment/userInteraction.js", {
namedExports: {
ui: {
writeInformation: (message) => loggedMessages.push(message),
},
},
});
const {
getNpmCustomRegistries,
getPipCustomRegistries,
@ -545,6 +554,7 @@ describe("getMalwareListBaseUrl", () => {
delete process.env[envVarName];
// Reset CLI arguments state
initializeCliArguments([]);
loggedMessages = [];
});
afterEach(() => {
@ -644,4 +654,46 @@ describe("getMalwareListBaseUrl", () => {
assert.strictEqual(url, "https://cli-mirror.com");
});
it("should mask credentials in logged URL from CLI argument", () => {
initializeCliArguments(["--safe-chain-malware-list-base-url=https://user:pass@cli-mirror.com"]);
const url = getMalwareListBaseUrl();
assert.strictEqual(url, "https://user:pass@cli-mirror.com");
assert.strictEqual(loggedMessages.length, 1);
assert.strictEqual(loggedMessages[0], "Fetching malware lists from https://***@cli-mirror.com as defined by CLI argument --safe-chain-malware-list-base-url");
});
it("should mask credentials in logged URL from environment variable", () => {
process.env[envVarName] = "https://user:pass@env-mirror.com";
const url = getMalwareListBaseUrl();
assert.strictEqual(url, "https://user:pass@env-mirror.com");
assert.strictEqual(loggedMessages.length, 1);
assert.strictEqual(loggedMessages[0], "Fetching malware lists from https://***@env-mirror.com as defined by environment variable SAFE_CHAIN_MALWARE_LIST_BASE_URL");
});
it("should mask credentials in logged URL from config file", () => {
configFileContent = JSON.stringify({
malwareListBaseUrl: "https://user:pass@config-mirror.com",
});
const url = getMalwareListBaseUrl();
assert.strictEqual(url, "https://user:pass@config-mirror.com");
assert.strictEqual(loggedMessages.length, 1);
assert.strictEqual(loggedMessages[0], "Fetching malware lists from https://***@config-mirror.com as defined by config file (malwareListBaseUrl)");
});
it("should sanitize control characters in logged URL", () => {
initializeCliArguments(["--safe-chain-malware-list-base-url=https://user:pass@cli-mirror.com\nmalicious"]);
const url = getMalwareListBaseUrl();
assert.strictEqual(url, "https://user:pass@cli-mirror.com\nmalicious");
assert.strictEqual(loggedMessages.length, 1);
assert.strictEqual(loggedMessages[0], "Fetching malware lists from https://***@cli-mirror.commalicious as defined by CLI argument --safe-chain-malware-list-base-url");
});
});