PHP WebShell
Текущая директория: /opt/BitGoJS/node_modules/improved-yarn-audit/bin
Просмотр файла: improved-yarn-audit
#! /usr/bin/env node
const { execSync, spawn } = require("child_process")
const { randomBytes } = require("crypto")
const {
existsSync,
createReadStream,
createWriteStream,
mkdtempSync,
readFileSync,
rmdirSync,
statSync,
writeFileSync,
unlinkSync
} = require("fs")
const { tmpdir } = require("os")
const path = require("path")
const { env, exit, platform } = require("process")
const { createInterface } = require("readline")
const GITHUB_ADVISORY_CODE = "GHSA"
const joinPath = path.join
const isWindows = platform === "win32"
const packageInfo = require(joinPath(__dirname, "..", "package.json"))
const optionFlags = {
outputFormat: ["--output-format", "-of"],
outputPath: ["--output-path", "-op"],
severity: ["--min-severity", "-s"],
exclude: ["--exclude", "-e"],
retryNetworkIssues: ["--retry-on-network-failure", "-r"],
ignoreDevDependencies: ["--ignore-dev-deps", "-i"],
failOnMissingExclusions: ["--fail-on-missing-exclusions", "-f"],
quiet: ["--quiet", "-q"],
debug: ["--debug", "-d"],
version: ["--version", "-v"],
help: ["--help", "-h"]
}
const severityToIntMap = {
info: 0,
low: 1,
moderate: 2,
high: 3,
critical: 4
}
const outputFormats = [
"text",
"json",
"yarn-json"
]
const maxSeverityNameLength = 8
const exclusionsFileName = ".iyarc"
let logEnabled = true
let outputFormat = "text"
let outputPath = null
let minSeverityName = "low"
let minSeverity = severityToIntMap[minSeverityName]
let excludedAdvisories = []
let ignoreDevDependencies = false
let failOnMissingAdvisoryExclusions = false
let debugEnabled = false
let shouldRetryNetworkErrors = false
let auditResultsFilePath = "/dev/null"
function severityShouldBeIgnored(severity) {
return severityToIntMap[severity] < minSeverity
}
function flatMap(arr, callback) {
let arrays = arr
let returnArray = []
if (typeof callback === "function") {
arrays = arr.map(callback)
}
arrays.forEach((a) => a.forEach((i) => returnArray.push(i)))
return returnArray
}
async function logDebug(strOrFunc) {
if (!debugEnabled || !logEnabled) {
return
}
let output = typeof strOrFunc === "function" ? await strOrFunc() : strOrFunc
console.log(`DEBUG: ${output}`)
}
function log(str) {
if (logEnabled) {
console.log(str)
}
}
function checkForMissingExclusions(allAdvisories) {
const missingExcludedAdvisories = excludedAdvisories.filter(
(ea) =>
allAdvisories.find((a) => a.id === ea || a.github_advisory_id === ea) ===
undefined
)
if (missingExcludedAdvisories.length < 1) {
logDebug("No missing advisory exclusions found")
return
}
log()
log(
"WARNING: One or more excluded audit advisories were missing from yarn audit output: " +
`${missingExcludedAdvisories.join(",")}`
)
if (failOnMissingAdvisoryExclusions) {
console.error(
`ERROR: ${optionFlags.failOnMissingExclusions.join(
"/"
)} was specified, ` +
"exit code will indicate number of missing exclusions"
)
process.exit(missingExcludedAdvisories.length)
}
}
function reportIgnoredAdvisories(
devDependencyAdvisoryIds,
severityIgnoredAuditAdvisories,
excludedAuditAdvisories
) {
if (ignoreDevDependencies && devDependencyAdvisoryIds.length > 0) {
console.warn(`${devDependencyAdvisoryIds.length} ignored because ` +
`they are dev dependencies\n`)
}
if (severityIgnoredAuditAdvisories.length > 0) {
console.warn(`${severityIgnoredAuditAdvisories.length} ignored because ` +
`severity was lower than "${minSeverityName}"\n`)
}
if (excludedAuditAdvisories.length > 0) {
console.warn(`${excludedAuditAdvisories.length} ignored because of advisory exclusions\n`)
}
}
async function createReport(
filteredAuditAdvisories,
devDependencyAdvisories,
devDependencyAdvisoryIds,
severityIgnoredAuditAdvisories,
excludedAuditAdvisories
) {
logDebug(() => `Dev audit advisories:\n${toJson(devDependencyAdvisories)}\n`)
logDebug(
() => `Excluded audit advisories:\n${toJson(excludedAuditAdvisories)}\n`
)
logDebug(
() =>
`Severity ignored advisories:\n${toJson(
severityIgnoredAuditAdvisories
)}\n`
)
log(`Found ${filteredAuditAdvisories.length} vulnerabilities\n`)
reportIgnoredAdvisories(
devDependencyAdvisoryIds,
severityIgnoredAuditAdvisories,
excludedAuditAdvisories
)
const outputText = outputFormat === "text"
? createTextReport(filteredAuditAdvisories)
: await createJsonReport(filteredAuditAdvisories, outputFormat === "yarn-json")
if (isNullOrEmpty(outputPath)) {
console.log(`${outputText}`)
} else {
writeFileSync(outputPath, outputText)
}
return filteredAuditAdvisories.length
}
const createTextReport = filteredAuditAdvisories =>
filteredAuditAdvisories.map((a) => {
const formattedSeverity = a.severity
.toUpperCase()
.padEnd(maxSeverityNameLength, " ")
const affectedModulePaths = flatMap(a.findings, (f) => f.paths)
const affectedModules = affectedModulePaths.join(", ")
return `Vulnerability Found:
Severity: ${formattedSeverity}
Modules: ${affectedModules}
URL: ${a.url}`
}).join("\n\n")
async function createJsonReport(filteredAuditAdvisories, produceYarnJson) {
const report = filteredAuditAdvisories.map(a => {
return {
type: "auditAdvisory",
data: {
resolution: {
id: a.id,
// TODO: handle different paths for the same vulnerability
path: a.findings[0].paths[0],
dev: a._resolution.dev,
optional: a._resolution.optional,
bundled: a._resolution.bundled
},
advisory: a
}
}
})
const getAdvisoryCountBySeverity = s => filteredAuditAdvisories.filter(a => a.severity === s).length
const auditSummary = await getAuditSummary()
auditSummary.data.vulnerabilities = {
info: getAdvisoryCountBySeverity("info"),
low: getAdvisoryCountBySeverity("low"),
moderate: getAdvisoryCountBySeverity("moderate"),
high: getAdvisoryCountBySeverity("high"),
critical: getAdvisoryCountBySeverity("critical")
}
report.push(auditSummary)
return produceYarnJson
? report.map(e => JSON.stringify(e)).join("\n")
: json = JSON.stringify(report)
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
function isNonExcludedAdvisory(advisory, devDependencyAdvisoryIds) {
return (
!severityShouldBeIgnored(advisory.severity) &&
!excludedAdvisories.includes(advisory.id) &&
!excludedAdvisories.includes(advisory.github_advisory_id) &&
(!devDependencyAdvisoryIds.includes(advisory.id) || !ignoreDevDependencies)
)
}
function parseAuditJson(jsonString) {
try {
return JSON.parse(jsonString)
} catch (ex) {
console.error(`ERROR: Unable to parse yarn audit output: ${ex}`)
console.error("Try running `yarn audit` for more info")
process.exit(1)
}
}
async function getAuditSummary() {
logDebug("Getting yarn audit summary")
let auditSummary = ""
await iterateOverAuditResults((l) => (auditSummary = l))
return JSON.parse(auditSummary)
}
async function handleAuditNetworkError(output) {
const error = "ERROR: Network error occurred when querying audit registry"
if (!shouldRetryNetworkErrors) {
errorAndExit(`${error}\n\n${output}`)
}
console.error(`${error}, retrying...\n`)
await sleep(1000)
return await runYarnAudit()
}
async function dumpAuditResultsAsString() {
let output = ""
await iterateOverAuditResults((l) => (output += l))
return output
}
async function iterateOverAuditResults(action) {
logDebug("Iterating over audit results")
const auditResultsFileStream = getAuditResultsFileStream("r")
const iterator = createInterface(auditResultsFileStream)
iterator.on("line", action)
await new Promise((resolve) => iterator.on("close", resolve))
auditResultsFileStream.close()
}
function getAuditResultsFileStream(mode) {
logDebug(
`Opening file stream for file '${auditResultsFilePath}' in '${mode}' mode`
)
if (mode === "w") {
return createWriteStream(auditResultsFilePath)
}
return createReadStream(auditResultsFilePath)
}
async function cleanupAuditResultsFile() {
if (!existsSync(auditResultsFilePath)) {
return
}
if (isWindows) {
// workaround for unlinkSync issues on windows
execSync(`del "${auditResultsFilePath}"`)
return
}
unlinkSync(auditResultsFilePath)
}
async function streamYarnAuditOutput(auditParams, auditResultsFileStream) {
const yarnBinaryPostFix = isWindows ? ".cmd" : ""
const yarnProcess = spawn(`yarn${yarnBinaryPostFix}`, auditParams, {
env: env,
shell: isWindows,
stdio: ["pipe", auditResultsFileStream, auditResultsFileStream]
})
let exitCode = await new Promise((resolve, reject) =>
yarnProcess.on("exit", resolve).on("error", reject)
)
auditResultsFileStream.close()
logDebug(
() => `Yarn audit output size: ${statSync(auditResultsFilePath).size} bytes`
)
return exitCode
}
async function invokeYarnAudit() {
const auditParams = ["audit", "--json", `--level=${minSeverityName}`]
if (ignoreDevDependencies) {
auditParams.push("--groups=dependencies")
}
cleanupAuditResultsFile()
const auditResultsFileStream = getAuditResultsFileStream("w")
let exitCode = await new Promise((resolve) =>
auditResultsFileStream.on("open", async () => {
let exitCode = await streamYarnAuditOutput(
auditParams,
auditResultsFileStream
)
resolve(exitCode)
})
)
return exitCode
}
async function runYarnAudit() {
log("Running yarn audit...\n")
const exitCode = await invokeYarnAudit()
let networkErrorHasOccurred = false
if (existsSync(auditResultsFilePath)) {
await iterateOverAuditResults(
(a) =>
(networkErrorHasOccurred =
networkErrorHasOccurred || a.includes("Error: Request failed "))
)
}
logDebug(`networkErrorHasOccurred: ${networkErrorHasOccurred}`)
if (networkErrorHasOccurred) {
return await handleAuditNetworkError()
}
if (exitCode === 1) {
errorAndExit(
`ERROR: Yarn audit error:\n${await dumpAuditResultsAsString()}`
)
}
}
function getDevDependenciesRegex() {
if (!existsSync("package.json")) {
log(
"WARNING: No package.json was found in the current working directory"
)
logDebug("Dev dependencies will not be ignored")
return
}
const packageJson = readFileSync("package.json")
const package = JSON.parse(packageJson)
let devDependencies = []
if (
typeof package === "object" &&
typeof package.devDependencies === "object"
) {
devDependencies = Object.keys(package.devDependencies)
}
if (devDependencies.length < 1) {
logDebug("No dev dependencies installed")
logDebug("Dev dependencies will not be ignored")
return
}
logDebug(() => `Dev dependencies: ${devDependencies.join(", ")}`)
ignoreDevDependencies
? logDebug("Dev dependencies will be ignored")
: logDebug("Dev dependencies will not be ignored")
const devDependenciesOr = devDependencies.map((d) => `(${d})`).join("|")
const devDependenciesRegex = `^${devDependenciesOr}>*.*$`
logDebug(`Dev dependencies regex: ${devDependenciesRegex}`)
return new RegExp(devDependenciesRegex)
}
async function runAuditReport() {
const devDependenciesRegex = getDevDependenciesRegex()
await runYarnAudit()
logDebug(async () => {
let auditSummaryJson = await getAuditSummary()
let auditSummary = toJson(auditSummaryJson)
return `Audit summary:\n${auditSummary}\n`
})
const allAdvisories = []
const filteredAuditAdvisories = []
const severityIgnoredAuditAdvisories = []
const excludedAuditAdvisories = []
let devDependencyAdvisories = []
let devDependencyAdvisoryIds = []
await iterateOverAuditResults((resultJson) => {
const potentialResult = parseAuditJson(resultJson)
if (
typeof potentialResult.type !== "string" ||
potentialResult.type !== "auditAdvisory"
) {
return
}
const result = potentialResult.data.advisory
result._resolution = potentialResult.data.resolution
allAdvisories.push(result)
if (devDependenciesRegex) {
const isDevDependencyAdvisory = flatMap(
result.findings,
(f) => f.paths
).every((d) => d.match(devDependenciesRegex))
if (isDevDependencyAdvisory) {
devDependencyAdvisories = devDependencyAdvisories.concat(result)
devDependencyAdvisoryIds = devDependencyAdvisories.concat(
devDependencyAdvisories.map((d) => d.id)
)
}
}
if (isNonExcludedAdvisory(result, devDependencyAdvisoryIds)) {
filteredAuditAdvisories.push(result)
}
if (
(excludedAdvisories.includes(result.id) ||
excludedAdvisories.includes(result.github_advisory_id)) &&
!severityShouldBeIgnored(result.severity)
) {
excludedAuditAdvisories.push(result)
}
if (severityShouldBeIgnored(result.severity)) {
severityIgnoredAuditAdvisories.push(result)
}
})
checkForMissingExclusions(allAdvisories)
return await createReport(
filteredAuditAdvisories,
devDependencyAdvisories,
devDependencyAdvisoryIds,
severityIgnoredAuditAdvisories,
excludedAuditAdvisories
)
}
async function withTempDir(action, cleanupAction) {
const prefix = joinPath(tmpdir(), "iya")
const tempDirPath = mkdtempSync(prefix)
try {
return await action(tempDirPath)
} finally {
await cleanupAction()
rmdirSync(tempDirPath, { recursive: true })
}
}
async function withTempFile(action, cleanupAction) {
return await withTempDir((tempDirPath) => {
const randomName = randomBytes(16).toString("hex")
const tmpFilePath = joinPath(tempDirPath, randomName)
return action(tmpFilePath)
}, cleanupAction)
}
function toJson(value) {
return JSON.stringify(value, null, 2)
}
function printVersionAndExit() {
errorAndExit(packageInfo.version)
}
function printUsageAndExit() {
errorAndExit(`
improved-yarn-audit [OPTIONS]
Options:
--min-severity, -s Minimum severity to treat as an error, default is low (info, low, moderate, high, critical)
--exclude, -e CSV list of advisory ID's to ignore, e.x. 432,564 (this overrides .iyarc)
--retry-on-network-failure, -r Retry audit if NPM registry throws a network error
--ignore-dev-deps, -i Ignore advisories for dev dependencies
--fail-on-missing-exclusions, -f Return a non-zero exit code when advisory exclusions are no longer detected by yarn audit
--output-path, -op The path to the output file to write audit reports to - if not provided stdout is used
--output-format, -of The format of the audit report to produce (text, json or yarn-json) - default is text
--quiet, -q Don't print out any log lines to stdout, only audit reports
--debug, -d Print out raw audit report's and advisory details
--version, -v Print version info and exit
--help, -h Show this information
The CSV list of advisory ID's can also be provided in a '.iyarc' file in the current working directory. This file also supports
comments, lines beginning with a '#' character are ignored.
`)
}
function errorAndExit(msg) {
console.error(msg)
process.exit(1)
}
function parseJSONSafeExcludeList(str) {
return str
.split(",")
.map((option) => {
if (option.startsWith(GITHUB_ADVISORY_CODE)) {
return `"${option}"`
}
return option
})
.filter((str) => str)
.join(",")
}
function parseConfigToJson(str) {
const jsonSafeString = parseJSONSafeExcludeList(str)
return JSON.parse(`[${jsonSafeString}]`)
}
function isValidConfigFormat(str) {
try {
const testExcludeList = parseConfigToJson(str)
testExcludeList.forEach((excludeOption) => {
if (
parseInt(excludeOption) === NaN &&
!excludeOption.startsWith(GITHUB_ADVISORY_CODE)
) {
throw "Invalid format"
}
})
return true
} catch {
return false
}
}
function isNullOrEmpty(str) {
return typeof str !== "string" || str.trim() === ""
}
function loadExclusionsFromFileIfPresent() {
if (!existsSync(exclusionsFileName)) {
logDebug(`No ${exclusionsFileName} found in working directory`)
return
}
if (excludedAdvisories.length > 0) {
log(
`WARNING: Ignoring ${exclusionsFileName} as exclusions were passed in via command line`
)
return
}
let matchedNpmAdvisories = readFileSync(exclusionsFileName)
.toString()
.match(/(?<=^(?:\d+,)*)\d+(?=(?:,\d+)*$)/gm)
let matchedGithubAdvisories = readFileSync(exclusionsFileName)
.toString()
.match(/^(GHSA-([a-z0-9]{4})-([a-z0-9]{4})-([a-z0-9]{4}))$/gm)
let advisoriesNPMCsv = matchedNpmAdvisories
? matchedNpmAdvisories.join(",")
: ""
let advisoriesGithubCsv = matchedGithubAdvisories
? matchedGithubAdvisories.join(",")
: ""
let advisoriesCsv = [advisoriesNPMCsv, advisoriesGithubCsv].join(",")
logDebug(`.iyarc contents (excluding comments): ${advisoriesCsv}`)
if (!isValidConfigFormat(advisoriesCsv)) {
errorAndExit(
`ERROR: ${exclusionsFileName} is not in the correct format, excluded advisories must be provided on ` +
"individual lines, or as a CSV list (eg: '2341,21,43,GHSA-42xw-2xvc-qx8mas')"
)
}
log(`Reading excluded advisories from ${exclusionsFileName}`)
excludedAdvisories = parseConfigToJson(advisoriesCsv)
}
function isFlag(flags, ...strings) {
return (
strings.filter(
(s) => !isNullOrEmpty(s) && flags.includes(s.trim().toLowerCase())
).length > 0
)
}
function parseCommandLineArgs() {
process.argv.reduce((a, b) => {
if (isFlag(optionFlags.version, a, b)) {
printVersionAndExit()
}
if (isFlag(optionFlags.help, a, b)) {
printUsageAndExit()
}
if (isFlag(optionFlags.exclude, a) && !isNullOrEmpty(b)) {
if (isValidConfigFormat(b)) {
excludedAdvisories = parseConfigToJson(b)
} else {
errorAndExit(`ERROR: Unable to parse --exclude option value: ${b}`)
}
}
if (isFlag(optionFlags.severity, a) && !isNullOrEmpty(b)) {
minSeverityName = b.trim().toLowerCase()
minSeverity = severityToIntMap[minSeverityName]
if ([null, undefined].includes(minSeverity)) {
errorAndExit(
`ERROR: Unrecognised --min-severity option value: ${minSeverityName}`
)
}
}
if (isFlag(optionFlags.outputFormat, a) && !isNullOrEmpty(b)) {
outputFormat = b.trim().toLowerCase()
if (outputFormats.indexOf(outputFormat) < 0) {
errorAndExit(
`ERROR: Unrecognised --output-format option value: ${outputFormat}`
)
}
}
if (isFlag(optionFlags.outputPath, a) && !isNullOrEmpty(b)) {
outputPath = b.trim()
}
if (isFlag(optionFlags.quiet, a, b)) {
logEnabled = false
}
if (isFlag(optionFlags.debug, a, b)) {
debugEnabled = true
}
if (isFlag(optionFlags.retryNetworkIssues, a, b)) {
shouldRetryNetworkErrors = true
}
if (isFlag(optionFlags.ignoreDevDependencies, a, b)) {
ignoreDevDependencies = true
}
if (isFlag(optionFlags.failOnMissingExclusions, a, b)) {
failOnMissingAdvisoryExclusions = true
}
return b
}, "")
}
async function main() {
parseCommandLineArgs()
log(`Improved Yarn Audit - v${packageInfo.version}`)
log()
logDebug(`shouldRetryNetworkErrors = ${shouldRetryNetworkErrors}`)
logDebug(`ignoreDevDependencies = ${ignoreDevDependencies}`)
loadExclusionsFromFileIfPresent()
log(`Minimum severity level to report: ${minSeverityName}`)
log(
excludedAdvisories.length > 0
? `Excluded Advisories: ${JSON.stringify(excludedAdvisories)}\n`
: ""
)
try {
let advisoryCount = await withTempFile((filePath) => {
auditResultsFilePath = filePath
logDebug(`Temporary file path: ${auditResultsFilePath}`)
return runAuditReport()
}, cleanupAuditResultsFile)
if (advisoryCount > 0) {
log("Run `yarn audit` for more information")
}
exit(advisoryCount)
} catch (e) {
console.error("Audit failed due to an error: \n")
console.error(e)
}
exit(1)
}
main()
Выполнить команду
Для локальной разработки. Не используйте в интернете!