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 8da2bf1f7a..c04ed78b94 100644 --- a/src/cli/commands/test/formatters/legacy-format-issue.ts +++ b/src/cli/commands/test/formatters/legacy-format-issue.ts @@ -4,8 +4,12 @@ import * as wrap from 'wrap-ansi'; 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 { GroupedVuln } from '../../../../lib/snyk-test/legacy'; +import { + WIZARD_SUPPORTED_PACKAGE_MANAGERS, + SupportedPackageManagers, + PINNING_SUPPORTED_PACKAGE_MANAGERS } from '../../../../lib/package-managers'; +import { GroupedVuln, DockerIssue } from '../../../../lib/snyk-test/legacy'; +import parsePackageNameVersion = require('snyk-module'); export function formatIssues(vuln: GroupedVuln, options: Options & TestOptions) { const vulnID = vuln.list[0].id; @@ -129,7 +133,7 @@ function createTruncatedVulnsPathsText(vulnList) { } } -function createFixedInText(vuln: any): string { +function createFixedInText(vuln: GroupedVuln & DockerIssue): string { if (vuln.nearestFixedInVersion) { return chalk.bold('\n Fixed in: ' + vuln.nearestFixedInVersion); } else if (vuln.fixedIn && vuln.fixedIn.length > 0) { @@ -139,12 +143,21 @@ function createFixedInText(vuln: any): string { return ''; } -function createRemediationText(vuln, packageManager) { +function createRemediationText( + vuln: GroupedVuln, + packageManager: SupportedPackageManagers, +): string { let wizardHintText = ''; if (WIZARD_SUPPORTED_PACKAGE_MANAGERS.includes(packageManager)) { wizardHintText = 'Run `snyk wizard` to explore remediation options.'; } + if (vuln.fixedIn && PINNING_SUPPORTED_PACKAGE_MANAGERS.includes(packageManager)) { + const toVersion = 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]; @@ -157,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).name; 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 f1d9122e5f..9030f59fa0 100644 --- a/src/cli/commands/test/formatters/remediation-based-format-issues.ts +++ b/src/cli/commands/test/formatters/remediation-based-format-issues.ts @@ -1,10 +1,14 @@ -import * as _ from 'lodash'; import chalk from 'chalk'; import * as wrap from 'wrap-ansi'; 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, + UpgradeRemediation, + PinRemediation, +} from '../../../../lib/snyk-test/legacy'; import { SEVERITIES } from '../../../../lib/snyk-test/common'; interface BasicVulnInfo { @@ -15,11 +19,21 @@ interface BasicVulnInfo { version: string; fixedIn: string[]; legalInstructions?: string; + paths: string[][]; +} + +interface TopLevelPackageUpgrade { + name: string; + version: string; +} + +interface UpgradesByAffectedPackage { + [pkgNameAndVersion: string]: TopLevelPackageUpgrade[]; } export function formatIssuesWithRemediation( vulns: GroupedVuln[], - remediationInfo: RemediationResult, + remediationInfo: RemediationChanges, options: TestOptions, ): string[] { @@ -36,11 +50,33 @@ export function formatIssuesWithRemediation( version: vuln.version, fixedIn: vuln.fixedIn, legalInstructions: vuln.legalInstructions, + paths: vuln.list.map((v) => v.from), }; } const results = [chalk.bold.white('Remediation advice')]; - const upgradeTextArray = constructUpgradesText(remediationInfo.upgrade, basicVulnInfo); + let upgradeTextArray: string[]; + if (remediationInfo.pin && Object.keys(remediationInfo.pin).length) { + const upgradesByAffected: UpgradesByAffectedPackage = {}; + for (const topLevelPkg of Object.keys(remediationInfo.upgrade)) { + for (const targetPkgStr of remediationInfo.upgrade[topLevelPkg].upgrades) { + if (!upgradesByAffected[targetPkgStr]) { + upgradesByAffected[targetPkgStr] = []; + } + upgradesByAffected[targetPkgStr].push({ + name: topLevelPkg, + version: remediationInfo.upgrade[topLevelPkg].upgradeTo, + }); + } + } + upgradeTextArray = constructPinText(remediationInfo.pin, upgradesByAffected, basicVulnInfo); + 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 { + upgradeTextArray = constructUpgradesText(remediationInfo.upgrade, basicVulnInfo); + } if (upgradeTextArray.length > 0) { results.push(upgradeTextArray.join('\n')); } @@ -82,8 +118,7 @@ function constructPatchesText( // todo: add vulnToPatch package name const packageAtVersion = `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`; const patchedText = `\n Patch available for ${chalk.bold.whiteBright(packageAtVersion)}\n`; - const thisPatchFixes = - formatIssue( + const thisPatchFixes = formatIssue( id, basicVulnInfo[id].title, basicVulnInfo[id].severity, @@ -97,6 +132,36 @@ function constructPatchesText( return patchedTextArray; } +function thisUpgradeFixes(vulnIds: string[], basicVulnInfo: Record) { + return vulnIds + .sort((a, b) => getSeverityValue(basicVulnInfo[a].severity) - getSeverityValue(basicVulnInfo[b].severity)) + .map((id) => formatIssue( + id, + basicVulnInfo[id].title, + basicVulnInfo[id].severity, + basicVulnInfo[id].isNew, + basicVulnInfo[id].legalInstructions, + `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`, + )) + .join('\n'); +} + +function processUpgrades( + sink: string[], + upgradesByDep: DependencyUpdates | DependencyPins, + deps: string[], + basicVulnInfo: Record, +) { + for (const dep of deps) { + const data = upgradesByDep[dep]; + const upgradeDepTo = data.upgradeTo; + const vulnIds = (data as UpgradeRemediation).vulns || (data as PinRemediation).issues; + const upgradeText = + `\n Upgrade ${chalk.bold.whiteBright(dep)} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix\n`; + sink.push(upgradeText + thisUpgradeFixes(vulnIds, basicVulnInfo)); + } +} + function constructUpgradesText( upgrades: DependencyUpdates, basicVulnInfo: { @@ -108,25 +173,73 @@ function constructUpgradesText( return []; } - const upgradeTextArray = [chalk.bold.green('\nUpgradable Issues:')]; - for (const upgrade of Object.keys(upgrades)) { - const upgradeDepTo = _.get(upgrades, [upgrade, 'upgradeTo']); - const vulnIds = _.get(upgrades, [upgrade, 'vulns']); - const upgradeText = - `\n Upgrade ${chalk.bold.whiteBright(upgrade)} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix\n`; - const thisUpgradeFixes = vulnIds - .sort((a, b) => getSeverityValue(basicVulnInfo[a].severity) - getSeverityValue(basicVulnInfo[b].severity)) - .map((id) => formatIssue( - id, - basicVulnInfo[id].title, - basicVulnInfo[id].severity, - basicVulnInfo[id].isNew, - basicVulnInfo[id].legalInstructions, - `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`, - )) - .join('\n'); - upgradeTextArray.push(upgradeText + thisUpgradeFixes); + const upgradeTextArray = [chalk.bold.green('\nIssues to fix by upgrading:')]; + processUpgrades(upgradeTextArray, upgrades, Object.keys(upgrades), basicVulnInfo); + return upgradeTextArray; +} + +function constructPinText( + pins: DependencyPins, + upgradesByAffected: UpgradesByAffectedPackage, // classical "remediation via top-level dep" upgrades + basicVulnInfo: Record, +): string[] { + + if (!(Object.keys(pins).length)) { + return []; } + + // 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:')); + processUpgrades(upgradeTextArray, pins, upgradeables, basicVulnInfo); + } + + // 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 pkgName of pinables) { + const data = pins[pkgName]; + const vulnIds = data.issues; + const upgradeDepTo = data.upgradeTo; + const upgradeText = + `\n Pin ${chalk.bold.whiteBright(pkgName)} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix`; + upgradeTextArray.push(upgradeText); + upgradeTextArray.push(thisUpgradeFixes(vulnIds, basicVulnInfo)); + + // Transitive dependencies are not visible for the user. Therefore, it makes sense to print + // at least some guidance explaining where they do come from. We want to limit the number + // of paths, because it can be in thousands for some projects. + 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))`); + + // Finally, if we have some upgrade paths that fix the same issues, suggest them as well. + const topLevelUpgradesAlreadySuggested = new Set(); + for (const vid of vulnIds) { + for (const topLevelPkg of upgradesByAffected[pkgName + '@' + basicVulnInfo[vid].version] || []) { + const setKey = `${topLevelPkg.name}\n${topLevelPkg.version}`; + if (!topLevelUpgradesAlreadySuggested.has(setKey)) { + topLevelUpgradesAlreadySuggested.add(setKey); + upgradeTextArray.push(' The issues above can also be fixed by upgrading top-level dependency ' + + `${topLevelPkg.name} to ${topLevelPkg.version}`); + } + } + } + } + } + return upgradeTextArray; } @@ -140,15 +253,15 @@ function constructUnfixableText(unresolved: IssueData[]) { ? `\n This issue was fixed in versions: ${chalk.bold(issue.fixedIn.join(', '))}` : '\n No upgrade or patch available'; const packageNameAtVersion = chalk.bold - .whiteBright(`\n ${issue.packageName}@${issue.version}\n`); + .whiteBright(`\n ${issue.packageName}@${issue.version}\n`); unfixableIssuesTextArray .push(packageNameAtVersion + - formatIssue( - issue.id, - issue.title, - issue.severity, - issue.isNew, - issue.legalInstructions) + `${extraInfo}`); + formatIssue( + issue.id, + issue.title, + issue.severity, + issue.isNew, + issue.legalInstructions) + `${extraInfo}`); } return unfixableIssuesTextArray; 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 3140527fa3..53db769609 100644 --- a/src/lib/snyk-test/legacy.ts +++ b/src/lib/snyk-test/legacy.ts @@ -45,10 +45,8 @@ export interface GroupedVuln { isNew: boolean; name: string; version: string; + isFixable: boolean; fixedIn: string[]; - dockerfileInstruction: string; - dockerBaseImage: string; - nearestFixedInVersion: string; legalInstructions?: string; } @@ -78,11 +76,10 @@ interface AnnotatedIssue extends IssueData { credit: any; name: string; version: string; - from: Array; + from: string[]; upgradePath: Array; isUpgradable: boolean; isPatchable: boolean; - nearestFixedInVersion?: string; severity: SEVERITY; // These fields present for "node_module" based scans to allow remediation @@ -91,9 +88,6 @@ interface AnnotatedIssue extends IssueData { __filename?: string; parentDepType: string; - dockerfileInstruction?: any; - dockerBaseImage?: any; - type?: 'license'; title: string; patch?: any; @@ -101,6 +95,13 @@ interface AnnotatedIssue extends IssueData { publicationTime?: string; } +// Mixin, to be added to GroupedVuln / AnnotatedIssue +export interface DockerIssue { + nearestFixedInVersion?: string; + dockerfileInstruction?: any; + dockerBaseImage?: any; +} + export interface LegacyVulnApiResult { vulnerabilities: AnnotatedIssue[]; ok: boolean; @@ -112,12 +113,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 { @@ -156,7 +160,7 @@ interface TestDepGraphResult { binariesVulns?: TestDepGraphResult; baseImage?: any; }; - remediation?: RemediationResult; + remediation?: RemediationChanges; } interface TestDepGraphMeta { @@ -207,15 +211,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( @@ -262,7 +277,7 @@ function convertTestDepGraphResultToLegacy( name: pkgInfo.pkg.name, version: pkgInfo.pkg.version as string, nearestFixedInVersion: pkgIssue.fixInfo.nearestFixedInVersion, - }) as AnnotatedIssue; // TODO(kyegupov): get rid of type assertion + }) as AnnotatedIssue & DockerIssue; // TODO(kyegupov): get rid of type assertion vulns.push(annotatedIssue); } diff --git a/src/lib/snyk-test/run-test.ts b/src/lib/snyk-test/run-test.ts index 86cd0d8521..a101b44e9d 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'; @@ -15,7 +15,10 @@ import spinner = require('../spinner'); import common = require('./common'); import {DepTree, TestOptions} from '../types'; import gemfileLockToDependencies = require('../../lib/plugins/rubygems/gemfile-lock-to-dependencies'); -import {convertTestDepGraphResultToLegacy, AnnotatedIssue, LegacyVulnApiResult, TestDepGraphResponse} from './legacy'; +import { + convertTestDepGraphResultToLegacy, AnnotatedIssue, LegacyVulnApiResult, + TestDepGraphResponse, DockerIssue, +} from './legacy'; import {Options} from '../types'; import { NoSupportedManifestsFoundError, @@ -128,15 +131,15 @@ async function runTest(packageManager: SupportedPackageManagers, res.vulnerabilities = res.vulnerabilities.map((vuln) => { const dockerfilePackage = dockerfilePackages[vuln.name.split('/')[0]]; if (dockerfilePackage) { - vuln.dockerfileInstruction = dockerfilePackage.instruction; + (vuln as DockerIssue).dockerfileInstruction = dockerfilePackage.instruction; } - vuln.dockerBaseImage = res.docker!.baseImage; + (vuln as DockerIssue).dockerBaseImage = res.docker!.baseImage; return vuln; }); } if (options.docker && options.file && options['exclude-base-image-vulns']) { - res.vulnerabilities = res.vulnerabilities.filter((vuln) => (vuln.dockerfileInstruction)); + res.vulnerabilities = res.vulnerabilities.filter((vuln) => ((vuln as DockerIssue).dockerfileInstruction)); } res.uniqueCount = countUniqueVulns(res.vulnerabilities); diff --git a/test/acceptance/cli.acceptance.test.ts b/test/acceptance/cli.acceptance.test.ts index de26c2a082..bc83c6b178 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 @@ -1284,6 +1287,94 @@ 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.setNextResponse( + loadJson('./pip-app-transitive-vuln/response-without-remediation.json'), + ); + 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')); + } + const 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.setNextResponse( + loadJson('./pip-app-transitive-vuln/response-with-remediation.json'), + ); + 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')); + } + const 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/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..a51fe44769 --- /dev/null +++ b/test/acceptance/workspaces/pip-app-transitive-vuln/cli-output-actionable-remediation.txt @@ -0,0 +1,31 @@ + +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 diff --git a/test/git-repos.test.ts b/test/git-repos.test.ts index 32a0f5709d..7b87b59492 100644 --- a/test/git-repos.test.ts +++ b/test/git-repos.test.ts @@ -16,7 +16,7 @@ urls.forEach((url) => { await cli.test(url); t.pass('url worked'); } catch (err) { - t.threw(); + t.threw(err); t.end(); } });