Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(audit) Initial addition of yarn audit command. #6409

Merged
merged 20 commits into from Oct 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
});