diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 295488e2..7da8da9b 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -31,8 +31,9 @@ jobs: - name: Bootstrap run: npm run bootstrap - - name: audit tools #disabled while we wait for https://github.com/actions/toolkit/issues/539 - run: npm audit --audit-level=moderate + - name: audit tools + # `|| npm audit` to pretty-print the output if vulnerabilies are found after filtering. + run: npm audit --audit-level=moderate --json | scripts/audit-allow-list || npm audit --audit-level=moderate - name: audit packages run: npm run audit-all diff --git a/package-lock.json b/package-lock.json index 526e751c..078409f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13828,9 +13828,9 @@ } }, "normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.0.1.tgz", + "integrity": "sha512-VU4pzAuh7Kip71XEmO9aNREYAdMHFGTVj/i+CaTImS8x0i1d3jUZkXhqluy/PRgjPLMgsLQulYY3PJ/aSbSjpQ==", "dev": true }, "npm-bundled": { @@ -14487,13 +14487,13 @@ } }, "parse-url": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.2.tgz", - "integrity": "sha512-Czj+GIit4cdWtxo3ISZCvLiUjErSo0iI3wJ+q9Oi3QuMYTI6OZu+7cewMWZ+C1YAnKhYTk6/TLuhIgCypLthPA==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.3.tgz", + "integrity": "sha512-nrLCVMJpqo12X8uUJT4GJPd5AFaTOrGx/QpJy3HNcVtq0AZSstVIsnxS5fqNPuoqMUs3MyfBoOP6Zvu2Arok5A==", "dev": true, "requires": { "is-ssh": "^1.3.0", - "normalize-url": "^3.3.0", + "normalize-url": "^6.0.1", "parse-path": "^4.0.0", "protocols": "^1.4.0" } @@ -16416,9 +16416,9 @@ "dev": true }, "trim-newlines": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", - "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, "trim-off-newlines": { diff --git a/scripts/audit-allow-list b/scripts/audit-allow-list new file mode 100755 index 00000000..761b9a2c --- /dev/null +++ b/scripts/audit-allow-list @@ -0,0 +1,89 @@ +#!/usr/bin/env node + +/* +This script takes the output of npm audit --json from stdin +and writes a filtered version to stdout. +The filtered version will have the entries listed in `AUDIT_ALLOW_LIST` removed. +Specifically, each property of `vulnerabilities` in the input is matched by name in the allow list. + +Sample output of `npm audit --json` (NPM v6): + +{ + "actions": [ + { + "action": "review", + "module": "trim-newlines", + "resolves": [ + { + "id": 1753, + "path": "lerna>@lerna/publish>@lerna/version>@lerna/conventional-commits>conventional-changelog-core>get-pkg-repo>meow>trim-newlines", + "dev": true, + "optional": false, + "bundled": false + } + ] + } + ], + // Other properties ... +} + + +The reason we have this script is that there may be low-severity or unexploitable vulnerabilities +that have not yet been fixed in newer versions of the package. + +Note: if we update to NPM v7, we will have to change this script because the `npm audit` output will be different. +See commit 935647112d96fa5cf82e61314f7135376d24f291 in https://github.com/actions/toolkit/pull/846. +*/ + +'use strict' +const fs = require('fs') + +const USAGE = "Usage: npm audit --json | scripts/audit-allow-list" + +// To add entires to the allow list: +// - Run `npm audit --json` +// - Copy `path` from each `actions[k].resolves` you want to allow +// - Fill in the `advisoryUrl` and `justification` (these are just for documentation) +const AUDIT_ALLOW_LIST = [ + { + path: "lerna>@lerna/publish>@lerna/version>@lerna/conventional-commits>conventional-changelog-core>get-pkg-repo>meow>trim-newlines", + advisoryUrl: "https://www.npmjs.com/advisories/1753", + justification: "dependency of lerna (dev only); low severity" + }, + { + path: "lerna>@lerna/version>@lerna/conventional-commits>conventional-changelog-core>get-pkg-repo>meow>trim-newlines", + advisoryUrl: "https://www.npmjs.com/advisories/1753", + justification: "dependency of lerna (dev only); low severity" + } +] + +/** + * @param audits - JavaScript object matching the schema of `npm audit --json` + * @param allowedPaths - List of dependency paths to exclude from the audit +*/ +function filterVulnerabilities(audits, allowedPaths) { + const vulnerabilities = audits.actions.flatMap(x => x.resolves) + return vulnerabilities.filter(x => !allowedPaths.includes(x.path)) +} + +const input = fs.readFileSync("/dev/stdin", "utf-8") +if (input === "") { + console.error(USAGE) + process.exit(1) +} + +const audits = JSON.parse(input) +const allowedPaths = AUDIT_ALLOW_LIST.map(x => x.path) +// This function assumes `audits` has the right structure. +// Just let the error terminate the process if the input doesn't match the schema. +const remainingVulnerabilities = filterVulnerabilities(audits, allowedPaths) + +// `npm audit` will return exit code 1 if it finds vulnerabilities. +// This script should do the same. +const numVulnerabilities = remainingVulnerabilities.length +if (numVulnerabilities > 0) { + const pluralized = numVulnerabilities === 1 ? "y" : "ies" + console.log(`Found ${numVulnerabilities} unrecognized vulnerabilit${pluralized} from \`npm audit\`:`) + console.log(JSON.stringify(remainingVulnerabilities, null, 2)) + process.exit(1) +} \ No newline at end of file