mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
369 lines
10 KiB
JavaScript
369 lines
10 KiB
JavaScript
import os from "os";
|
|
import path from "path";
|
|
import * as cliArguments from "./cliArguments.js";
|
|
import * as configFile from "./configFile.js";
|
|
import * as environmentVariables from "./environmentVariables.js";
|
|
import { ui } from "../environment/userInteraction.js";
|
|
|
|
export const LOGGING_SILENT = "silent";
|
|
export const LOGGING_NORMAL = "normal";
|
|
export const LOGGING_VERBOSE = "verbose";
|
|
|
|
export function getLoggingLevel() {
|
|
// Priority 1: CLI argument
|
|
const cliLevel = cliArguments.getLoggingLevel();
|
|
if (isValidVerbosity(cliLevel)) {
|
|
return cliLevel;
|
|
}
|
|
if (cliLevel) {
|
|
// CLI arg was set but invalid, default to normal for backwards compatibility.
|
|
return LOGGING_NORMAL;
|
|
}
|
|
|
|
// Priority 2: Environment variable
|
|
const envLevel = environmentVariables.getLoggingLevel()?.toLowerCase();
|
|
if (isValidVerbosity(envLevel)) {
|
|
return envLevel;
|
|
}
|
|
|
|
return LOGGING_NORMAL;
|
|
}
|
|
|
|
/**
|
|
* Gets the log file path with priority: CLI argument > environment variable > config file > undefined
|
|
* @returns {string | undefined}
|
|
*/
|
|
export function getLogFile() {
|
|
// Priority 1: CLI argument
|
|
const cliValue = cliArguments.getLogFile();
|
|
if (cliValue) {
|
|
return expandTilde(cliValue);
|
|
}
|
|
|
|
// Priority 2: Environment variable
|
|
const envValue = environmentVariables.getLogFile();
|
|
if (envValue) {
|
|
return expandTilde(envValue);
|
|
}
|
|
|
|
// Priority 3: Config file
|
|
const configValue = configFile.getLogFile();
|
|
if (configValue) {
|
|
return expandTilde(configValue);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Expands a leading "~/" or bare "~" to the user's home directory. Shells
|
|
* don't expand tilde after "=" (e.g. --safe-chain-log-file=~/foo.log) and
|
|
* env vars are never shell-expanded, so we do it here.
|
|
*
|
|
* @param {string} filePath
|
|
* @returns {string}
|
|
*/
|
|
function expandTilde(filePath) {
|
|
if (filePath === "~") return os.homedir();
|
|
if (filePath.startsWith("~/")) return path.join(os.homedir(), filePath.slice(2));
|
|
return filePath;
|
|
}
|
|
|
|
export const LOG_FILE_FORMAT_PLAIN = "plain";
|
|
export const LOG_FILE_FORMAT_JSON = "json";
|
|
|
|
/**
|
|
* Gets the log file format with priority: CLI argument > environment variable > config file > "json"
|
|
* @returns {string}
|
|
*/
|
|
export function getLogFileFormat() {
|
|
// Priority 1: CLI argument
|
|
const cliValue = cliArguments.getLogFileFormat();
|
|
if (cliValue === LOG_FILE_FORMAT_PLAIN || cliValue === LOG_FILE_FORMAT_JSON) {
|
|
return cliValue;
|
|
}
|
|
if (cliValue) {
|
|
// CLI arg was set but invalid, default to json. Mirrors getLoggingLevel.
|
|
return LOG_FILE_FORMAT_JSON;
|
|
}
|
|
|
|
// Priority 2: Environment variable
|
|
const envValue = environmentVariables.getLogFileFormat()?.toLowerCase();
|
|
if (envValue === LOG_FILE_FORMAT_PLAIN || envValue === LOG_FILE_FORMAT_JSON) {
|
|
return envValue;
|
|
}
|
|
|
|
// Priority 3: Config file
|
|
const configValue = configFile.getLogFileFormat()?.toLowerCase();
|
|
if (configValue === LOG_FILE_FORMAT_PLAIN || configValue === LOG_FILE_FORMAT_JSON) {
|
|
return configValue;
|
|
}
|
|
|
|
return LOG_FILE_FORMAT_JSON;
|
|
}
|
|
|
|
/**
|
|
* Gets the log file verbosity with priority: CLI argument > environment
|
|
* variable > config file > LOGGING_VERBOSE. Default is verbose because the
|
|
* file is meant to be the diagnostic record; users opt down explicitly.
|
|
* Reuses the LOGGING_* enum since the levels mean the same thing as for
|
|
* console output.
|
|
*
|
|
* @returns {string}
|
|
*/
|
|
export function getLogFileVerbosity() {
|
|
// Priority 1: CLI argument
|
|
const cliValue = cliArguments.getLogFileVerbosity();
|
|
if (isValidVerbosity(cliValue)) {
|
|
return cliValue;
|
|
}
|
|
if (cliValue) {
|
|
// CLI arg was set but invalid, default to verbose. Mirrors getLoggingLevel.
|
|
return LOGGING_VERBOSE;
|
|
}
|
|
|
|
// Priority 2: Environment variable
|
|
const envValue = environmentVariables.getLogFileVerbosity()?.toLowerCase();
|
|
if (isValidVerbosity(envValue)) {
|
|
return envValue;
|
|
}
|
|
|
|
// Priority 3: Config file
|
|
const configValue = configFile.getLogFileVerbosity()?.toLowerCase();
|
|
if (isValidVerbosity(configValue)) {
|
|
return configValue;
|
|
}
|
|
|
|
return LOGGING_VERBOSE;
|
|
}
|
|
|
|
/**
|
|
* @param {string | undefined} value
|
|
* @returns {value is "silent" | "normal" | "verbose"}
|
|
*/
|
|
function isValidVerbosity(value) {
|
|
return (
|
|
value === LOGGING_SILENT ||
|
|
value === LOGGING_NORMAL ||
|
|
value === LOGGING_VERBOSE
|
|
);
|
|
}
|
|
|
|
export const ECOSYSTEM_JS = "js";
|
|
export const ECOSYSTEM_PY = "py";
|
|
|
|
// Default to JavaScript ecosystem
|
|
const ecosystemSettings = {
|
|
ecoSystem: ECOSYSTEM_JS,
|
|
};
|
|
|
|
/** @returns {string} - The current ecosystem setting (ECOSYSTEM_JS or ECOSYSTEM_PY) */
|
|
export function getEcoSystem() {
|
|
return ecosystemSettings.ecoSystem;
|
|
}
|
|
/**
|
|
* @param {string} setting - The ecosystem to set (ECOSYSTEM_JS or ECOSYSTEM_PY)
|
|
*/
|
|
export function setEcoSystem(setting) {
|
|
ecosystemSettings.ecoSystem = setting;
|
|
}
|
|
|
|
const defaultMinimumPackageAge = 48;
|
|
/** @returns {number} */
|
|
export function getMinimumPackageAgeHours() {
|
|
// Priority 1: CLI argument
|
|
const cliValue = validateMinimumPackageAgeHours(
|
|
cliArguments.getMinimumPackageAgeHours()
|
|
);
|
|
if (cliValue !== undefined) {
|
|
return cliValue;
|
|
}
|
|
|
|
// Priority 2: Environment variable
|
|
const envValue = validateMinimumPackageAgeHours(
|
|
environmentVariables.getMinimumPackageAgeHours()
|
|
);
|
|
if (envValue !== undefined) {
|
|
return envValue;
|
|
}
|
|
|
|
// Priority 3: Config file
|
|
const configValue = configFile.getMinimumPackageAgeHours();
|
|
if (configValue !== undefined) {
|
|
return configValue;
|
|
}
|
|
|
|
return defaultMinimumPackageAge;
|
|
}
|
|
|
|
/**
|
|
* @param {string | undefined} value
|
|
* @returns {number | undefined}
|
|
*/
|
|
function validateMinimumPackageAgeHours(value) {
|
|
if (!value) {
|
|
return undefined;
|
|
}
|
|
|
|
const numericValue = Number(value);
|
|
if (Number.isNaN(numericValue)) {
|
|
return undefined;
|
|
}
|
|
|
|
if (numericValue >= 0) {
|
|
return numericValue;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
const defaultSkipMinimumPackageAge = false;
|
|
export function skipMinimumPackageAge() {
|
|
const cliValue = cliArguments.getSkipMinimumPackageAge();
|
|
|
|
if (cliValue === true) {
|
|
return true;
|
|
}
|
|
|
|
return defaultSkipMinimumPackageAge;
|
|
}
|
|
|
|
/**
|
|
* Normalizes a registry URL by removing protocol if present
|
|
* @param {string} registry
|
|
* @returns {string}
|
|
*/
|
|
function normalizeRegistry(registry) {
|
|
// Remove protocol (http://, https://) if present
|
|
return registry.replace(/^https?:\/\//, "");
|
|
}
|
|
|
|
/**
|
|
* Parses comma-separated registries from environment variable
|
|
* @param {string | undefined} envValue
|
|
* @returns {string[]}
|
|
*/
|
|
function parseRegistriesFromEnv(envValue) {
|
|
if (!envValue || typeof envValue !== "string") {
|
|
return [];
|
|
}
|
|
|
|
// Split by comma and trim whitespace
|
|
return envValue
|
|
.split(",")
|
|
.map((registry) => registry.trim())
|
|
.filter((registry) => registry.length > 0);
|
|
}
|
|
|
|
/**
|
|
* Gets the custom npm registries from both environment variable and config file (merged)
|
|
* @returns {string[]}
|
|
*/
|
|
export function getNpmCustomRegistries() {
|
|
const envRegistries = parseRegistriesFromEnv(
|
|
environmentVariables.getNpmCustomRegistries()
|
|
);
|
|
const configRegistries = configFile.getNpmCustomRegistries();
|
|
|
|
// Merge both sources and remove duplicates
|
|
const allRegistries = [...envRegistries, ...configRegistries];
|
|
const uniqueRegistries = [...new Set(allRegistries)];
|
|
|
|
// Normalize each registry (remove protocol if any)
|
|
return uniqueRegistries.map(normalizeRegistry);
|
|
}
|
|
|
|
/**
|
|
* Gets the custom npm registries from both environment variable and config file (merged)
|
|
* @returns {string[]}
|
|
*/
|
|
export function getPipCustomRegistries() {
|
|
const envRegistries = parseRegistriesFromEnv(
|
|
environmentVariables.getPipCustomRegistries()
|
|
);
|
|
const configRegistries = configFile.getPipCustomRegistries();
|
|
|
|
// Merge both sources and remove duplicates
|
|
const allRegistries = [...envRegistries, ...configRegistries];
|
|
const uniqueRegistries = [...new Set(allRegistries)];
|
|
|
|
// Normalize each registry (remove protocol if any)
|
|
return uniqueRegistries.map(normalizeRegistry);
|
|
}
|
|
|
|
/**
|
|
* Parses comma-separated exclusions from environment variable
|
|
* @param {string | undefined} envValue
|
|
* @returns {string[]}
|
|
*/
|
|
function parseExclusionsFromEnv(envValue) {
|
|
if (!envValue || typeof envValue !== "string") {
|
|
return [];
|
|
}
|
|
|
|
return envValue
|
|
.split(",")
|
|
.map((exclusion) => exclusion.trim())
|
|
.filter((exclusion) => exclusion.length > 0);
|
|
}
|
|
|
|
/**
|
|
* Gets the minimum package age exclusions from both environment variable and config file (merged)
|
|
* @returns {string[]}
|
|
*/
|
|
export function getMinimumPackageAgeExclusions() {
|
|
const envExclusions = parseExclusionsFromEnv(
|
|
environmentVariables.getMinimumPackageAgeExclusions()
|
|
);
|
|
const configExclusions = configFile.getMinimumPackageAgeExclusions();
|
|
|
|
// Merge both sources and remove duplicates
|
|
const allExclusions = [...envExclusions, ...configExclusions];
|
|
return [...new Set(allExclusions)];
|
|
}
|
|
|
|
/**
|
|
* Gets the malware list base URL with priority: CLI argument > environment variable > config file > default
|
|
* @returns {string}
|
|
*/
|
|
export function getMalwareListBaseUrl() {
|
|
// Priority 1: CLI argument
|
|
const cliValue = cliArguments.getMalwareListBaseUrl();
|
|
if (cliValue) {
|
|
const url = removeTrailingSlashes(cliValue);
|
|
ui.writeVerbose(`Fetching malware lists from ${url} as defined by CLI argument --safe-chain-malware-list-base-url`);
|
|
return url;
|
|
}
|
|
|
|
// Priority 2: Environment variable
|
|
const envValue = environmentVariables.getMalwareListBaseUrl();
|
|
if (envValue) {
|
|
const url = removeTrailingSlashes(envValue);
|
|
ui.writeVerbose(`Fetching malware lists from ${url} as defined by environment variable SAFE_CHAIN_MALWARE_LIST_BASE_URL`);
|
|
return url;
|
|
}
|
|
|
|
// Priority 3: Config file
|
|
const configValue = configFile.getMalwareListBaseUrl();
|
|
if (configValue) {
|
|
const url = removeTrailingSlashes(configValue);
|
|
ui.writeVerbose(`Fetching malware lists from ${url} as defined by config file (malwareListBaseUrl)`);
|
|
return url;
|
|
}
|
|
|
|
// Default
|
|
return removeTrailingSlashes("https://malware-list.aikido.dev");
|
|
}
|
|
|
|
/**
|
|
* Removes trailing slashes from a URL-like string.
|
|
* @param {string} value
|
|
* @returns {string}
|
|
*/
|
|
function removeTrailingSlashes(value) {
|
|
if (!value || typeof value !== "string") {
|
|
return value;
|
|
}
|
|
|
|
return value.replace(/\/+$/, "");
|
|
}
|