Skip to content

Commit

Permalink
Correctly label packages as dev-only in yarn audit (#6970)
Browse files Browse the repository at this point in the history
* Test dev vs. prod dependencies in yarn audit

* Implement getTransitiveDevDependencies utility function

* Use getTransitiveDevDependencies in yarn audit

* Fix up yarn audit tests

* Update CHANGELOG.md
  • Loading branch information
as3richa authored and arcanis committed Mar 14, 2019
1 parent 9437e51 commit d4a46a5
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,10 @@ Please add one entry in this file for each change in Yarn's behavior. Use the sa

## Master

- Fixes production / development reporting when running `yarn audit`

[#6970](https://github.com/yarnpkg/yarn/pull/6970) - [**Adam Richardson**](https://github.com/as3richa)

## 1.15.0

- Removes `--scripts-prepend-node-path` as Yarn's default behavior makes this obsolete
Expand Down
70 changes: 67 additions & 3 deletions __tests__/commands/audit.js
Expand Up @@ -68,6 +68,7 @@ test.concurrent('sends correct dependency map to audit api for single dependency
'brace-expansion': '^1.0.0',
},
dependencies: {},
dev: false,
},
'brace-expansion': {
version: '1.1.11',
Expand All @@ -77,21 +78,25 @@ test.concurrent('sends correct dependency map to audit api for single dependency
'concat-map': '0.0.1',
},
dependencies: {},
dev: false,
},
'balanced-match': {
version: '1.0.0',
integrity: 'sha1-ibTRmasr7kneFk6gK4nORi1xt2c=',
requires: {},
dependencies: {},
dev: false,
},
'concat-map': {
version: '0.0.1',
integrity: 'sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=',
requires: {},
dependencies: {},
dev: false,
},
},
version: '0.0.0',
dev: false,
};

return runAudit([], {}, 'single-vulnerable-dep-installed', async config => {
Expand Down Expand Up @@ -141,8 +146,10 @@ test.concurrent('sends correct dependency map to audit api for private package.'
integrity: 'sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==',
requires: {},
dependencies: {},
dev: false,
},
},
dev: false,
};

return runAudit([], {}, 'private-package', async config => {
Expand All @@ -166,11 +173,54 @@ test('calls reporter auditSummary with correct data for private package', () =>
});
});

test.concurrent('distinguishes dev and prod transitive dependencies in audit request and result', () => {
const expectedApiPost = {
name: 'foo',
version: '1.0.0',
install: [],
remove: [],
metadata: {},
requires: {
mime: '1.4.0',
hoek: '4.2.0',
},
dependencies: {
mime: {
version: '1.4.0',
integrity: 'sha512-n9ChLv77+QQEapYz8lV+rIZAW3HhAPW2CXnzb1GN5uMkuczshwvkW7XPsbzU0ZQN3sP47Er2KVkp2p3KyqZKSQ==',
requires: {},
dependencies: {},
dev: false,
},
hoek: {
version: '4.2.0',
integrity: 'sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==',
requires: {},
dependencies: {},
dev: true,
},
},
dev: false,
};

return runAudit([], {}, 'dev-and-prod-vulnerabilities', async (config, reporter) => {
const calledWithPipe = config.requestManager.request.mock.calls[0][0].body;
const calledWith = JSON.parse(await gunzip(calledWithPipe));
expect(calledWith).toEqual(expectedApiPost);

const apiResponse = getAuditResponse(config);
expect(reporter.auditSummary).toBeCalledWith(apiResponse.metadata);
});
});

