Skip to content

Commit

Permalink
Add mute option for security advisories
Browse files Browse the repository at this point in the history
in the audit command you can now add for example --mute 123 to
mute specific advisories. This code is mostly taken from pr yarnpkg#8223
and Fixes yarnpkg#6669
  • Loading branch information
aterlamia committed Oct 1, 2020
1 parent a4708b2 commit 6aa1ae0
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 3 deletions.
8 changes: 8 additions & 0 deletions __tests__/commands/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@ test('calls reporter auditAdvisory with correct data', () => {
});
});

test('calls reporter auditAdvisory with non muted data', () => {
return runAudit([], {mute: ['119']}, 'multi-vulnerable-dep-installed', (config, reporter) => {
const apiResponse = getAuditResponse(config);
expect(reporter.auditAdvisory).toHaveBeenCalledTimes(1);
expect(reporter.auditAdvisory).toBeCalledWith(apiResponse.actions[1].resolves[0], apiResponse.advisories['120']);
});
});

// *** Test temporarily removed due to inability to correctly suggest actions to the user.
// test('calls reporter auditAction with correct data', () => {
// return runAudit([], {}, 'single-vulnerable-dep-installed', (config, reporter) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
{
"actions": [
{
"action": "install",
"module": "minimatch",
"target": "3.0.4",
"isMajor": false,
"resolves": [
{
"id": 119,
"path": "minimatch",
"dev": false,
"optional": false,
"bundled": false
}
]
},
{
"action": "install",
"module": "minimatch",
"target": "3.0.5",
"isMajor": false,
"resolves": [
{
"id": 120,
"path": "minimatch",
"dev": false,
"optional": false,
"bundled": false
}
]
}
],
"advisories": {
"119": {
"findings": [
{
"version": "3.0.0",
"paths": [
"minimatch"
],
"dev": false,
"optional": false,
"bundled": false
}
],
"id": 119,
"created": "2016-05-25T16:37:20.000Z",
"updated": "2018-03-01T21:58:01.072Z",
"deleted": null,
"title": "Regular Expression Denial of Service",
"found_by": {
"name": "Nick Starke"
},
"reported_by": {
"name": "Nick Starke"
},
"module_name": "minimatch",
"cves": [
"CVE-2016-10540"
],
"vulnerable_versions": "<=3.0.1",
"patched_versions": ">=3.0.2",
"overview": "Affected versions of `minimatch` are vulnerable to regular expression denial of service attacks when user input is passed into the `pattern` argument of `minimatch(path, pattern)`.\n\n\n## Proof of Concept\n```\nvar minimatch = require(“minimatch”);\n\n// utility function for generating long strings\nvar genstr = function (len, chr) {\n var result = “”;\n for (i=0; i<=len; i++) {\n result = result + chr;\n }\n return result;\n}\n\nvar exploit = “[!” + genstr(1000000, “\\\\”) + “A”;\n\n// minimatch exploit.\nconsole.log(“starting minimatch”);\nminimatch(“foo”, exploit);\nconsole.log(“finishing minimatch”);\n```",
"recommendation": "Update to version 3.0.2 or later.",
"references": "",
"access": "public",
"severity": "high",
"cwe": "CWE-400",
"metadata": {
"module_type": "Multi.Library",
"exploitability": 4,
"affected_components": "Internal::Code::Function::minimatch({type:'args', key:0, vector:{type:'string'}})"
},
"url": "https://nodesecurity.io/advisories/118"
},
"120": {
"findings": [
{
"version": "3.0.0",
"paths": [
"minimatch"
],
"dev": false,
"optional": false,
"bundled": false
}
],
"id": 120,
"created": "2016-05-25T16:37:20.000Z",
"updated": "2018-03-01T21:58:01.072Z",
"deleted": null,
"title": "Regular Expression Denial of Service unmuted",
"found_by": {
"name": "Nick Starke"
},
"reported_by": {
"name": "Nick Starke"
},
"module_name": "minimatch",
"cves": [
"CVE-2016-10540"
],
"vulnerable_versions": "<=3.0.1",
"patched_versions": ">=3.0.2",
"overview": "Affected versions of `minimatch` are vulnerable to regular expression denial of service attacks when user input is passed into the `pattern` argument of `minimatch(path, pattern)`.\n\n\n## Proof of Concept\n```\nvar minimatch = require(“minimatch”);\n\n// utility function for generating long strings\nvar genstr = function (len, chr) {\n var result = “”;\n for (i=0; i<=len; i++) {\n result = result + chr;\n }\n return result;\n}\n\nvar exploit = “[!” + genstr(1000000, “\\\\”) + “A”;\n\n// minimatch exploit.\nconsole.log(“starting minimatch”);\nminimatch(“foo”, exploit);\nconsole.log(“finishing minimatch”);\n```",
"recommendation": "Update to version 3.0.2 or later.",
"references": "",
"access": "public",
"severity": "high",
"cwe": "CWE-400",
"metadata": {
"module_type": "Multi.Library",
"exploitability": 4,
"affected_components": "Internal::Code::Function::minimatch({type:'args', key:0, vector:{type:'string'}})"
},
"url": "https://nodesecurity.io/advisories/118"
}
},
"muted": [],
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 0,
"high": 2,
"critical": 0
},
"dependencies": 5,
"devDependencies": 0,
"optionalDependencies": 0,
"totalDependencies": 5
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "yarn-test",
"version": "0.0.0",
"dependencies": {
"minimatch": "^3.0.0"
}
}
28 changes: 28 additions & 0 deletions __tests__/fixtures/audit/multi-vulnerable-dep-installed/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=

brace-expansion@^1.0.0:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"

concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=

minimatch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.0.tgz#5236157a51e4f004c177fb3c527ff7dd78f0ef83"
integrity sha1-UjYVelHk8ATBd/s8Un/33Xjw74M=
dependencies:
brace-expansion "^1.0.0"
57 changes: 54 additions & 3 deletions src/cli/commands/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const gzip = promisify(zlib.gzip);
export type AuditOptions = {
groups: Array<string>,
level?: string,
muteIssues?: string[],
};

export type AuditNode = {
Expand Down Expand Up @@ -134,6 +135,11 @@ export function setFlags(commander: Object) {
info|low|moderate|high|critical. Default: info`,
'info',
);
commander.option(
'--mute <advisory> [<advisory> ...]',
`Mute any of the specified advisory ids`,
muteIssues => muteIssues && muteIssues.split(','),
);
}

export function hasWrapper(commander: Object, args: Array<string>): boolean {
Expand All @@ -145,6 +151,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
const audit = new Audit(config, reporter, {
groups: flags.groups || OWNED_DEPENDENCY_TYPES,
level: flags.level || DEFAULT_LOG_LEVEL,
muteIssues: flags.mute,
});
const lockfile = await Lockfile.fromDirectory(config.lockfileFolder, reporter);
const install = new Install({}, config, reporter, lockfile);
Expand Down Expand Up @@ -260,8 +267,49 @@ export default class Audit {
if (!responseJson.metadata) {
throw new Error(`Unexpected audit response (Missing Metadata): ${JSON.stringify(responseJson, null, 2)}`);
}
this.reporter.verbose(`Audit Response: ${JSON.stringify(responseJson, null, 2)}`);
return responseJson;

const filteredResponse = responseJson;

if (this.options.muteIssues && this.options.muteIssues.length) {
const newAdvisories = {};
const newActions = [];
const newMuted = [];
const newVulnerabilities = Object.assign({}, responseJson.metadata.vulnerabilities);

for (const [key, value] of Object.entries(responseJson.advisories)) {
if (this.options.muteIssues && this.options.muteIssues.includes(key)) {
newMuted.push(value);
responseJson.actions.forEach(action => {
const newResolves = action.resolves.filter(resolve => {
if (key == resolve.id.toString() && value && value.severity) {
newVulnerabilities[value.severity] -= 1;
return false;
}
return true;
});
if (newResolves.length) {
newActions.push({
action: action.action,
module: action.module,
resolves: newResolves,
});
}
});
} else {
newAdvisories[key] = value;
}
}

Object.assign(filteredResponse, responseJson, {
muted: newMuted,
advisories: newAdvisories,
actions: newActions,
metadata: Object.assign(responseJson.metadata, {vulnerabilities: newVulnerabilities}),
});
}
this.reporter.verbose(`Audit Response: ${JSON.stringify(filteredResponse, null, 2)}`);

return filteredResponse;
}

_insertWorkspacePackagesIntoManifest(manifest: Object, resolver: PackageResolver) {
Expand Down Expand Up @@ -304,11 +352,13 @@ export default class Audit {
if (!this.auditData) {
return;
}

const startLoggingAt: number = Math.max(0, this.severityLevels.indexOf(this.options.level));

const reportAdvisory = (resolution: AuditResolution) => {
const advisory = this.auditData.advisories[resolution.id.toString()];
if (!advisory) {
return;
}

if (this.severityLevels.indexOf(advisory.severity) >= startLoggingAt) {
this.reporter.auditAdvisory(resolution, advisory);
Expand Down Expand Up @@ -349,5 +399,6 @@ export default class Audit {
}

this.summary();
this.reporter.auditMute(this.auditData.muted);
}
}
3 changes: 3 additions & 0 deletions src/reporters/base-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ export default class BaseReporter {
// summary for security audit report
auditSummary(auditMetadata: AuditMetadata) {}

// muted advisories for security audit report
auditMute(mutedAdvisories: AuditAdvisory[]) {}

// render an activity spinner and return a function that will trigger an update
activity(): ReporterSpinner {
return {
Expand Down
9 changes: 9 additions & 0 deletions src/reporters/console/console-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,15 @@ export default class ConsoleReporter extends BaseReporter {
}
}
auditMute(mutedAdvisories: AuditAdvisory[]) {
const message = this.lang(
'auditMute',
this.rawText(chalk.yellow(mutedAdvisories.length.toString())),
this.rawText(mutedAdvisories.map(advisory => advisory.id).join(', ')),
);
this._log(message);
}
auditAction(recommendation: AuditActionRecommendation) {
const label = recommendation.action.resolves.length === 1 ? 'vulnerability' : 'vulnerabilities';
this._log(
Expand Down
4 changes: 4 additions & 0 deletions src/reporters/json-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,8 @@ export default class JSONReporter extends BaseReporter {
auditSummary(auditMetadata: AuditMetadata) {
this._dump('auditSummary', auditMetadata);
}

auditMute(mutedAdvisories: AuditAdvisory[]) {
this._dump('auditMute', mutedAdvisories);
}
}
1 change: 1 addition & 0 deletions src/reporters/lang/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ const messages = {
auditRunning: 'Auditing packages',
auditSummary: '$0 vulnerabilities found - Packages audited: $1',
auditSummarySeverity: 'Severity:',
auditMute: '$0 vulnerabilities muted - Advisory ids: ($1)',
auditCritical: '$0 Critical',
auditHigh: '$0 High',
auditModerate: '$0 Moderate',
Expand Down

0 comments on commit 6aa1ae0

Please sign in to comment.