Skip to content

Instantly share code, notes, and snippets.

@KittyGiraudel
Last active February 18, 2022 08:44
Show Gist options
  • Save KittyGiraudel/37438267cb36448a85d56b8501d91aab to your computer and use it in GitHub Desktop.
Save KittyGiraudel/37438267cb36448a85d56b8501d91aab to your computer and use it in GitHub Desktop.
const { exec } = require('child_process')
const { promisify } = require('util')
const chalk = require('chalk')
// See: https://docs.npmjs.com/about-audit-reports#severity
const SEVERITY_LEVELS = ['low', 'moderate', 'high', 'critical']
const SEVERITY_THRESHOLD = 'critical'
const run = promisify(exec)
// Get the output of a command. If the command exits with a non-zero code, try
// to get the output from the error instead.
// Fair warning: written specifically for the `npm audit` command and might not
// work universally.
// @param {String} command - Command to get the output from
// @return {String}
const getOutput = async command => {
try {
const output = await run(command, 'inherit')
return JSON.parse(output.stdout)
} catch (error) {
return JSON.parse(error.stdout.toString())
}
}
// Determine whether the given severity is considered severe or not based on the
// set threshold.
// @param {String} severity
// @return {Boolean}
const isSevere = severity =>
SEVERITY_LEVELS.indexOf(severity) >=
SEVERITY_LEVELS.indexOf(SEVERITY_THRESHOLD)
// Determines whether a `resolve` object from the audit is considered important
// by checking if it is a development dependency, and if its severity reaches
// the set severity threshold.
// @param {Object} data - Dependency audit
// @param {Object} resolve - Resolve object
// @return {Boolean}
const isResolveImportant = data => resolve =>
!resolve.dev && isSevere(data.advisories[resolve.id].severity)
// Determines whether an `action` object from the audit is considered important
// by checking if some of the issues it resolves are important.
// @param {Object} data - Dependency audit
// @param {Object} action - Action object
// @return {Boolean}
const isActionImportant = data => action =>
action.resolves.some(isResolveImportant(data))
// Determines whether the script should exit with an error based on whether some
// suggested actions can resolve important issues.
// @param {Object} data - Dependency audit
// @return {Boolean}
const shouldAbort = data => data.actions.some(isActionImportant(data))
// Sorts actions based on their severity level
// @param {Object} data - Dependency audit
// @param {Object} a - Action object
// @param {Object} b - Action object
// @return {Boolean}
const sortBySeverity = data => (a, b) =>
SEVERITY_LEVELS.indexOf(data.advisories[a.resolves[0].id].severity) -
SEVERITY_LEVELS.indexOf(data.advisories[b.resolves[0].id].severity)
// Gets some information and suggestion for each action based on the issues they
// resolve.
// @param {Object} data - Dependency audit
// @return {String[]}
const getSuggestions = data =>
data.actions.sort(sortBySeverity(data)).map(getSuggestion(data))
// Finds the depth of the vulnerability to be able to provide it as `--depth` to
// `npm update`.
// @param {Object[]} findings - Advisory findings
// @return {Number}
const findDepth = findings =>
Math.max(
...findings.map(finding =>
Math.max(...finding.paths.map(path => path.split('>').length))
)
)
// Gets some information and suggestion for the given action based on the issues
// it resolves.
// @param {Object} data - Dependency audit
// @param {Object} action - Action object
// @return {String}
const getSuggestion = data => action => {
const advisory = data.advisories[action.resolves[0].id]
const module = chalk.bold.red(advisory.module_name)
const version = advisory.vulnerable_versions
const type = advisory.title
const severity = advisory.severity + ' severity'
const explanation = chalk.dim(advisory.overview.replace(/\n+$/, ''))
const url = chalk.dim(advisory.url)
const recommendation = advisory.recommendation
const depth = findDepth(advisory.findings)
const command = chalk.green(
action.action === 'install'
? `npm install ${action.module}@${action.target}`
: `npm update ${action.module} --depth ${depth}`
)
return `${module} (${version})
${type} (${severity})
${explanation}
See: ${url}
${recommendation}
${command}`
}
;(async () => {
try {
const output = await getOutput(`npm audit --json`)
const suggestions = getSuggestions(output)
const count = suggestions.length
if (count) {
const word = count > 1 ? 'vulnerabilities' : 'vulnerability'
console.log(`⚠️ ${count} ${word} found:\n`)
suggestions.forEach(suggestion => console.log(suggestion, '\n'))
}
process.exit(Number(shouldAbort(output)))
} catch (error) {
console.error('Could not run dependency audit', error)
}
})()
@AxelTheGerman
Copy link

Love your blog post which led me here https://hugogiraudel.com/2020/11/19/managing-npm-dependencies

Thanks for writing that up and sharing your code - hopefully all this useful tooling could even find it's way upstream, e.g. directly into npm audit and same for other scripts you wrote to check for new versions

🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment