Skip to content

Commit

Permalink
feat(audit) Initial addition of yarn audit command. (#6409)
Browse files Browse the repository at this point in the history
* WIP: audit command added. sends data to registry.

* code cleanup

* WIP: Added Audit command. No tests. Existing test fail.

* Print audit summary when no problems found

* Don't send package version to audit API if it is not in the manifest.

* Add audit functions to json-reporter

* WIP: First successful audit command test

* added more audit tests

* feat(audit): Initial addition of yarn audit command and --audit flag

Added "yarn audit" command which copies the behavior of "npm audit". Unline npm, yarn does not
automatically run "audit" during "add/install/upgrade" commands. Since this would cause an
additional network call, it broke all existing unit tests to add this feature and have it run
automatically. In the interest of getting an initial release in the hands of our users the
"add/install/upgrade" commands accept a "--audit" flag that will enable the audit. If you want audit
to always execute, you can add "--*.audit true" to .yarnrc

fix #5808

* gzip the JSON sent to npm audit API to reduce payload

* fix audit test for gzip data

* Update install.js

* removed audit correction suggestions due to them being unreliable

* Updates the changelog
  • Loading branch information
rally25rs authored and arcanis committed Oct 2, 2018
1 parent 9028f9a commit 49a157c
Show file tree
Hide file tree
Showing 22 changed files with 993 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,10 @@ Please add one entry in this file for each change in Yarn's behavior. Use the sa

[#6447](https://github.com/yarnpkg/yarn/pull/6447) - [**John-David Dalton**](https://twitter.com/jdalton)

- Adds `yarn audit` (and the `--audit` flag for all installs)

[#6409](https://github.com/yarnpkg/yarn/pull/6409) - [**Jeff Valore**](https://github.com/rally25rs)

- Adds a special logic to PnP for ESLint compatibility (temporary, until [eslint/eslint#10125](https://github.com/eslint/eslint/issues/10125) is fixed)

[#6449](https://github.com/yarnpkg/yarn/pull/6449) - [**Maël Nison**](https://twitter.com/arcanis)
Expand Down
125 changes: 125 additions & 0 deletions __tests__/commands/audit.js
@@ -0,0 +1,125 @@
/* @flow */

import {NoopReporter} from '../../src/reporters/index.js';
import {run as buildRun} from './_helpers.js';
import {run as audit} from '../../src/cli/commands/audit.js';
import {promisify} from '../../src/util/promise.js';

const path = require('path');
const zlib = require('zlib');
const gunzip = promisify(zlib.gunzip);

const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'audit');

const setupMockRequestManager = function(config) {
const apiResponse = JSON.stringify(getAuditResponse(config), null, 2);
// $FlowFixMe
config.requestManager.request = jest.fn();
config.requestManager.request.mockReturnValue(
new Promise(resolve => {
resolve(apiResponse);
}),
);
};

const setupMockReporter = function(reporter) {
// $FlowFixMe
reporter.auditAdvisory = jest.fn();
// $FlowFixMe
reporter.auditAction = jest.fn();
// $FlowFixMe
reporter.auditSummary = jest.fn();
};

const getAuditResponse = function(config): Object {
// $FlowFixMe
return require(path.join(config.cwd, 'audit-api-response.json'));
};

const runAudit = buildRun.bind(
null,
NoopReporter,
fixturesLoc,
async (args, flags, config, reporter, lockfile, getStdout): Promise<string> => {
setupMockRequestManager(config);
setupMockReporter(reporter);
await audit(config, reporter, flags, args);
return getStdout();
},
);

test.concurrent('sends correct dependency map to audit api for single dependency.', () => {
const expectedApiPost = {
name: 'yarn-test',
install: [],
remove: [],
metadata: {},
requires: {
minimatch: '^3.0.0',
},
dependencies: {
minimatch: {
version: '3.0.0',
integrity: 'sha1-UjYVelHk8ATBd/s8Un/33Xjw74M=',
requires: {
'brace-expansion': '^1.0.0',
},
dependencies: {},
},
'brace-expansion': {
version: '1.1.11',
integrity: 'sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==',
requires: {
'balanced-match': '^1.0.0',
'concat-map': '0.0.1',
},
dependencies: {},
},
'balanced-match': {
version: '1.0.0',
integrity: 'sha1-ibTRmasr7kneFk6gK4nORi1xt2c=',
requires: {},
dependencies: {},
},
'concat-map': {
version: '0.0.1',
integrity: 'sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=',
requires: {},
dependencies: {},
},
},
version: '0.0.0',
};

return runAudit([], {}, 'single-vulnerable-dep-installed', async config => {
const calledWithPipe = config.requestManager.request.mock.calls[0][0].body;
const calledWith = JSON.parse(await gunzip(calledWithPipe));
expect(calledWith).toEqual(expectedApiPost);
});
});

test('calls reporter auditAdvisory with correct data', () => {
return runAudit([], {}, 'single-vulnerable-dep-installed', (config, reporter) => {
const apiResponse = getAuditResponse(config);
expect(reporter.auditAdvisory).toBeCalledWith(apiResponse.actions[0].resolves[0], apiResponse.advisories['118']);
});
});

// *** Test temporarily removed due to inability to correctly puggest actions to the user.
// test('calls reporter auditAction with correct data', () => {
// return runAudit([], {}, 'single-vulnerable-dep-installed', (config, reporter) => {
// const apiResponse = getAuditResponse(config);
// expect(reporter.auditAction).toBeCalledWith({
// cmd: 'yarn upgrade minimatch@3.0.4',
// isBreaking: false,
// action: apiResponse.actions[0],
// });
// });
// });

test('calls reporter auditSummary with correct data', () => {
return runAudit([], {}, 'single-vulnerable-dep-installed', (config, reporter) => {
const apiResponse = getAuditResponse(config);
expect(reporter.auditSummary).toBeCalledWith(apiResponse.metadata);
});
});
@@ -0,0 +1,77 @@
{
"actions": [
{
"action": "install",
"module": "minimatch",
"target": "3.0.4",
"isMajor": false,
"resolves": [
{
"id": 118,
"path": "minimatch",
"dev": false,
"optional": false,
"bundled": false
}
]
}
],
"advisories": {
"118": {
"findings": [
{
"version": "3.0.0",
"paths": [
"minimatch"
],
"dev": false,
"optional": false,
"bundled": false
}
],
"id": 118,
"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"
}
},
"muted": [],
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 0,
"high": 1,
"critical": 0
},
"dependencies": 5,
"devDependencies": 0,
"optionalDependencies": 0,
"totalDependencies": 5
}
}
@@ -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/single-vulnerable-dep-installed/yarn.lock
@@ -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"
8 changes: 8 additions & 0 deletions __tests__/reporters/__snapshots__/console-reporter.js.snap
Expand Up @@ -7,6 +7,14 @@ Object {
}
`;

exports[`ConsoleReporter.auditSummary 1`] = `
Object {
"stderr": "",
"stdout": "1 vulnerabilities found - Packages audited: 5
Severity: 1 High",
}
`;
exports[`ConsoleReporter.command 1`] = `
Object {
"stderr": "",
Expand Down
21 changes: 21 additions & 0 deletions __tests__/reporters/__snapshots__/json-reporter.js.snap
Expand Up @@ -17,6 +17,27 @@ Object {
}
`;

exports[`JSONReporter.auditAction 1`] = `
Object {
"stderr": "",
"stdout": "{\\"type\\":\\"auditAction\\",\\"data\\":{\\"cmd\\":\\"yarn upgrade gulp@4.0.0\\",\\"isBreaking\\":true,\\"action\\":{\\"action\\":\\"install\\",\\"module\\":\\"gulp\\",\\"target\\":\\"4.0.0\\",\\"isMajor\\":true,\\"resolves\\":[]}}}",
}
`;

exports[`JSONReporter.auditAdvisory 1`] = `
Object {
"stderr": "",
"stdout": "{\\"type\\":\\"auditAdvisory\\",\\"data\\":{\\"resolution\\":{\\"id\\":118,\\"path\\":\\"gulp>vinyl-fs>glob-stream>minimatch\\",\\"dev\\":false,\\"optional\\":false,\\"bundled\\":false},\\"advisory\\":{\\"findings\\":[{\\"bundled\\":false,\\"optional\\":false,\\"dev\\":false,\\"paths\\":[],\\"version\\":\\"\\"}],\\"id\\":118,\\"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\\":\\"\\",\\"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\\":\\"\\"},\\"url\\":\\"https://nodesecurity.io/advisories/118\\"}}}",
}
`;

exports[`JSONReporter.auditSummary 1`] = `
Object {
"stderr": "",
"stdout": "{\\"type\\":\\"auditSummary\\",\\"data\\":{\\"vulnerabilities\\":{\\"info\\":0,\\"low\\":1,\\"moderate\\":0,\\"high\\":4,\\"critical\\":0},\\"dependencies\\":29105,\\"devDependencies\\":0,\\"optionalDependencies\\":0,\\"totalDependencies\\":29105}}",
}
`;

exports[`JSONReporter.command 1`] = `
Object {
"stderr": "",
Expand Down
22 changes: 22 additions & 0 deletions __tests__/reporters/console-reporter.js
Expand Up @@ -304,3 +304,25 @@ test('ConsoleReporter.tree is silent when isSilent is true', async () => {
}),
).toMatchSnapshot();
});

test('ConsoleReporter.auditSummary', async () => {
const auditMetadata = {
vulnerabilities: {
info: 0,
low: 0,
moderate: 0,
high: 1,
critical: 0,
},
dependencies: 5,
devDependencies: 0,
optionalDependencies: 0,
totalDependencies: 5,
};

expect(
await getConsoleBuff(r => {
r.auditSummary(auditMetadata);
}),
).toMatchSnapshot();
});

0 comments on commit 49a157c

Please sign in to comment.