describe('returns semantic exit codes', () => {
let lockfileSpy;
let installSpy;

beforeAll(() => {
// mock unrelated stuff
jest.spyOn(lockfileModule.default, 'fromDirectory').mockImplementation(jest.fn());
jest.spyOn(installModule, 'Install').mockImplementation(() => {
lockfileSpy = jest.spyOn(lockfileModule.default, 'fromDirectory').mockImplementation(jest.fn());
installSpy = jest.spyOn(installModule, 'Install').mockImplementation(() => {
return {
fetchRequestFromCwd: jest.fn(() => {
return {};
Expand All @@ -185,6 +235,11 @@ describe('returns semantic exit codes', () => {
});
});

afterAll(() => {
lockfileSpy.mockRestore();
installSpy.mockRestore();
});

const exitCodeTestCases = [
[0, {}, 'zero when no vulnerabilities'],
[1, {info: 77}, '1 for info'],
Expand All @@ -196,13 +251,16 @@ describe('returns semantic exit codes', () => {
];
exitCodeTestCases.forEach(([expectedExitCode, foundVulnerabilities, description]) => {
test(description, async () => {
jest.spyOn(auditModule.default.prototype, 'performAudit').mockImplementation(() => {
const spy = jest.spyOn(auditModule.default.prototype, 'performAudit').mockImplementation(() => {
return foundVulnerabilities;
});

const configMock: any = {};
const reporterMock: any = {};
const exitCode = await audit(configMock, reporterMock, {}, []);
expect(exitCode).toEqual(expectedExitCode);

spy.mockRestore();
});
});
});
Expand All @@ -212,12 +270,14 @@ test.concurrent('sends correct dependency map to audit api for workspaces.', ()
dependencies: {
'balanced-match': {
dependencies: {},
dev: false,
integrity: 'sha1-ibTRmasr7kneFk6gK4nORi1xt2c=',
requires: {},
version: '1.0.0',
},
'brace-expansion': {
dependencies: {},
dev: false,
integrity: 'sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==',
requires: {
'balanced-match': '^1.0.0',
Expand All @@ -227,12 +287,14 @@ test.concurrent('sends correct dependency map to audit api for workspaces.', ()
},
'concat-map': {
dependencies: {},
dev: false,
integrity: 'sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=',
requires: {},
version: '0.0.1',
},
minimatch: {
dependencies: {},
dev: false,
integrity: 'sha1-UjYVelHk8ATBd/s8Un/33Xjw74M=',
requires: {
'brace-expansion': '^1.0.0',
Expand All @@ -241,6 +303,7 @@ test.concurrent('sends correct dependency map to audit api for workspaces.', ()
},
prj1: {
dependencies: {},
dev: false,
integrity: '',
requires: {
minimatch: '3.0.0',
Expand All @@ -256,6 +319,7 @@ test.concurrent('sends correct dependency map to audit api for workspaces.', ()
prj1: '0.0.0',
},
version: '1.0.0',
dev: false,
};

return runAudit([], {}, 'workspace', async config => {
Expand Down
@@ -0,0 +1,132 @@
{
"actions": [
{
"action": "install",
"module": "mime",
"target": "2.4.0",
"isMajor": true,
"resolves": [
{
"id": 535,
"path": "mime",
"dev": false,
"optional": false,
"bundled": false
}
]
},
{
"action": "install",
"module": "hoek",
"target": "6.1.2",
"isMajor": true,
"resolves": [
{
"id": 566,
"path": "hoek",
"dev": true,
"optional": false,
"bundled": false
}
]
}
],
"advisories": {
"535": {
"findings": [
{
"version": "1.4.0",
"paths": [
"mime"
],
"dev": false,
"optional": false,
"bundled": false
}
],
"id": 535,
"created": "2017-09-25T19:02:28.152Z",
"updated": "2018-04-09T00:38:22.785Z",
"deleted": null,
"title": "Regular Expression Denial of Service",
"found_by": {
"name": "Cristian-Alexandru Staicu"
},
"reported_by": {
"name": "Cristian-Alexandru Staicu"
},
"module_name": "mime",
"cves": [
"CVE-2017-16138"
],
"vulnerable_versions": "< 1.4.1 || > 2.0.0 < 2.0.3",
"patched_versions": ">= 1.4.1 < 2.0.0 || >= 2.0.3",
"overview": "Affected versions of `mime` are vulnerable to regular expression denial of service when a mime lookup is performed on untrusted user input.",
"recommendation": "Update to version 2.0.3 or later.",
"references": "[Issue #167](https://github.com/broofa/node-mime/issues/167)",
"access": "public",
"severity": "moderate",
"cwe": "CWE-400",
"metadata": {
"module_type": "Multi.Library",
"exploitability": 4,
"affected_components": ""
},
"url": "https://npmjs.com/advisories/535"
},
"566": {
"findings": [
{
"version": "4.2.0",
"paths": [
"hoek"
],
"dev": true,
"optional": false,
"bundled": false
}
],
"id": 566,
"created": "2018-04-20T21:25:58.421Z",
"updated": "2018-04-20T21:25:58.421Z",
"deleted": null,
"title": "Prototype pollution",
"found_by": {
"name": "HoLyVieR"
},
"reported_by": {
"name": "HoLyVieR"
},
"module_name": "hoek",
"cves": [],
"vulnerable_versions": "<= 4.2.0 || >= 5.0.0 < 5.0.3",
"patched_versions": "> 4.2.0 < 5.0.0 || >= 5.0.3",
"overview": "Versions of `hoek` prior to 4.2.1 and 5.0.3 are vulnerable to prototype pollution.\n\nThe `merge` function, and the `applyToDefaults` and `applyToDefaultsWithShallow` functions which leverage `merge` behind the scenes, are vulnerable to a prototype pollution attack when provided an _unvalidated_ payload created from a JSON string containing the `__proto__` property.\n\nThis can be demonstrated like so:\n\n```javascript\nvar Hoek = require('hoek');\nvar malicious_payload = '{\"__proto__\":{\"oops\":\"It works !\"}}';\n\nvar a = {};\nconsole.log(\"Before : \" + a.oops);\nHoek.merge({}, JSON.parse(malicious_payload));\nconsole.log(\"After : \" + a.oops);\n```\n\nThis type of attack can be used to overwrite existing properties causing a potential denial of service.",
"recommendation": "Update to version 4.2.1, 5.0.3 or later.",
"references": "",
"access": "public",
"severity": "moderate",
"cwe": "CWE-471",
"metadata": {
"module_type": "",
"exploitability": 5,
"affected_components": ""
},
"url": "https://npmjs.com/advisories/566"
}
},
"muted": [],
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 2,
"high": 0,
"critical": 0
},
"dependencies": 1,
"devDependencies": 1,
"optionalDependencies": 0,
"totalDependencies": 2
}
}
10 changes: 10 additions & 0 deletions __tests__/fixtures/audit/dev-and-prod-vulnerabilities/package.json
@@ -0,0 +1,10 @@
{
"name": "foo",
"version": "1.0.0",
"devDependencies": {
"hoek": "4.2.0"
},
"dependencies": {
"mime": "1.4.0"
}
}
13 changes: 13 additions & 0 deletions __tests__/fixtures/audit/dev-and-prod-vulnerabilities/yarn.lock
@@ -0,0 +1,13 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


hoek@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
integrity sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==

mime@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.0.tgz#69e9e0db51d44f2a3b56e48b7817d7d137f1a343"
integrity sha512-n9ChLv77+QQEapYz8lV+rIZAW3HhAPW2CXnzb1GN5uMkuczshwvkW7XPsbzU0ZQN3sP47Er2KVkp2p3KyqZKSQ==

0 comments on commit d4a46a5

Please sign in to comment.