diff --git a/src/cli/commands/test.js b/src/cli/commands/test.js index 626ea30712e..bc08f49f2c6 100644 --- a/src/cli/commands/test.js +++ b/src/cli/commands/test.js @@ -26,7 +26,7 @@ function test() { } // populate with default path (cwd) if no path given - if (args.length === 0) { + if (args.length === 0) { args.unshift(process.cwd()); } @@ -43,12 +43,12 @@ function test() { return apiTokenExists('snyk test') .then(function () { - // Promise waterfall to test all other paths sequentially + // Promise waterfall to test all other paths sequentially var testsPromises = args.reduce(function (acc, path) { return acc.then(function () { - // Create a copy of the options so a specific test can - // modify them i.e. add `options.file` etc. We'll need - // these options later. + // Create a copy of the options so a specific test can + // modify them i.e. add `options.file` etc. We'll need + // these options later. var testOpts = _.cloneDeep(options); testOpts.path = path; resultOptions.push(testOpts); @@ -89,12 +89,12 @@ function test() { return testsPromises; }).then(function () { - // resultOptions is now an array of 1 or more options used for - // the tests results is now an array of 1 or more test results - // values depend on `options.json` value - string or object + // resultOptions is now an array of 1 or more options used for + // the tests results is now an array of 1 or more test results + // values depend on `options.json` value - string or object if (options.json) { results = results.map(function (result) { - // add json for when thrown exception + // add json for when thrown exception if (result instanceof Error) { return { ok: false, @@ -142,8 +142,8 @@ function test() { if (results.length > 1) { var projects = results.length === 1 ? ' project' : ' projects'; summaryMessage = '\n\n' + '\nTested ' + results.length + projects + - summariseVulnerableResults(vulnerableResults, options) + - summariseErrorResults(errorResults) + '\n'; + summariseVulnerableResults(vulnerableResults, options) + + summariseErrorResults(errorResults) + '\n'; } var notSuccess = vulnerableResults.length > 0 || errorResults.length > 0; @@ -183,7 +183,7 @@ function summariseVulnerableResults(vulnerableResults, options) { function summariseErrorResults(errorResults) { const projects = - errorResults.length > 1 ? ' projects' : ' project'; + errorResults.length > 1 ? ' projects' : ' project'; if (errorResults.length > 0) { return ' Failed to test ' + errorResults.length + projects + '.\nRun with `-d` for debug output and contact support@snyk.io'; @@ -227,13 +227,17 @@ function displayResult(res, options) { '✓ ' + testedInfoText + vulnPathsText ); var nextStepsText = - '\n\nNext steps:' + - '\n- Run `snyk monitor` to be notified ' + - 'about new related vulnerabilities.' + - '\n- Run `snyk test` as part of ' + - 'your CI/test.'; + '\n\nNext steps:' + + '\n- Run `snyk monitor` to be notified ' + + 'about new related vulnerabilities.' + + '\n- Run `snyk test` as part of ' + + 'your CI/test.'; return ( - prefix + meta + summaryOKText + (isCI ? '' : dockerAdvice + nextStepsText + dockerSuggestion) + prefix + meta + summaryOKText + ( + isCI ? '' : + dockerAdvice + + nextStepsText + + dockerSuggestion) ); } @@ -268,11 +272,11 @@ function displayResult(res, options) { } if (options.docker && - !options.file && - (config.disableSuggestions !== 'true')) { + !options.file && + (config.disableSuggestions !== 'true')) { summary += chalk.bold.white('\n\nPro tip: use `--file` option to get base image remediation advice.' + - `\nExample: $ snyk test --docker ${options.path} --file=path/to/Dockerfile` + - '\n\nTo remove this message in the future, please run `snyk config set disableSuggestions=true`'); + `\nExample: $ snyk test --docker ${options.path} --file=path/to/Dockerfile` + + '\n\nTo remove this message in the future, please run `snyk config set disableSuggestions=true`'); } summary += dockerSuggestion; @@ -294,6 +298,7 @@ function displayResult(res, options) { return i.from; })) .join(', '); + var vulnOutput = { issueHeading: createSeverityBasedIssueHeading( vuln.metadata.severity, @@ -326,7 +331,7 @@ function displayResult(res, options) { var body = groupedVulnInfoOutput.join('\n\n') + '\n\n' + meta + summary; return prefix + body + dockerAdvice; -} +}; function createFixedInText(groupedVuln) { var vulnerableRange = groupedVuln.list[0].semver.vulnerable[0]; @@ -347,13 +352,13 @@ function createRemediationText(vuln, packageManager) { if (vuln.isOutdated === true) { var packageManagerOutdatedText = { npm: '\n Try deleting node_modules, reinstalling ' + - 'and running `snyk test` again. If the problem persists, ' + - 'one of your dependencies may be bundling outdated modules.', + 'and running `snyk test` again. If the problem persists, ' + + 'one of your dependencies may be bundling outdated modules.', rubygems: '\n Try running `bundle update ' + packageName + '` ' + - 'and running `snyk test` again.', + 'and running `snyk test` again.', yarn: '\n Try deleting node_modules, reinstalling ' + - 'and running `snyk test` again. If the problem persists, ' + - 'one of your dependencies may be bundling outdated modules.', + 'and running `snyk test` again. If the problem persists, ' + + 'one of your dependencies may be bundling outdated modules.', }; return chalk.bold( @@ -535,7 +540,7 @@ function getSeverityValue(severity) { } function titleCaseText(text) { - return text[0].toUpperCase() + text.slice(1) ; + return text[0].toUpperCase() + text.slice(1); } // This is all a copy from Registry snapshots/index @@ -566,11 +571,11 @@ function groupVulnerabilities(vulns) { } if (!map[curr.id].isOutdated) { - map[curr.id].isOutdated = !!curr.isOutdated ; + map[curr.id].isOutdated = !!curr.isOutdated; } if (!map[curr.id].note) { - map[curr.id].note = !!curr.note ; + map[curr.id].note = !!curr.note; } return map; diff --git a/src/lib/snyk-test/legacy.ts b/src/lib/snyk-test/legacy.ts index 6ef6face4c3..ad22a78ed92 100644 --- a/src/lib/snyk-test/legacy.ts +++ b/src/lib/snyk-test/legacy.ts @@ -151,6 +151,27 @@ function convertTestDepGraphResultToLegacy( } } + const dockerRes = result.docker as any; + + if (dockerRes && dockerRes.binariesVulns) { + const binariesVulns = dockerRes.binariesVulns; + for (const pkgInfo of _.values(binariesVulns.affectedPkgs)) { + for (const pkgIssue of _.values(pkgInfo.issues)) { + const pkgAndVersion = + pkgInfo.pkg.name + '@' + pkgInfo.pkg.version as string; + const annotatedIssue = Object.assign({}, binariesVulns.issuesData[pkgIssue.issueId], { + from: ['Upstream', pkgAndVersion], + upgradePath: [], + isUpgradable: false, + isPatchable: false, + name: pkgInfo.pkg.name, + version: pkgInfo.pkg.version as string, + }); + vulns.push(annotatedIssue); + } + } + } + const meta = res.meta || {}; severityThreshold = (severityThreshold === 'low') ? undefined : severityThreshold; diff --git a/src/lib/snyk-test/run-test.ts b/src/lib/snyk-test/run-test.ts index 2133362e286..58ab7d70be8 100644 --- a/src/lib/snyk-test/run-test.ts +++ b/src/lib/snyk-test/run-test.ts @@ -32,12 +32,14 @@ async function runTest(packageManager: string, root: string , options): Promise< const payload = await assemblePayload(root, options, policyLocations); const filesystemPolicy = payload.body && !!payload.body.policy; const depGraph = payload.body && payload.body.depGraph; - await spinner(spinnerLbl); let res = await sendPayload(payload, hasDevDependencies); - if (depGraph) { - res = convertTestDepGraphResultToLegacy(res, depGraph, packageManager, options.severityThreshold); + res = convertTestDepGraphResultToLegacy( + res, + depGraph, + packageManager, + options.severityThreshold); } analytics.add('vulns-pre-policy', res.vulnerabilities.length); @@ -186,6 +188,8 @@ async function assembleLocalPayload(root, options, policyLocations): Promise { const pkg = moduleToObject(root); debug('testing remote: %s', pkg.name + '@' + pkg.version); diff --git a/test/acceptance/cli.acceptance.test.ts b/test/acceptance/cli.acceptance.test.ts index b7bf6c679f9..f76807d055a 100644 --- a/test/acceptance/cli.acceptance.test.ts +++ b/test/acceptance/cli.acceptance.test.ts @@ -1563,6 +1563,109 @@ test('`test foo:latest --docker` supports custom policy', async (t) => { t.equal(policyString, expected, 'sends correct policy'); }); +test('`test foo:latest --docker with binaries`', async (t) => { + const plugin = { + inspect: () => { + return Promise.resolve({ + plugin: { + packageManager: 'deb', + }, + package: { + docker: { + binaries: { + Analysis: [{name: 'node', version: '5.10.1'}], + }, + }, + }, + }); + }, + }; + const spyPlugin = sinon.spy(plugin, 'inspect'); + + const loadPlugin = sinon.stub(plugins, 'loadPlugin'); + loadPlugin.withArgs(sinon.match.any, sinon.match({docker: true})).returns(plugin); + t.teardown(loadPlugin.restore); + + const res = await cli.test('foo:latest', { + docker: true, + org: 'explicit-org', + }); + const req = server.popRequest(); + t.equal(req.method, 'POST', 'makes POST request'); + t.match(req.url, '/test-dep-graph', 'posts to correct url'); + t.equal(req.body.depGraph.pkgManager.name, 'deb'); + t.match(req.body.docker.binaries, [{name: 'node', version: '5.10.1'}], + 'posts docker binaries'); + t.same(spyPlugin.getCall(0).args, + ['foo:latest', null, { + args: null, + file: null, + docker: true, + org: 'explicit-org', + packageManager: null, + path: 'foo:latest', + showVulnPaths: true, + }], 'calls docker plugin with expected arguments'); +}); + +test('`test foo:latest --docker with binaries vulnerabilities`', async (t) => { + const plugin = { + inspect: () => { + return Promise.resolve({ + plugin: { + packageManager: 'deb', + }, + package: { + name: 'docker-image', + dependencies: { + 'apt/libapt-pkg5.0': { + version: '1.6.3ubuntu0.1', + dependencies: { + 'bzip2/libbz2-1.0': { + version: '1.0.6-8.1', + }, + }, + }, + 'bzip2/libbz2-1.0': { + version: '1.0.6-8.1', + }, + }, + docker: { + binaries: { + Analysis: [{name: 'node', version: '5.10.1'}], + }, + }, + }, + }); + }, + }; + const spyPlugin = sinon.spy(plugin, 'inspect'); + + const loadPlugin = sinon.stub(plugins, 'loadPlugin'); + loadPlugin.withArgs(sinon.match.any, sinon.match({docker: true})).returns(plugin); + t.teardown(loadPlugin.restore); + + const vulns = require('./fixtures/docker/find-result-binaries.json'); + server.setNextResponse(vulns); + + try { + await cli.test('foo:latest', { + docker: true, + org: 'explicit-org', + }); + t.fail('should have found vuln'); + } catch (err) { + const msg = err.message; + console.log(msg); + t.match(msg, 'Tested 2 dependencies for known vulnerabilities, found 2 vulnerabilities'); + t.match(msg, 'From: bzip2/libbz2-1.0@1.0.6-8.1'); + t.match(msg, 'From: apt/libapt-pkg5.0@1.6.3ubuntu0.1 > bzip2/libbz2-1.0@1.0.6-8.1'); + t.match(msg, 'Info: http://localhost:12345/vuln/SNYK-UPSTREAM-NODE-72359'); + t.false(msg.includes('vulnerable paths'), + 'docker should not includes number of vulnerable paths'); + } +}); + test('`test --policy-path`', async (t) => { t.plan(3); diff --git a/test/acceptance/fixtures/docker/find-result-binaries.json b/test/acceptance/fixtures/docker/find-result-binaries.json new file mode 100644 index 00000000000..d8c2456ac76 --- /dev/null +++ b/test/acceptance/fixtures/docker/find-result-binaries.json @@ -0,0 +1,194 @@ +{ + "result": { + "affectedPkgs": { + "bzip2/libbz2-1.0@1.0.6-8.1": { + "pkg": { + "version": "1.0.6-8.1", + "name": "bzip2/libbz2-1.0" + }, + "issues": { + "SNYK-LINUX-BZIP2-106947": { + "issueId": "SNYK-LINUX-BZIP2-106947", + "fixInfo": { + "upgradePaths": [], + "isPatchable": false + } + } + } + } + }, + "issuesData": { + "SNYK-LINUX-BZIP2-106947": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "alternativeIds": [], + "creationTime": "2018-06-27T16:12:23.571063Z", + "credit": [ + "" + ], + "cvssScore": 6.5, + "description": "## Overview\nUse-after-free vulnerability in bzip2recover in bzip2 1.0.6 allows remote attackers to cause a denial of service (crash) via a crafted bzip2 file, related to block ends set to before the start of the block.\n\n## References\n- [GENTOO](https://security.gentoo.org/glsa/201708-08)\n- [CONFIRM](https://bugzilla.redhat.com/show_bug.cgi?id=1319648)\n- [SECTRACK](http://www.securitytracker.com/id/1036132)\n- [BID](http://www.securityfocus.com/bid/91297)\n- [CONFIRM](http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html)\n- [MLIST](http://www.openwall.com/lists/oss-security/2016/06/20/1)\n", + "disclosureTime": null, + "id": "SNYK-LINUX-BZIP2-106947", + "identifiers": { + "CVE": [ + "CVE-2016-3189" + ], + "CWE": [] + }, + "internal": {}, + "language": "linux", + "modificationTime": "2018-10-22T04:31:58.564093Z", + "packageManager": "linux", + "packageName": "bzip2", + "patches": [], + "publicationTime": "2016-06-30T17:59:00Z", + "references": [ + { + "title": "GENTOO", + "url": "https://security.gentoo.org/glsa/201708-08" + }, + { + "title": "CONFIRM", + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1319648" + }, + { + "title": "SECTRACK", + "url": "http://www.securitytracker.com/id/1036132" + }, + { + "title": "BID", + "url": "http://www.securityfocus.com/bid/91297" + }, + { + "title": "CONFIRM", + "url": "http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html" + }, + { + "title": "MLIST", + "url": "http://www.openwall.com/lists/oss-security/2016/06/20/1" + } + ], + "semver": { + "vulnerableByDistro": { + "alpine:3.4": [ + "<1.0.6-r5" + ], + "alpine:3.5": [ + "<1.0.6-r5" + ], + "alpine:3.6": [ + "<1.0.6-r5" + ], + "alpine:3.7": [ + "<1.0.6-r5" + ], + "alpine:3.8": [ + "<1.0.6-r5" + ], + "debian:10": [ + "<1.0.6-8.1" + ], + "debian:8": [ + "*" + ], + "debian:9": [ + "<1.0.6-8.1" + ], + "debian:unstable": [ + "<1.0.6-8.1" + ], + "ubuntu:12.04": [ + "*" + ], + "ubuntu:14.04": [ + "*" + ], + "ubuntu:16.04": [ + "*" + ], + "ubuntu:18.04": [ + "*" + ] + }, + "vulnerable": [ + "*" + ] + }, + "severity": "low", + "title": "Denial of Service (DoS)" + } + }, + "docker": { + "binariesVulns": { + "affectedPkgs": { + "node/5.10.1": { + "pkg": { + "version": "5.10.1", + "name": "node" + }, + "issues": { + "SNYK-UPSTREAM-BZIP2-106947": { + "issueId": "SNYK-UPSTREAM-BZIP2-106947", + "fixInfo": { + "upgradePaths": [], + "isPatchable": false + } + } + } + } + }, + "issuesData": { + "SNYK-UPSTREAM-BZIP2-106947": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", + "alternativeIds": [], + "creationTime": "2018-09-12T13:04:09.124530Z", + "credit": [ + "Unknown" + ], + "cvssScore": 7.3, + "description": "## Overview\n[node](https://nodejs.org/en/) is a JavaScript runtime built on Chrome's V8 JavaScript engine.\r\n\r\nAffected versions of this package are vulnerable to Denial of Service (out-of-bounds read) due to improperly loading array elements.\r\n\r\n## Details\r\nDenial of Service (DoS) describes a family of attacks, all aimed at making a system inaccessible to its intended and legitimate users.\r\n\r\nUnlike other vulnerabilities, DoS attacks usually do not aim at breaching security. Rather, they are focused on making websites and services unavailable to genuine users resulting in downtime.\r\n\r\nOne popular Denial of Service vulnerability is DDoS (a Distributed Denial of Service), an attack that attempts to clog network pipes to the system by generating a large volume of traffic from many machines.\r\n\r\nWhen it comes to open source libraries, DoS vulnerabilities allow attackers to trigger such a crash or crippling of the service by using a flaw either in the application code or from the use of open source libraries.\r\n\r\nTwo common types of DoS vulnerabilities:\r\n\r\n* High CPU/Memory Consumption- An attacker sending crafted requests that could cause the system to take a disproportionate amount of time to process. For example, [commons-fileupload:commons-fileupload](SNYK-JAVA-COMMONSFILEUPLOAD-30082).\r\n\r\n* Crash - An attacker sending crafted requests that could cause the system to crash. For Example, [npm `ws` package](npm:ws:20171108)\r\n\r\n## Remediation\r\nUpgrade `node` to versions 5.1.1, 4.2.3 or higher.\n\n## References\n- [NVD](https://nvd.nist.gov/vuln/detail/CVE-2015-6764)\n", + "disclosureTime": null, + "functions": [], + "id": "SNYK-UPSTREAM-NODE-72359", + "identifiers": { + "CVE": [ + "CVE-2015-6764" + ], + "CWE": [] + }, + "internal": { + "content": "templated", + "flags": { + "premium": false + }, + "source": "external" + }, + "language": "upstream", + "methods": [], + "modificationTime": "2018-12-12T17:12:34.427465Z", + "moduleName": "node", + "packageManager": "upstream", + "packageName": "node", + "patches": [], + "publicationTime": "2018-12-12T17:12:34.375733Z", + "references": [ + { + "title": "NVD", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2015-6764" + } + ], + "semver": { + "vulnerable": [ + ">=4.0.0 <4.2.3", + ">=5.0.0 <5.1.1" + ] + }, + "severity": "high", + "title": "Denial of Service" + } + } + } + } + }, + "meta": {} + } \ No newline at end of file