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

Correctly label packages as dev-only in yarn audit #6970

Merged
merged 6 commits into from Mar 14, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
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==