diff --git a/src/cli/commands/monitor.ts b/src/cli/commands/monitor.ts index 54bafcfd65..4712efaa6f 100644 --- a/src/cli/commands/monitor.ts +++ b/src/cli/commands/monitor.ts @@ -11,7 +11,6 @@ import chalk from 'chalk'; import * as pathUtil from 'path'; import * as spinner from '../../lib/spinner'; -import request = require('../../lib/request'); import * as detect from '../../lib/detect'; import * as plugins from '../../lib/plugins'; import {ModuleInfo} from '../../lib/module-info'; // TODO(kyegupov): fix import @@ -28,14 +27,10 @@ import { UnsupportedFeatureFlagError, } from '../../lib/errors'; import { legacyPlugin as pluginApi } from '@snyk/cli-interface'; +import { isFeatureFlagSupportedForOrg } from '../../lib/feature-flags'; const SEPARATOR = '\n-------------------------------------------------------\n'; -interface OrgFeatureFlagResponse { - ok: boolean; - userMessage?: string; -} - interface GoodResult { ok: true; data: string; @@ -292,17 +287,3 @@ function formatMonitorOutput( packageManager, })) : strOutput; } - -async function isFeatureFlagSupportedForOrg(featureFlag: string): Promise { - const response = await request({ - method: 'GET', - headers: { - Authorization: `token ${snyk.api}`, - }, - url: `${config.API}/cli-config/feature-flags/${featureFlag}`, - gzip: true, - json: true, - }); - - return (response as any).body; -} diff --git a/src/cli/commands/test/formatters/legacy-format-issue.ts b/src/cli/commands/test/formatters/legacy-format-issue.ts index 625daa4ab8..934c65cbeb 100644 --- a/src/cli/commands/test/formatters/legacy-format-issue.ts +++ b/src/cli/commands/test/formatters/legacy-format-issue.ts @@ -3,8 +3,12 @@ import chalk from 'chalk'; import * as config from '../../../../lib/config'; import {Options, TestOptions} from '../../../../lib/types'; import {isLocalFolder} from '../../../../lib/detect'; -import { WIZARD_SUPPORTED_PACKAGE_MANAGERS } from '../../../../lib/package-managers'; +import { + WIZARD_SUPPORTED_PACKAGE_MANAGERS, + SupportedPackageManagers, + PINNING_SUPPORTED_PACKAGE_MANAGERS } from '../../../../lib/package-managers'; import { GroupedVuln } from '../../../../lib/snyk-test/legacy'; +import parsePackageNameVersion = require('snyk-module'); export function formatIssues(vuln: GroupedVuln, options: Options & TestOptions) { const vulnID = vuln.list[0].id; @@ -38,7 +42,7 @@ export function formatIssues(vuln: GroupedVuln, options: Options & TestOptions) ? createTruncatedVulnsPathsText(vuln.list) : '', extraInfo: vuln.note ? chalk.bold('\n Note: ' + vuln.note) : '', remediationInfo: vuln.metadata.type !== 'license' && localPackageTest - ? createRemediationText(vuln, packageManager) + ? createRemediationText(vuln, packageManager, !!options.pinningSupported) : '', fixedIn: options.docker ? createFixedInText(vuln) : '', dockerfilePackage: options.docker ? dockerfileInstructionText(vuln) : '', @@ -135,12 +139,25 @@ function createFixedInText(vuln: any): string { return ''; } -function createRemediationText(vuln, packageManager) { +function createRemediationText( + vuln: GroupedVuln, + packageManager: SupportedPackageManagers, + pinningSupported: boolean, +): string { let wizardHintText = ''; if (WIZARD_SUPPORTED_PACKAGE_MANAGERS.includes(packageManager)) { wizardHintText = 'Run `snyk wizard` to explore remediation options.'; } - + if (pinningSupported + && (vuln.nearestFixedInVersion || vuln.fixedIn) + && PINNING_SUPPORTED_PACKAGE_MANAGERS.includes(packageManager) + ) { + const toVersion = vuln.nearestFixedInVersion || vuln.fixedIn.join(' or '); + const transitive = vuln.list.every((i) => i.from.length > 2); + + const action = transitive ? 'Pin the transitive' : 'Update the'; + return chalk.bold(`\n Remediation: \n ${action} dependency ${vuln.name} to version ${toVersion}`); + } if (vuln.isFixable === true) { const upgradePathsArray = _.uniq(vuln.list.map((v) => { const shouldUpgradeItself = !!v.upgradePath[0]; @@ -153,7 +170,7 @@ function createRemediationText(vuln, packageManager) { const selfUpgradeInfo = (v.upgradePath.length > 0) ? ` (triggers upgrades to ${ v.upgradePath.join(' > ')})` : ''; - const testedPackageName = v.upgradePath[0].split('@'); + const testedPackageName = parsePackageNameVersion(v.upgradePath[0] as string); return `You've tested an outdated version of ${testedPackageName[0]}.` + + ` Upgrade to ${v.upgradePath[0]}${selfUpgradeInfo}`; } diff --git a/src/cli/commands/test/formatters/remediation-based-format-issues.ts b/src/cli/commands/test/formatters/remediation-based-format-issues.ts index 4ad0ee9eab..ef1abdc260 100644 --- a/src/cli/commands/test/formatters/remediation-based-format-issues.ts +++ b/src/cli/commands/test/formatters/remediation-based-format-issues.ts @@ -1,9 +1,11 @@ -import * as _ from 'lodash'; import chalk from 'chalk'; import * as config from '../../../../lib/config'; import { TestOptions } from '../../../../lib/types'; -import { RemediationResult, PatchRemediation, - DependencyUpdates, IssueData, SEVERITY, GroupedVuln } from '../../../../lib/snyk-test/legacy'; +import { + RemediationChanges, PatchRemediation, + DependencyUpdates, IssueData, SEVERITY, GroupedVuln, + DependencyPins, +} from '../../../../lib/snyk-test/legacy'; interface BasicVulnInfo { title: string; @@ -12,13 +14,23 @@ interface BasicVulnInfo { name: string; version: string; fixedIn: string[]; + paths: string[][]; +} + +interface TopLevelPackageUpgrade { + name: string; + version: string; +} + +interface UpgradesByCulprit { + [culpritNameAndVersion: string]: TopLevelPackageUpgrade[]; } export function formatIssuesWithRemediation( vulns: GroupedVuln[], - remediationInfo: RemediationResult, + remediationInfo: RemediationChanges, options: TestOptions, - ): string[] { +): string[] { const basicVulnInfo: { [name: string]: BasicVulnInfo, @@ -32,13 +44,37 @@ export function formatIssuesWithRemediation( name: vuln.name, version: vuln.version, fixedIn: vuln.fixedIn, + paths: vuln.list.map((v) => v.from), }; } const results = [chalk.bold.white('Remediation advice')]; - const upgradeTextArray = constructUpgradesText(remediationInfo.upgrade, basicVulnInfo); - if (upgradeTextArray.length > 0) { - results.push(upgradeTextArray.join('\n')); + if (remediationInfo.pin && Object.keys(remediationInfo.pin).length) { + const upgragesByCulprit: UpgradesByCulprit = {}; + for (const topLvlPkg of Object.keys(remediationInfo.upgrade)) { + for (const targetPkgStr of remediationInfo.upgrade[topLvlPkg].upgrades) { + if (!upgragesByCulprit[targetPkgStr]) { + upgragesByCulprit[targetPkgStr] = []; + } + upgragesByCulprit[targetPkgStr].push({ + name: topLvlPkg, + version: remediationInfo.upgrade[topLvlPkg].upgradeTo, + }); + } + } + const upgradeTextArray = constructPinOrUpgradesText(remediationInfo.pin, upgragesByCulprit, basicVulnInfo); + if (upgradeTextArray.length > 0) { + results.push(upgradeTextArray.join('\n')); + } + const allVulnIds = new Set(); + Object.keys(remediationInfo.pin).forEach( + (name) => remediationInfo.pin[name].issues.forEach((vid) => allVulnIds.add(vid))); + remediationInfo.unresolved = remediationInfo.unresolved.filter((issue) => !allVulnIds.has(issue.id)); + } else { + const upgradeTextArray = constructUpgradesText(remediationInfo.upgrade, basicVulnInfo); + if (upgradeTextArray.length > 0) { + results.push(upgradeTextArray.join('\n')); + } } const patchedTextArray = constructPatchesText(remediationInfo.patch, basicVulnInfo); @@ -63,7 +99,7 @@ function constructPatchesText( basicVulnInfo: { [name: string]: BasicVulnInfo; }, - ): string[] { +): string[] { if (!(Object.keys(patches).length > 0)) { return []; @@ -74,12 +110,12 @@ function constructPatchesText( const packageAtVersion = `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`; const patchedText = `\n Patch available for ${chalk.bold.whiteBright(packageAtVersion)}\n`; const thisPatchFixes = - formatIssue( - id, - basicVulnInfo[id].title, - basicVulnInfo[id].severity, - basicVulnInfo[id].isNew, - `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`); + formatIssue( + id, + basicVulnInfo[id].title, + basicVulnInfo[id].severity, + basicVulnInfo[id].isNew, + `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`); patchedTextArray.push(patchedText + thisPatchFixes); } @@ -91,31 +127,115 @@ function constructUpgradesText( basicVulnInfo: { [name: string]: BasicVulnInfo; }, - ): string[] { +): string[] { if (!(Object.keys(upgrades).length > 0)) { return []; } - const upgradeTextArray = [chalk.bold.green('\nUpgradable Issues:')]; + const upgradeTextArray = [chalk.bold.green('\nIssues to fix by upgrading:')]; for (const upgrade of Object.keys(upgrades)) { - const upgradeDepTo = _.get(upgrades, [upgrade, 'upgradeTo']); - const vulnIds = _.get(upgrades, [upgrade, 'vulns']); + const data = upgrades[upgrade]; + const upgradeDepTo = data.upgradeTo; + const vulnIds = data.vulns; const upgradeText = - `\n Upgrade ${chalk.bold.whiteBright(upgrade)} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix\n`; + `\n Upgrade ${chalk.bold.whiteBright(upgrade)} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix\n`; const thisUpgradeFixes = vulnIds .map((id) => formatIssue( - id, - basicVulnInfo[id].title, - basicVulnInfo[id].severity, - basicVulnInfo[id].isNew, - `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`)) + id, + basicVulnInfo[id].title, + basicVulnInfo[id].severity, + basicVulnInfo[id].isNew, + `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`)) .join('\n'); upgradeTextArray.push(upgradeText + thisUpgradeFixes); } return upgradeTextArray; } +function constructPinOrUpgradesText( + pins: DependencyPins, + upgradesByCulprit: UpgradesByCulprit, + basicVulnInfo: { + [name: string]: BasicVulnInfo; + }, +): string[] { + + if (!(Object.keys(pins).length)) { + return []; + } + + const thisUpgradeFixes = (vulnIds: string[]) => ( + vulnIds.map((id) => formatIssue( + id, + basicVulnInfo[id].title, + basicVulnInfo[id].severity, + basicVulnInfo[id].isNew, + `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`)) + .join('\n') + ); + + // First, direct upgrades + const upgradeTextArray: string[] = []; + + const upgradeables = Object.keys(pins).filter((name) => !pins[name].isTransitive); + if (upgradeables.length) { + upgradeTextArray.push(chalk.bold.green('\nIssues to fix by upgrading existing dependencies:')); + + for (const pin of upgradeables) { + const data = pins[pin]; + const vulnIds = data.issues; + const upgradeDepTo = data.upgradeTo; + const upgradeText = + `\n Upgrade ${chalk.bold.whiteBright(pin)} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix\n`; + upgradeTextArray.push(upgradeText); + upgradeTextArray.push(thisUpgradeFixes(vulnIds)); + } + } + + // Second, pins + const pinables = Object.keys(pins).filter((name) => pins[name].isTransitive); + + if (pinables.length) { + upgradeTextArray.push(chalk.bold.green('\nIssues to fix by pinning sub-dependencies:')); + + for (const pin of pinables) { + const data = pins[pin]; + const vulnIds = data.issues; + const upgradeDepTo = data.upgradeTo; + const upgradeText = + `\n Pin ${chalk.bold.whiteBright(pin)} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix\n`; + upgradeTextArray.push(upgradeText); + upgradeTextArray.push(thisUpgradeFixes(vulnIds)); + const allPaths = new Set(); + for (const vid of vulnIds) { + for (const path of basicVulnInfo[vid].paths) { + allPaths.add(path.slice(1).join(' > ')); + } + } + upgradeTextArray.push(allPaths.size === 1 + ? ` (introduced by ${allPaths.keys().next().value})` + : ` (introduced by ${allPaths.keys().next().value} and ${allPaths.size - 1} other path(s))`); + const topLevelUpgradesSet = new Set(); + for (const vid of vulnIds) { + const maybeTopLevelUpgrades = upgradesByCulprit[pin + '@' + basicVulnInfo[vid].version]; + if (maybeTopLevelUpgrades) { + for (const topLvlPkg of maybeTopLevelUpgrades) { + const setKey = `${topLvlPkg.name}\n${topLvlPkg.version}`; + if (!topLevelUpgradesSet.has(setKey)) { + topLevelUpgradesSet.add(setKey); + upgradeTextArray.push(' The issues above can also be fixed by upgrading top-level dependency ' + + `${topLvlPkg.name} to ${topLvlPkg.version}`); + } + } + } + } + } + } + + return upgradeTextArray; +} + function constructUnfixableText(unresolved: IssueData[]) { if (!(unresolved.length > 0)) { return []; @@ -161,7 +281,7 @@ function formatIssue( return severitiesColourMapping[severity].colorFunc( ` ✗ ${chalk.bold(title)}${newBadge} [${titleCaseText(severity)} Severity]`, - ) + `[${config.ROOT}/vuln/${id}]` + name; + ) + `[${config.ROOT}/vuln/${id}]` + name; } function titleCaseText(text) { diff --git a/src/cli/commands/test/index.ts b/src/cli/commands/test/index.ts index 15b4a1c0ec..9f7a56d4b7 100644 --- a/src/cli/commands/test/index.ts +++ b/src/cli/commands/test/index.ts @@ -15,6 +15,7 @@ import { LegacyVulnApiResult, SEVERITY, GroupedVuln, VulnMetaData } from '../../ import { formatIssues } from './formatters/legacy-format-issue'; import { WIZARD_SUPPORTED_PACKAGE_MANAGERS } from '../../../lib/package-managers'; import { formatIssuesWithRemediation } from './formatters/remediation-based-format-issues'; +import { isFeatureFlagSupportedForOrg } from '../../../lib/feature-flags'; const debug = Debug('snyk'); const SEPARATOR = '\n-------------------------------------------------------\n'; @@ -142,8 +143,15 @@ async function test(...args: MethodArgs): Promise { throw err; } + const pinningSupported = + results.find((r) => (r as LegacyVulnApiResult).packageManager === 'pip') && + (await isFeatureFlagSupportedForOrg('pythonPinning')).ok; + let response = results - .map((unused, i) => displayResult(results[i] as LegacyVulnApiResult, resultOptions[i])) + .map((unused, i) => { + resultOptions[i].pinningSupported = pinningSupported; + return displayResult(results[i] as LegacyVulnApiResult, resultOptions[i]); + }) .join(`\n${SEPARATOR}`); if (notSuccess) { @@ -217,7 +225,7 @@ function summariseErrorResults(errorResults) { return ''; } -function displayResult(res, options: Options & TestOptions) { +function displayResult(res: LegacyVulnApiResult, options: Options & TestOptions) { const meta = metaForDisplay(res, options) + '\n\n'; const dockerAdvice = dockerRemediationForDisplay(res); const packageManager = options.packageManager; diff --git a/src/lib/feature-flags.ts b/src/lib/feature-flags.ts new file mode 100644 index 0000000000..7173ebc01f --- /dev/null +++ b/src/lib/feature-flags.ts @@ -0,0 +1,22 @@ +import request = require('./request'); +import snyk = require('.'); // TODO(kyegupov): fix import +import * as config from './config'; + +interface OrgFeatureFlagResponse { + ok: boolean; + userMessage?: string; +} + +export async function isFeatureFlagSupportedForOrg(featureFlag: string): Promise { + const response = await request({ + method: 'GET', + headers: { + Authorization: `token ${snyk.api}`, + }, + url: `${config.API}/cli-config/feature-flags/${featureFlag}`, + gzip: true, + json: true, + }); + + return (response as any).body; +} diff --git a/src/lib/package-managers.ts b/src/lib/package-managers.ts index e870392447..c837a21cc6 100644 --- a/src/lib/package-managers.ts +++ b/src/lib/package-managers.ts @@ -26,3 +26,7 @@ export const PROTECT_SUPPORTED_PACKAGE_MANAGERS: SupportedPackageManagers[] = ['yarn', 'npm']; export const GRAPH_SUPPORTED_PACKAGE_MANAGERS: SupportedPackageManagers[] = ['npm', 'sbt']; +// For ecosystems with a flat set of libraries (e.g. Python, JVM), one can +// "pin" a transitive dependency +export const PINNING_SUPPORTED_PACKAGE_MANAGERS: SupportedPackageManagers[] + = ['pip']; diff --git a/src/lib/snyk-test/legacy.ts b/src/lib/snyk-test/legacy.ts index 4179134260..0e95502eb5 100644 --- a/src/lib/snyk-test/legacy.ts +++ b/src/lib/snyk-test/legacy.ts @@ -44,6 +44,7 @@ export interface GroupedVuln { isNew: boolean; name: string; version: string; + isFixable: boolean; fixedIn: string[]; dockerfileInstruction: string; dockerBaseImage: string; @@ -75,7 +76,7 @@ interface AnnotatedIssue extends IssueData { credit: any; name: string; version: string; - from: Array; + from: string[]; upgradePath: Array; isUpgradable: boolean; isPatchable: boolean; @@ -109,12 +110,15 @@ export interface LegacyVulnApiResult { packageManager: string; ignoreSettings: object | null; summary: string; - docker?: {baseImage?: any}; + docker?: { + baseImage?: any; + binariesVulns?: unknown; + }; severityThreshold?: string; filesystemPolicy?: boolean; uniqueCount?: any; - remediation?: RemediationResult; + remediation?: RemediationChanges; } interface UpgradePathItem { @@ -153,7 +157,7 @@ interface TestDepGraphResult { binariesVulns?: TestDepGraphResult; baseImage?: any; }; - remediation?: RemediationResult; + remediation?: RemediationChanges; } interface TestDepGraphMeta { @@ -204,15 +208,26 @@ export interface DependencyUpdates { [from: string]: UpgradeRemediation; } +export interface PinRemediation { + upgradeTo: string; + issues: string[]; + isTransitive: boolean; +} + +export interface DependencyPins { + [name: string]: PinRemediation; +} + // Remediation changes to be applied to the project, // including information on all and unresolved issues. -export interface RemediationResult { +export interface RemediationChanges { unresolved: IssueData[]; upgrade: DependencyUpdates; patch: { [name: string]: PatchRemediation; }; ignore: unknown; + pin: DependencyPins; } function convertTestDepGraphResultToLegacy( diff --git a/src/lib/snyk-test/run-test.ts b/src/lib/snyk-test/run-test.ts index 86cd0d8521..b71fcb8cbb 100644 --- a/src/lib/snyk-test/run-test.ts +++ b/src/lib/snyk-test/run-test.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import fs = require('then-fs'); +import * as fs from 'fs'; import pathUtil = require('path'); import moduleToObject = require('snyk-module'); import * as depGraphLib from '@snyk/dep-graph'; diff --git a/src/lib/types.ts b/src/lib/types.ts index 503f9514e2..6385a6f18a 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -24,6 +24,7 @@ export interface TestOptions { traverseNodeModules: boolean; interactive: boolean; 'prune-repeated-subdependencies'?: boolean; + pinningSupported?: boolean; } export interface ProtectOptions { loose: boolean; diff --git a/test/acceptance/cli.acceptance.test.ts b/test/acceptance/cli.acceptance.test.ts index de26c2a082..61900fa8f4 100644 --- a/test/acceptance/cli.acceptance.test.ts +++ b/test/acceptance/cli.acceptance.test.ts @@ -32,8 +32,11 @@ const after = tap.runOnly ? only : test; // Should be after `process.env` setup. import * as plugins from '../../src/lib/plugins'; import { legacyPlugin as pluginApi } from '@snyk/cli-interface'; -import { AuthFailedError } from '../../src/lib/errors/authentication-failed-error'; -import { InternalServerError } from '../../src/lib/errors'; +import { fail } from 'assert'; + +function loadJson(filename: string) { + return JSON.parse(fs.readFileSync(filename, 'utf-8')) +} // @later: remove this config stuff. // Was copied straight from ../src/cli-server.js @@ -1224,7 +1227,10 @@ test('`test pip-app --file=requirements.txt`', async (t) => { await cli.test('pip-app', { file: 'requirements.txt', }); - const req = server.popRequest(); + let req = server.popRequest(); + t.equal(req.method, 'GET', 'makes GET request'); + t.match(req.url, 'cli-config/feature-flags/pythonPinning', 'to correct url'); + req = server.popRequest(); t.equal(req.method, 'POST', 'makes POST request'); t.equal(req.headers['x-snyk-cli-version'], versionNumber, 'sends version number'); t.match(req.url, '/test-dep-graph', 'posts to correct url'); @@ -1266,7 +1272,10 @@ test('`test pipenv-app --file=Pipfile`', async (t) => { await cli.test('pipenv-app', { file: 'Pipfile', }); - const req = server.popRequest(); + let req = server.popRequest(); + t.equal(req.method, 'GET', 'makes GET request'); + t.match(req.url, 'cli-config/feature-flags/pythonPinning', 'to correct url'); + req = server.popRequest(); t.equal(req.method, 'POST', 'makes POST request'); t.equal(req.headers['x-snyk-cli-version'], versionNumber, 'sends version number'); t.match(req.url, '/test-dep-graph', 'posts to correct url'); @@ -1284,6 +1293,104 @@ test('`test pipenv-app --file=Pipfile`', async (t) => { }], 'calls python plugin'); }); +test('`test pip-app-transitive-vuln --file=requirements.txt (actionableCliRemediation=false)`', async (t) => { + chdirWorkspaces(); + const plugin = { + async inspect() { + return loadJson('./pip-app-transitive-vuln/inspect-result.json'); + }, + }; + const spyPlugin = sinon.spy(plugin, 'inspect'); + + const loadPlugin = sinon.stub(plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + loadPlugin + .withArgs('pip') + .returns(plugin); + + server.setNextResponses([ + {ok: true}, + loadJson('./pip-app-transitive-vuln/response-without-remediation.json'), + ]); + t.teardown(() => server.setNextResponses([])); + try { + await cli.test('pip-app-transitive-vuln', { + file: 'requirements.txt', + }); + t.fail('should throw, since there are vulns'); + } catch (e) { + t.equals(e.message, + fs.readFileSync('pip-app-transitive-vuln/cli-output.txt', 'utf8')); + } + let req = server.popRequest(); + t.equal(req.method, 'GET', 'makes GET request'); + t.match(req.url, 'cli-config/feature-flags/pythonPinning', 'to correct url'); + req = server.popRequest(); + t.equal(req.method, 'POST', 'makes POST request'); + t.equal(req.headers['x-snyk-cli-version'], versionNumber, 'sends version number'); + t.match(req.url, '/test-dep-graph', 'posts to correct url'); + t.equal(req.body.depGraph.pkgManager.name, 'pip'); + t.same(spyPlugin.getCall(0).args, + ['pip-app-transitive-vuln', 'requirements.txt', { + args: null, + file: 'requirements.txt', + org: null, + projectName: null, + packageManager: 'pip', + path: 'pip-app-transitive-vuln', + showVulnPaths: true, + }], 'calls python plugin'); +}); + +test('`test pip-app-transitive-vuln --file=requirements.txt (actionableCliRemediation=true)`', async (t) => { + chdirWorkspaces(); + const plugin = { + async inspect() { + return loadJson('./pip-app-transitive-vuln/inspect-result.json'); + }, + }; + const spyPlugin = sinon.spy(plugin, 'inspect'); + + const loadPlugin = sinon.stub(plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + loadPlugin + .withArgs('pip') + .returns(plugin); + + server.setNextResponses([ + {ok: true}, + loadJson('./pip-app-transitive-vuln/response-with-remediation.json'), + ]); + t.teardown(() => server.setNextResponses([])); + try { + await cli.test('pip-app-transitive-vuln', { + file: 'requirements.txt', + }); + t.fail('should throw, since there are vulns'); + } catch (e) { + t.equals(e.message, + fs.readFileSync('pip-app-transitive-vuln/cli-output-actionable-remediation.txt', 'utf8')); + } + let req = server.popRequest(); + t.equal(req.method, 'GET', 'makes GET request'); + t.match(req.url, 'cli-config/feature-flags/pythonPinning', 'to correct url'); + req = server.popRequest(); + t.equal(req.method, 'POST', 'makes POST request'); + t.equal(req.headers['x-snyk-cli-version'], versionNumber, 'sends version number'); + t.match(req.url, '/test-dep-graph', 'posts to correct url'); + t.equal(req.body.depGraph.pkgManager.name, 'pip'); + t.same(spyPlugin.getCall(0).args, + ['pip-app-transitive-vuln', 'requirements.txt', { + args: null, + file: 'requirements.txt', + org: null, + projectName: null, + packageManager: 'pip', + path: 'pip-app-transitive-vuln', + showVulnPaths: true, + }], 'calls python plugin'); +}); + test('`test nuget-app --file=project.assets.json`', async (t) => { chdirWorkspaces(); const plugin = { diff --git a/test/acceptance/fake-server.ts b/test/acceptance/fake-server.ts index b196997887..1a5d848e91 100644 --- a/test/acceptance/fake-server.ts +++ b/test/acceptance/fake-server.ts @@ -2,10 +2,11 @@ import * as restify from 'restify'; interface FakeServer extends restify.Server { _reqLog: restify.Request[]; - _nextResponse?: restify.Response; + _nextResponseQueue: restify.Response[]; _nextStatusCode?: number; popRequest: () => restify.Request; setNextResponse: (r: any) => void; + setNextResponses: (r: any[]) => void; setNextStatusCodeAndResponse: (c: number, r: any) => void; } @@ -14,6 +15,7 @@ export function fakeServer(root, apikey) { name: 'snyk-mock-server', version: '1.0.0', }) as FakeServer; + server._nextResponseQueue = []; server._reqLog = []; server.popRequest = function () { return server._reqLog.pop()!; @@ -53,11 +55,10 @@ export function fakeServer(root, apikey) { }); server.use(function (req, res, next) { - if (!server._nextResponse && !server._nextStatusCode) { + if (!server._nextResponseQueue.length && !server._nextStatusCode) { return next(); } - var response = server._nextResponse; - delete server._nextResponse; + var response = server._nextResponseQueue.pop(); if (server._nextStatusCode) { const code = server._nextStatusCode; delete server._nextStatusCode; @@ -144,12 +145,16 @@ export function fakeServer(root, apikey) { }); server.setNextResponse = function (response) { - server._nextResponse = response; + server._nextResponseQueue = [response]; + }; + + server.setNextResponses = function (responses) { + server._nextResponseQueue = responses; }; server.setNextStatusCodeAndResponse = (code, body) => { server._nextStatusCode = code; - server._nextResponse = body; + server._nextResponseQueue = [body]; }; return server; diff --git a/test/acceptance/workspaces/pip-app-transitive-vuln/cli-output-actionable-remediation.txt b/test/acceptance/workspaces/pip-app-transitive-vuln/cli-output-actionable-remediation.txt new file mode 100644 index 0000000000..3b8c33ff5c --- /dev/null +++ b/test/acceptance/workspaces/pip-app-transitive-vuln/cli-output-actionable-remediation.txt @@ -0,0 +1,34 @@ + +Testing pip-app-transitive-vuln... + +Remediation advice + + +Issues to fix by upgrading existing dependencies: + + Upgrade flask to 1.0 to fix + + ✗ Improper Input Validation [High Severity][http://localhost:12345/vuln/SNYK-PYTHON-FLASK-42185] in flask@0.12.2 + ✗ Denial of Service (DOS) [High Severity][http://localhost:12345/vuln/SNYK-PYTHON-FLASK-451637] in flask@0.12.2 + +Issues to fix by pinning sub-dependencies: + + Pin Jinja2 to 2.10.1 to fix + + ✗ Sandbox Escape [Medium Severity][http://localhost:12345/vuln/SNYK-PYTHON-JINJA2-174126] in Jinja2@2.9.6 + (introduced by flask@0.12.2 > Jinja2@2.9.6) + + Pin Werkzeug to 0.15.3 to fix + + ✗ Insufficient Randomness (new) [High Severity][http://localhost:12345/vuln/SNYK-PYTHON-WERKZEUG-458931] in Werkzeug@0.12.2 + (introduced by flask@0.12.2 > Werkzeug@0.12.2) + + + +Organization: kyegupov +Package manager: pip +Target file: requirements.txt +Open source: no +Project path: pip-app-transitive-vuln + +Tested 6 dependencies for known vulnerabilities, found 4 vulnerabilities, 4 vulnerable paths. \ No newline at end of file diff --git a/test/acceptance/workspaces/pip-app-transitive-vuln/cli-output.txt b/test/acceptance/workspaces/pip-app-transitive-vuln/cli-output.txt new file mode 100644 index 0000000000..16f4d8b640 --- /dev/null +++ b/test/acceptance/workspaces/pip-app-transitive-vuln/cli-output.txt @@ -0,0 +1,44 @@ + +Testing pip-app-transitive-vuln... + +✗ Medium severity vulnerability found in Jinja2 + Description: Sandbox Escape + Info: http://localhost:12345/vuln/SNYK-PYTHON-JINJA2-174126 + Introduced through: flask@0.12.2 + From: flask@0.12.2 > Jinja2@2.9.6 + Remediation: + Pin the transitive dependency Jinja2 to version 2.10.1 + +✗ High severity vulnerability found in flask + Description: Improper Input Validation + Info: http://localhost:12345/vuln/SNYK-PYTHON-FLASK-42185 + Introduced through: flask@0.12.2 + From: flask@0.12.2 + Remediation: + Update the dependency flask to version 0.12.3 + +✗ High severity vulnerability found in flask + Description: Denial of Service (DOS) + Info: http://localhost:12345/vuln/SNYK-PYTHON-FLASK-451637 + Introduced through: flask@0.12.2 + From: flask@0.12.2 + Remediation: + Update the dependency flask to version 1.0 + +✗ High severity vulnerability found in Werkzeug + Description: Insufficient Randomness + Info: http://localhost:12345/vuln/SNYK-PYTHON-WERKZEUG-458931 + Introduced through: flask@0.12.2 + From: flask@0.12.2 > Werkzeug@0.12.2 + Remediation: + Pin the transitive dependency Werkzeug to version 0.15.3 + + + +Organization: kyegupov +Package manager: pip +Target file: requirements.txt +Open source: no +Project path: pip-app-transitive-vuln + +Tested 6 dependencies for known vulnerabilities, found 4 vulnerabilities, 4 vulnerable paths. \ No newline at end of file diff --git a/test/acceptance/workspaces/pip-app-transitive-vuln/inspect-result.json b/test/acceptance/workspaces/pip-app-transitive-vuln/inspect-result.json new file mode 100644 index 0000000000..520c410548 --- /dev/null +++ b/test/acceptance/workspaces/pip-app-transitive-vuln/inspect-result.json @@ -0,0 +1,41 @@ +{ + "plugin": { + "name": "snyk-python-plugin", + "runtime": "Python 2.7.16" + }, + "package": { + "packageFormatVersion": "pip:0.0.1", + "version": "0.0.0", + "name": "pip-app-transitive-vuln", + "dependencies": { + "flask": { + "version": "0.12.2", + "name": "flask", + "dependencies": { + "itsdangerous": { + "version": "0.24", + "name": "itsdangerous" + }, + "Jinja2": { + "version": "2.9.6", + "name": "Jinja2", + "dependencies": { + "MarkupSafe": { + "version": "1.0", + "name": "MarkupSafe" + } + } + }, + "click": { + "version": "6.7", + "name": "click" + }, + "Werkzeug": { + "version": "0.12.2", + "name": "Werkzeug" + } + } + } + } + } +} \ No newline at end of file diff --git a/test/acceptance/workspaces/pip-app-transitive-vuln/requirements.txt b/test/acceptance/workspaces/pip-app-transitive-vuln/requirements.txt new file mode 100644 index 0000000000..484d3c5749 --- /dev/null +++ b/test/acceptance/workspaces/pip-app-transitive-vuln/requirements.txt @@ -0,0 +1 @@ +flask==0.12.2 diff --git a/test/acceptance/workspaces/pip-app-transitive-vuln/response-with-remediation.json b/test/acceptance/workspaces/pip-app-transitive-vuln/response-with-remediation.json new file mode 100644 index 0000000000..b30ad7f39c --- /dev/null +++ b/test/acceptance/workspaces/pip-app-transitive-vuln/response-with-remediation.json @@ -0,0 +1,490 @@ +{ + "result": { + "affectedPkgs": { + "flask@0.12.2": { + "pkg": { + "name": "flask", + "version": "0.12.2" + }, + "issues": { + "SNYK-PYTHON-FLASK-42185": { + "issueId": "SNYK-PYTHON-FLASK-42185", + "fixInfo": { + "isPatchable": false, + "upgradePaths": [ + { + "path": [ + { + "name": "pip-app-transitive-vuln", + "version": "0.0.0" + }, + { + "name": "flask", + "version": "0.12.2", + "newVersion": "0.12.3" + } + ] + } + ] + } + }, + "SNYK-PYTHON-FLASK-451637": { + "issueId": "SNYK-PYTHON-FLASK-451637", + "fixInfo": { + "isPatchable": false, + "upgradePaths": [ + { + "path": [ + { + "name": "pip-app-transitive-vuln", + "version": "0.0.0" + }, + { + "name": "flask", + "version": "0.12.2", + "newVersion": "1.0" + } + ] + } + ] + } + } + } + }, + "Jinja2@2.9.6": { + "pkg": { + "name": "Jinja2", + "version": "2.9.6" + }, + "issues": { + "SNYK-PYTHON-JINJA2-174126": { + "issueId": "SNYK-PYTHON-JINJA2-174126", + "fixInfo": { + "isPatchable": false, + "upgradePaths": [ + { + "path": [ + { + "name": "pip-app-transitive-vuln", + "version": "0.0.0" + }, + { + "name": "flask", + "version": "0.12.2", + "newVersion": "0.12.2" + }, + { + "name": "Jinja2", + "version": "2.9.6", + "isDropped": true + } + ] + } + ] + } + } + } + }, + "Werkzeug@0.12.2": { + "pkg": { + "name": "Werkzeug", + "version": "0.12.2" + }, + "issues": { + "SNYK-PYTHON-WERKZEUG-458931": { + "issueId": "SNYK-PYTHON-WERKZEUG-458931", + "fixInfo": { + "isPatchable": false, + "upgradePaths": [ + { + "path": [ + { + "name": "pip-app-transitive-vuln", + "version": "0.0.0" + }, + { + "name": "flask", + "version": "0.12.2", + "newVersion": "0.12.2" + }, + { + "name": "Werkzeug", + "version": "0.12.2", + "isDropped": true + } + ] + } + ] + } + } + } + } + }, + "issuesData": { + "SNYK-PYTHON-FLASK-42185": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "alternativeIds": [], + "creationTime": "2018-08-20T19:12:29.035000Z", + "credit": [ + "Unknown" + ], + "cvssScore": 7.5, + "description": "## Overview\n[flask](https://pypi.org/project/Flask/) is a lightweight WSGI web application framework.\n\nAffected versions of this package are vulnerable to Improper Input Validation. It did not detect the encoding of incoming JSON data as one of the supported UTF encodings, and allowed arbitrary encodings from the request.\n\n## Remediation\nUpgrade `flask` to version 0.12.3 or higher.\n\n## References\n- [GitHub PR](https://github.com/pallets/flask/pull/2691)\n- [GitHub Release Tag](https://github.com/pallets/flask/releases/tag/0.12.3)\n", + "disclosureTime": "2018-04-10T19:12:29.035000Z", + "fixedIn": [ + "0.12.3" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-FLASK-42185", + "identifiers": { + "CVE": [ + "CVE-2018-1000656" + ], + "CWE": [ + "CWE-20" + ] + }, + "language": "python", + "modificationTime": "2019-06-02T11:58:23.863763Z", + "moduleName": "flask", + "packageManager": "pip", + "packageName": "flask", + "patches": [], + "publicationTime": "2018-08-21T14:16:13.738000Z", + "references": [ + { + "title": "GitHub PR", + "url": "https://github.com/pallets/flask/pull/2691" + }, + { + "title": "GitHub Release Tag", + "url": "https://github.com/pallets/flask/releases/tag/0.12.3" + } + ], + "semver": { + "vulnerable": [ + "[,0.12.3)" + ] + }, + "severity": "high", + "title": "Improper Input Validation" + }, + "SNYK-PYTHON-FLASK-451637": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "alternativeIds": [], + "creationTime": "2019-07-17T15:17:46.764958Z", + "credit": [ + "Unknown" + ], + "cvssScore": 7.5, + "description": "## Overview\n\n[Flask](https://pypi.org/project/Flask/) is a lightweight WSGI web application framework\n\n\nAffected versions of this package are vulnerable to Denial of Service (DOS).\nThe package allows for unsafe encoded JSON data to be decoded.\n\n## Remediation\n\nUpgrade `Flask` to version 1.0 or higher.\n\n\n## References\n\n- [GitHub PR](https://github.com/pallets/flask/pull/2691)\n\n- [Release Notes](https://www.palletsprojects.com/blog/flask-1-0-released/)\n", + "disclosureTime": "2019-07-17T14:56:51Z", + "fixedIn": [ + "1.0" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-FLASK-451637", + "identifiers": { + "CVE": [ + "CVE-2019-1010083" + ], + "CWE": [ + "CWE-20" + ] + }, + "language": "python", + "modificationTime": "2019-07-17T17:05:27.527509Z", + "moduleName": "flask", + "packageManager": "pip", + "packageName": "flask", + "patches": [], + "publicationTime": "2019-07-17T14:56:51Z", + "references": [ + { + "title": "GitHub PR", + "url": "https://github.com/pallets/flask/pull/2691" + }, + { + "title": "Release Notes", + "url": "https://www.palletsprojects.com/blog/flask-1-0-released/" + } + ], + "semver": { + "vulnerable": [ + "[,1.0)" + ] + }, + "severity": "high", + "title": "Denial of Service (DOS)" + }, + "SNYK-PYTHON-JINJA2-174126": { + "CVSSv3": "CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:L/RL:O", + "alternativeIds": [], + "creationTime": "2019-04-07T10:24:16.310959Z", + "credit": [ + "Unknown" + ], + "cvssScore": 6, + "description": "## Overview\n\n[jinja2](https://pypi.org/project/Jinja2/) is a template engine written in pure Python. It provides a Django inspired non-XML syntax but supports inline expressions and an optional sandboxed environment.\n\n\nAffected versions of this package are vulnerable to Sandbox Escape\nvia the `str.format_map`.\n\n## Remediation\n\nUpgrade `jinja2` to version 2.10.1 or higher.\n\n\n## References\n\n- [Release Notes](https://palletsprojects.com/blog/jinja-2-10-1-released)\n", + "disclosureTime": "2019-04-07T00:42:43Z", + "fixedIn": [ + "2.10.1" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-JINJA2-174126", + "identifiers": { + "CVE": [ + "CVE-2019-10906" + ], + "CWE": [ + "CWE-265" + ] + }, + "language": "python", + "modificationTime": "2019-04-07T13:13:03.054711Z", + "moduleName": "jinja2", + "packageManager": "pip", + "packageName": "Jinja2", + "patches": [], + "publicationTime": "2019-04-07T00:42:43Z", + "references": [ + { + "title": "Release Notes", + "url": "https://palletsprojects.com/blog/jinja-2-10-1-released" + } + ], + "semver": { + "vulnerable": [ + "[,2.10.1)" + ] + }, + "severity": "medium", + "title": "Sandbox Escape" + }, + "SNYK-PYTHON-WERKZEUG-458931": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + "alternativeIds": [], + "creationTime": "2019-08-10T08:00:09.852965Z", + "credit": [ + "Unknown" + ], + "cvssScore": 9.1, + "description": "## Overview\n\n[Werkzeug](https://werkzeug.palletsprojects.com/) is a WSGI web application library.\n\n\nAffected versions of this package are vulnerable to Insufficient Randomness\nwhen used with Docker, it has insufficient debugger PIN randomness because Docker containers share the same machine id.\n\n## Remediation\n\nUpgrade `Werkzeug` to version 0.15.3 or higher.\n\n\n## References\n\n- [GitHub Fix Commit](https://github.com/pallets/werkzeug/commit/00bc43b1672e662e5e3b8cecd79e67fc968fa246)\n\n- [Release Notes](https://palletsprojects.com/blog/werkzeug-0-15-3-released/)\n", + "disclosureTime": "2019-08-09T16:11:50Z", + "fixedIn": [ + "0.15.3" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-WERKZEUG-458931", + "identifiers": { + "CVE": [ + "CVE-2019-14806" + ], + "CWE": [ + "CWE-310" + ] + }, + "language": "python", + "modificationTime": "2019-08-11T08:49:08.544640Z", + "moduleName": "werkzeug", + "packageManager": "pip", + "packageName": "Werkzeug", + "patches": [], + "publicationTime": "2019-08-11T07:58:24Z", + "references": [ + { + "title": "GitHub Fix Commit", + "url": "https://github.com/pallets/werkzeug/commit/00bc43b1672e662e5e3b8cecd79e67fc968fa246" + }, + { + "title": "Release Notes", + "url": "https://palletsprojects.com/blog/werkzeug-0-15-3-released/" + } + ], + "semver": { + "vulnerable": [ + "[,0.15.3)" + ] + }, + "severity": "high", + "title": "Insufficient Randomness" + } + }, + "remediation": { + "unresolved": [ + { + "CVSSv3": "CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:L/RL:O", + "alternativeIds": [], + "creationTime": "2019-04-07T10:24:16.310959Z", + "credit": [ + "Unknown" + ], + "cvssScore": 6, + "description": "## Overview\n\n[jinja2](https://pypi.org/project/Jinja2/) is a template engine written in pure Python. It provides a Django inspired non-XML syntax but supports inline expressions and an optional sandboxed environment.\n\n\nAffected versions of this package are vulnerable to Sandbox Escape\nvia the `str.format_map`.\n\n## Remediation\n\nUpgrade `jinja2` to version 2.10.1 or higher.\n\n\n## References\n\n- [Release Notes](https://palletsprojects.com/blog/jinja-2-10-1-released)\n", + "disclosureTime": "2019-04-07T00:42:43Z", + "fixedIn": [ + "2.10.1" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-JINJA2-174126", + "identifiers": { + "CVE": [ + "CVE-2019-10906" + ], + "CWE": [ + "CWE-265" + ] + }, + "language": "python", + "modificationTime": "2019-04-07T13:13:03.054711Z", + "moduleName": "jinja2", + "packageManager": "pip", + "packageName": "Jinja2", + "patches": [], + "publicationTime": "2019-04-07T00:42:43Z", + "references": [ + { + "title": "Release Notes", + "url": "https://palletsprojects.com/blog/jinja-2-10-1-released" + } + ], + "semver": { + "vulnerable": [ + "[,2.10.1)" + ] + }, + "severity": "medium", + "title": "Sandbox Escape", + "from": [ + "pip-app-transitive-vuln@0.0.0", + "flask@0.12.2", + "Jinja2@2.9.6" + ], + "upgradePath": [ + false, + "flask@0.12.2" + ], + "isUpgradable": true, + "isPatchable": false, + "name": "Jinja2", + "version": "2.9.6" + }, + { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + "alternativeIds": [], + "creationTime": "2019-08-10T08:00:09.852965Z", + "credit": [ + "Unknown" + ], + "cvssScore": 9.1, + "description": "## Overview\n\n[Werkzeug](https://werkzeug.palletsprojects.com/) is a WSGI web application library.\n\n\nAffected versions of this package are vulnerable to Insufficient Randomness\nwhen used with Docker, it has insufficient debugger PIN randomness because Docker containers share the same machine id.\n\n## Remediation\n\nUpgrade `Werkzeug` to version 0.15.3 or higher.\n\n\n## References\n\n- [GitHub Fix Commit](https://github.com/pallets/werkzeug/commit/00bc43b1672e662e5e3b8cecd79e67fc968fa246)\n\n- [Release Notes](https://palletsprojects.com/blog/werkzeug-0-15-3-released/)\n", + "disclosureTime": "2019-08-09T16:11:50Z", + "fixedIn": [ + "0.15.3" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-WERKZEUG-458931", + "identifiers": { + "CVE": [ + "CVE-2019-14806" + ], + "CWE": [ + "CWE-310" + ] + }, + "language": "python", + "modificationTime": "2019-08-11T08:49:08.544640Z", + "moduleName": "werkzeug", + "packageManager": "pip", + "packageName": "Werkzeug", + "patches": [], + "publicationTime": "2019-08-11T07:58:24Z", + "references": [ + { + "title": "GitHub Fix Commit", + "url": "https://github.com/pallets/werkzeug/commit/00bc43b1672e662e5e3b8cecd79e67fc968fa246" + }, + { + "title": "Release Notes", + "url": "https://palletsprojects.com/blog/werkzeug-0-15-3-released/" + } + ], + "semver": { + "vulnerable": [ + "[,0.15.3)" + ] + }, + "severity": "high", + "title": "Insufficient Randomness", + "from": [ + "pip-app-transitive-vuln@0.0.0", + "flask@0.12.2", + "Werkzeug@0.12.2" + ], + "upgradePath": [ + false, + "flask@0.12.2" + ], + "isUpgradable": true, + "isPatchable": false, + "name": "Werkzeug", + "version": "0.12.2" + } + ], + "upgrade": { + "flask@0.12.2": { + "upgradeTo": "flask@1.0", + "upgrades": [ + "flask@0.12.2", + "flask@0.12.2" + ], + "vulns": [ + "SNYK-PYTHON-FLASK-451637", + "SNYK-PYTHON-FLASK-42185" + ] + } + }, + "patch": {}, + "ignore": {}, + "pin": { + "flask": { + "upgradeTo": "1.0", + "issues": [ + "SNYK-PYTHON-FLASK-42185", + "SNYK-PYTHON-FLASK-451637" + ], + "isTransitive": false + }, + "Jinja2": { + "upgradeTo": "2.10.1", + "issues": [ + "SNYK-PYTHON-JINJA2-174126" + ], + "isTransitive": true + }, + "Werkzeug": { + "upgradeTo": "0.15.3", + "issues": [ + "SNYK-PYTHON-WERKZEUG-458931" + ], + "isTransitive": true + } + } + } + }, + "meta": { + "isPublic": false, + "isLicensesEnabled": false, + "licensesPolicy": null, + "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.13.5\nignore: {}\npatch: {}\n", + "ignoreSettings": null, + "org": "kyegupov" + }, + "filesystemPolicy": false +} \ No newline at end of file diff --git a/test/acceptance/workspaces/pip-app-transitive-vuln/response-without-remediation.json b/test/acceptance/workspaces/pip-app-transitive-vuln/response-without-remediation.json new file mode 100644 index 0000000000..adb6a6610b --- /dev/null +++ b/test/acceptance/workspaces/pip-app-transitive-vuln/response-without-remediation.json @@ -0,0 +1,327 @@ +{ + "result": { + "affectedPkgs": { + "flask@0.12.2": { + "pkg": { + "name": "flask", + "version": "0.12.2" + }, + "issues": { + "SNYK-PYTHON-FLASK-42185": { + "issueId": "SNYK-PYTHON-FLASK-42185", + "fixInfo": { + "isPatchable": false, + "upgradePaths": [ + { + "path": [ + { + "name": "pip-app-transitive-vuln", + "version": "0.0.0" + }, + { + "name": "flask", + "version": "0.12.2", + "newVersion": "0.12.3" + } + ] + } + ] + } + }, + "SNYK-PYTHON-FLASK-451637": { + "issueId": "SNYK-PYTHON-FLASK-451637", + "fixInfo": { + "isPatchable": false, + "upgradePaths": [ + { + "path": [ + { + "name": "pip-app-transitive-vuln", + "version": "0.0.0" + }, + { + "name": "flask", + "version": "0.12.2", + "newVersion": "1.0" + } + ] + } + ] + } + } + } + }, + "Jinja2@2.9.6": { + "pkg": { + "name": "Jinja2", + "version": "2.9.6" + }, + "issues": { + "SNYK-PYTHON-JINJA2-174126": { + "issueId": "SNYK-PYTHON-JINJA2-174126", + "fixInfo": { + "isPatchable": false, + "upgradePaths": [ + { + "path": [ + { + "name": "pip-app-transitive-vuln", + "version": "0.0.0" + }, + { + "name": "flask", + "version": "0.12.2", + "newVersion": "0.12.2" + }, + { + "name": "Jinja2", + "version": "2.9.6", + "isDropped": true + } + ] + } + ] + } + } + } + }, + "Werkzeug@0.12.2": { + "pkg": { + "name": "Werkzeug", + "version": "0.12.2" + }, + "issues": { + "SNYK-PYTHON-WERKZEUG-458931": { + "issueId": "SNYK-PYTHON-WERKZEUG-458931", + "fixInfo": { + "isPatchable": false, + "upgradePaths": [ + { + "path": [ + { + "name": "pip-app-transitive-vuln", + "version": "0.0.0" + }, + { + "name": "flask", + "version": "0.12.2", + "newVersion": "0.12.2" + }, + { + "name": "Werkzeug", + "version": "0.12.2", + "isDropped": true + } + ] + } + ] + } + } + } + } + }, + "issuesData": { + "SNYK-PYTHON-FLASK-42185": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "alternativeIds": [], + "creationTime": "2018-08-20T19:12:29.035000Z", + "credit": [ + "Unknown" + ], + "cvssScore": 7.5, + "description": "## Overview\n[flask](https://pypi.org/project/Flask/) is a lightweight WSGI web application framework.\n\nAffected versions of this package are vulnerable to Improper Input Validation. It did not detect the encoding of incoming JSON data as one of the supported UTF encodings, and allowed arbitrary encodings from the request.\n\n## Remediation\nUpgrade `flask` to version 0.12.3 or higher.\n\n## References\n- [GitHub PR](https://github.com/pallets/flask/pull/2691)\n- [GitHub Release Tag](https://github.com/pallets/flask/releases/tag/0.12.3)\n", + "disclosureTime": "2018-04-10T19:12:29.035000Z", + "fixedIn": [ + "0.12.3" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-FLASK-42185", + "identifiers": { + "CVE": [ + "CVE-2018-1000656" + ], + "CWE": [ + "CWE-20" + ] + }, + "language": "python", + "modificationTime": "2019-06-02T11:58:23.863763Z", + "moduleName": "flask", + "packageManager": "pip", + "packageName": "flask", + "patches": [], + "publicationTime": "2018-08-21T14:16:13.738000Z", + "references": [ + { + "title": "GitHub PR", + "url": "https://github.com/pallets/flask/pull/2691" + }, + { + "title": "GitHub Release Tag", + "url": "https://github.com/pallets/flask/releases/tag/0.12.3" + } + ], + "semver": { + "vulnerable": [ + "[,0.12.3)" + ] + }, + "severity": "high", + "title": "Improper Input Validation" + }, + "SNYK-PYTHON-FLASK-451637": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "alternativeIds": [], + "creationTime": "2019-07-17T15:17:46.764958Z", + "credit": [ + "Unknown" + ], + "cvssScore": 7.5, + "description": "## Overview\n\n[Flask](https://pypi.org/project/Flask/) is a lightweight WSGI web application framework\n\n\nAffected versions of this package are vulnerable to Denial of Service (DOS).\nThe package allows for unsafe encoded JSON data to be decoded.\n\n## Remediation\n\nUpgrade `Flask` to version 1.0 or higher.\n\n\n## References\n\n- [GitHub PR](https://github.com/pallets/flask/pull/2691)\n\n- [Release Notes](https://www.palletsprojects.com/blog/flask-1-0-released/)\n", + "disclosureTime": "2019-07-17T14:56:51Z", + "fixedIn": [ + "1.0" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-FLASK-451637", + "identifiers": { + "CVE": [ + "CVE-2019-1010083" + ], + "CWE": [ + "CWE-20" + ] + }, + "language": "python", + "modificationTime": "2019-07-17T17:05:27.527509Z", + "moduleName": "flask", + "packageManager": "pip", + "packageName": "flask", + "patches": [], + "publicationTime": "2019-07-17T14:56:51Z", + "references": [ + { + "title": "GitHub PR", + "url": "https://github.com/pallets/flask/pull/2691" + }, + { + "title": "Release Notes", + "url": "https://www.palletsprojects.com/blog/flask-1-0-released/" + } + ], + "semver": { + "vulnerable": [ + "[,1.0)" + ] + }, + "severity": "high", + "title": "Denial of Service (DOS)" + }, + "SNYK-PYTHON-JINJA2-174126": { + "CVSSv3": "CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:L/RL:O", + "alternativeIds": [], + "creationTime": "2019-04-07T10:24:16.310959Z", + "credit": [ + "Unknown" + ], + "cvssScore": 6, + "description": "## Overview\n\n[jinja2](https://pypi.org/project/Jinja2/) is a template engine written in pure Python. It provides a Django inspired non-XML syntax but supports inline expressions and an optional sandboxed environment.\n\n\nAffected versions of this package are vulnerable to Sandbox Escape\nvia the `str.format_map`.\n\n## Remediation\n\nUpgrade `jinja2` to version 2.10.1 or higher.\n\n\n## References\n\n- [Release Notes](https://palletsprojects.com/blog/jinja-2-10-1-released)\n", + "disclosureTime": "2019-04-07T00:42:43Z", + "fixedIn": [ + "2.10.1" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-JINJA2-174126", + "identifiers": { + "CVE": [ + "CVE-2019-10906" + ], + "CWE": [ + "CWE-265" + ] + }, + "language": "python", + "modificationTime": "2019-04-07T13:13:03.054711Z", + "moduleName": "jinja2", + "packageManager": "pip", + "packageName": "Jinja2", + "patches": [], + "publicationTime": "2019-04-07T00:42:43Z", + "references": [ + { + "title": "Release Notes", + "url": "https://palletsprojects.com/blog/jinja-2-10-1-released" + } + ], + "semver": { + "vulnerable": [ + "[,2.10.1)" + ] + }, + "severity": "medium", + "title": "Sandbox Escape" + }, + "SNYK-PYTHON-WERKZEUG-458931": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + "alternativeIds": [], + "creationTime": "2019-08-10T08:00:09.852965Z", + "credit": [ + "Unknown" + ], + "cvssScore": 9.1, + "description": "## Overview\n\n[Werkzeug](https://werkzeug.palletsprojects.com/) is a WSGI web application library.\n\n\nAffected versions of this package are vulnerable to Insufficient Randomness\nwhen used with Docker, it has insufficient debugger PIN randomness because Docker containers share the same machine id.\n\n## Remediation\n\nUpgrade `Werkzeug` to version 0.15.3 or higher.\n\n\n## References\n\n- [GitHub Fix Commit](https://github.com/pallets/werkzeug/commit/00bc43b1672e662e5e3b8cecd79e67fc968fa246)\n\n- [Release Notes](https://palletsprojects.com/blog/werkzeug-0-15-3-released/)\n", + "disclosureTime": "2019-08-09T16:11:50Z", + "fixedIn": [ + "0.15.3" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-WERKZEUG-458931", + "identifiers": { + "CVE": [ + "CVE-2019-14806" + ], + "CWE": [ + "CWE-310" + ] + }, + "language": "python", + "modificationTime": "2019-08-11T08:49:08.544640Z", + "moduleName": "werkzeug", + "packageManager": "pip", + "packageName": "Werkzeug", + "patches": [], + "publicationTime": "2019-08-11T07:58:24Z", + "references": [ + { + "title": "GitHub Fix Commit", + "url": "https://github.com/pallets/werkzeug/commit/00bc43b1672e662e5e3b8cecd79e67fc968fa246" + }, + { + "title": "Release Notes", + "url": "https://palletsprojects.com/blog/werkzeug-0-15-3-released/" + } + ], + "semver": { + "vulnerable": [ + "[,0.15.3)" + ] + }, + "severity": "high", + "title": "Insufficient Randomness" + } + } + }, + "meta": { + "isPublic": false, + "isLicensesEnabled": false, + "licensesPolicy": null, + "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.13.5\nignore: {}\npatch: {}\n", + "ignoreSettings": null, + "org": "kyegupov" + }, + "filesystemPolicy": false +} \ No newline at end of file