diff --git a/package.json b/package.json index 972a8a468ba..4b18877cfe3 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,8 @@ "semver": "^6.0.0", "snyk-config": "3.1.1", "snyk-cpp-plugin": "2.0.0", - "snyk-docker-plugin": "3.26.2", + "legacy-snyk-docker-plugin": "snyk/snyk-docker-plugin#v3.26.2", + "snyk-docker-plugin": "4.1.1", "snyk-go-plugin": "1.16.2", "snyk-gradle-plugin": "3.10.0", "snyk-module": "3.1.0", diff --git a/src/cli/commands/monitor/index.ts b/src/cli/commands/monitor/index.ts index ad5740140ea..b2e05b5b980 100644 --- a/src/cli/commands/monitor/index.ts +++ b/src/cli/commands/monitor/index.ts @@ -37,6 +37,8 @@ import { PluginMetadata } from '@snyk/cli-interface/legacy/plugin'; import { getContributors } from '../../../lib/monitor/dev-count-analysis'; import { FailedToRunTestError, MonitorError } from '../../../lib/errors'; import { isMultiProjectScan } from '../../../lib/is-multi-project-scan'; +import { getEcosystem, monitorEcosystem } from '../../../lib/ecosystems'; +import { getFormattedMonitorOutput } from '../../../lib/ecosystems/monitor'; const SEPARATOR = '\n-------------------------------------------------------\n'; const debug = Debug('snyk'); @@ -95,6 +97,24 @@ async function monitor(...args0: MethodArgs): Promise { } } + const ecosystem = getEcosystem(options); + if (ecosystem) { + const commandResult = await monitorEcosystem( + ecosystem, + args as string[], + options, + ); + + const [monitorResults, monitorErrors] = commandResult; + + return await getFormattedMonitorOutput( + results, + monitorResults, + monitorErrors, + options, + ); + } + // Part 1: every argument is a scan target; process them sequentially for (const path of args as string[]) { debug(`Processing ${path}...`); diff --git a/src/cli/commands/test/index.ts b/src/cli/commands/test/index.ts index 9c3bc7d481c..e8ceae9c962 100644 --- a/src/cli/commands/test/index.ts +++ b/src/cli/commands/test/index.ts @@ -49,7 +49,7 @@ import { } from './formatters'; import * as utils from './utils'; import { getIacDisplayedOutput, createSarifOutputForIac } from './iac-output'; -import { getEcosystem, testEcosystem } from '../../../lib/ecosystems'; +import { getEcosystemForTest, testEcosystem } from '../../../lib/ecosystems'; import { TestLimitReachedError } from '../../../lib/errors'; import { isMultiProjectScan } from '../../../lib/is-multi-project-scan'; import { createSarifOutputForContainers } from './sarif-output'; @@ -115,7 +115,7 @@ async function test(...args: MethodArgs): Promise { } } - const ecosystem = getEcosystem(options); + const ecosystem = getEcosystemForTest(options); if (ecosystem) { try { const commandResult = await testEcosystem( diff --git a/src/lib/ecosystems.ts b/src/lib/ecosystems.ts deleted file mode 100644 index 6c238328139..00000000000 --- a/src/lib/ecosystems.ts +++ /dev/null @@ -1,160 +0,0 @@ -import * as cppPlugin from 'snyk-cpp-plugin'; -import { DepGraphData } from '@snyk/dep-graph'; -import * as snyk from './index'; -import * as config from './config'; -import { isCI } from './is-ci'; -import { makeRequest } from './request/promise'; -import { Options } from './types'; -import { TestCommandResult } from '../cli/commands/types'; -import * as spinner from '../lib/spinner'; - -export interface PluginResponse { - scanResults: ScanResult[]; -} - -export interface GitTarget { - remoteUrl: string; - branch: string; -} - -export interface ContainerTarget { - image: string; -} - -export interface ScanResult { - identity: Identity; - facts: Facts[]; - name?: string; - policy?: string; - target?: GitTarget | ContainerTarget; -} - -export interface Identity { - type: string; - targetFile?: string; - args?: { [key: string]: string }; -} - -export interface Facts { - type: string; - data: any; -} - -export interface Issue { - pkgName: string; - pkgVersion?: string; - issueId: string; - fixInfo: { - nearestFixedInVersion?: string; - }; -} - -export interface IssuesData { - [issueId: string]: { - id: string; - severity: string; - title: string; - }; -} - -export interface TestResult { - issues: Issue[]; - issuesData: IssuesData; - depGraphData: DepGraphData; -} - -export interface EcosystemPlugin { - scan: (options: Options) => Promise; - display: ( - scanResults: ScanResult[], - testResults: TestResult[], - errors: string[], - options: Options, - ) => Promise; -} - -export type Ecosystem = 'cpp'; - -const EcosystemPlugins: { - readonly [ecosystem in Ecosystem]: EcosystemPlugin; -} = { - cpp: cppPlugin, -}; - -export function getPlugin(ecosystem: Ecosystem): EcosystemPlugin { - return EcosystemPlugins[ecosystem]; -} - -export function getEcosystem(options: Options): Ecosystem | null { - if (options.source) { - return 'cpp'; - } - return null; -} - -export async function testEcosystem( - ecosystem: Ecosystem, - paths: string[], - options: Options, -): Promise { - const plugin = getPlugin(ecosystem); - const scanResultsByPath: { [dir: string]: ScanResult[] } = {}; - for (const path of paths) { - options.path = path; - const pluginResponse = await plugin.scan(options); - scanResultsByPath[path] = pluginResponse.scanResults; - } - const [testResults, errors] = await testDependencies(scanResultsByPath); - const stringifiedData = JSON.stringify(testResults, null, 2); - if (options.json) { - return TestCommandResult.createJsonTestCommandResult(stringifiedData); - } - const emptyResults: ScanResult[] = []; - const scanResults = emptyResults.concat(...Object.values(scanResultsByPath)); - const readableResult = await plugin.display( - scanResults, - testResults, - errors, - options, - ); - - return TestCommandResult.createHumanReadableTestCommandResult( - readableResult, - stringifiedData, - ); -} - -export async function testDependencies(scans: { - [dir: string]: ScanResult[]; -}): Promise<[TestResult[], string[]]> { - const results: TestResult[] = []; - const errors: string[] = []; - for (const [path, scanResults] of Object.entries(scans)) { - await spinner(`Testing dependencies in ${path}`); - for (const scanResult of scanResults) { - const payload = { - method: 'POST', - url: `${config.API}/test-dependencies`, - json: true, - headers: { - 'x-is-ci': isCI(), - authorization: 'token ' + snyk.api, - }, - body: { - ...scanResult, - }, - }; - try { - const response = await makeRequest(payload); - results.push(response); - } catch (error) { - if (error.code >= 400 && error.code < 500) { - throw new Error(error.message); - } - errors.push('Could not test dependencies in ' + path); - } - } - } - spinner.clearAll(); - return [results, errors]; -} diff --git a/src/lib/ecosystems/index.ts b/src/lib/ecosystems/index.ts new file mode 100644 index 00000000000..9ffd905d3fe --- /dev/null +++ b/src/lib/ecosystems/index.ts @@ -0,0 +1,33 @@ +import { Options } from '../types'; +import { Ecosystem } from './types'; + +export { testEcosystem } from './test'; +export { monitorEcosystem } from './monitor'; +export { getPlugin } from './plugins'; + +/** + * Ecosystems are listed here if you opt in to the new plugin test flow. + * This is a breaking change to the old plugin formats, so only a select few + * plugins currently work with it. + * + * Currently container scanning is not yet ready to work with this flow, + * hence this is in a separate function from getEcosystem(). + */ +export function getEcosystemForTest(options: Options): Ecosystem | null { + if (options.source) { + return 'cpp'; + } + return null; +} + +export function getEcosystem(options: Options): Ecosystem | null { + if (options.source) { + return 'cpp'; + } + + const isDockerDesktopIntegration = options['isDockerUser']; + if (options.docker && !isDockerDesktopIntegration) { + return 'docker'; + } + return null; +} diff --git a/src/lib/ecosystems/monitor.ts b/src/lib/ecosystems/monitor.ts new file mode 100644 index 00000000000..b4a784d579b --- /dev/null +++ b/src/lib/ecosystems/monitor.ts @@ -0,0 +1,177 @@ +import { InspectResult } from '@snyk/cli-interface/legacy/plugin'; +import chalk from 'chalk'; + +import * as snyk from '../index'; +import * as config from '../config'; +import { isCI } from '../is-ci'; +import { makeRequest } from '../request/promise'; +import { MonitorResult, Options } from '../types'; +import * as spinner from '../../lib/spinner'; +import { getPlugin } from './plugins'; +import { BadResult, GoodResult } from '../../cli/commands/monitor/types'; +import { formatMonitorOutput } from '../../cli/commands/monitor/formatters/format-monitor-response'; +import { getExtraProjectCount } from '../plugins/get-extra-project-count'; +import { MonitorError } from '../errors'; +import { + Ecosystem, + ScanResult, + EcosystemMonitorResult, + EcosystemMonitorError, + MonitorDependenciesRequest, + MonitorDependenciesResponse, +} from './types'; +import { findAndLoadPolicyForScanResult } from './policy'; + +const SEPARATOR = '\n-------------------------------------------------------\n'; + +export async function monitorEcosystem( + ecosystem: Ecosystem, + paths: string[], + options: Options, +): Promise<[EcosystemMonitorResult[], EcosystemMonitorError[]]> { + const plugin = getPlugin(ecosystem); + const scanResultsByPath: { [dir: string]: ScanResult[] } = {}; + for (const path of paths) { + await spinner(`Analyzing dependencies in ${path}`); + options.path = path; + const pluginResponse = await plugin.scan(options); + scanResultsByPath[path] = pluginResponse.scanResults; + } + const [monitorResults, errors] = await monitorDependencies( + scanResultsByPath, + options, + ); + return [monitorResults, errors]; +} + +async function generateMonitorDependenciesRequest( + scanResult: ScanResult, + options: Options, +): Promise { + // WARNING! This mutates the payload. The project name logic should be handled in the plugin. + scanResult.name = + options['project-name'] || config.PROJECT_NAME || scanResult.name; + // WARNING! This mutates the payload. Policy logic should be in the plugin. + const policy = await findAndLoadPolicyForScanResult(scanResult, options); + if (policy !== undefined) { + scanResult.policy = policy.toString(); + } + + return { + scanResult, + method: 'cli', + projectName: options['project-name'] || config.PROJECT_NAME || undefined, + }; +} + +async function monitorDependencies( + scans: { + [dir: string]: ScanResult[]; + }, + options: Options, +): Promise<[EcosystemMonitorResult[], EcosystemMonitorError[]]> { + const results: EcosystemMonitorResult[] = []; + const errors: EcosystemMonitorError[] = []; + for (const [path, scanResults] of Object.entries(scans)) { + await spinner(`Monitoring dependencies in ${path}`); + for (const scanResult of scanResults) { + const monitorDependenciesRequest = await generateMonitorDependenciesRequest( + scanResult, + options, + ); + + const payload = { + method: 'PUT', + url: `${config.API}/monitor-dependencies`, + json: true, + headers: { + 'x-is-ci': isCI(), + authorization: 'token ' + snyk.api, + }, + body: monitorDependenciesRequest, + }; + try { + const response = await makeRequest( + payload, + ); + results.push({ + ...response, + path, + scanResult, + }); + } catch (error) { + if (error.code >= 400 && error.code < 500) { + throw new Error(error.message); + } + errors.push({ + error: 'Could not monitor dependencies in ' + path, + path, + scanResult, + }); + } + } + } + spinner.clearAll(); + return [results, errors]; +} + +export async function getFormattedMonitorOutput( + results: Array, + monitorResults: EcosystemMonitorResult[], + errors: EcosystemMonitorError[], + options: Options, +): Promise { + for (const monitorResult of monitorResults) { + const monOutput = formatMonitorOutput( + monitorResult.scanResult.identity.type, + monitorResult as MonitorResult, + options, + monitorResult.projectName, + await getExtraProjectCount( + monitorResult.path, + options, + // TODO: Fix to pass the old "inspectResult.plugin.meta.allSubProjectNames", which ecosystem uses this? + // "allSubProjectNames" can become a Fact returned by a plugin. + {} as InspectResult, + ), + ); + results.push({ + ok: true, + data: monOutput, + path: monitorResult.path, + projectName: monitorResult.id, + }); + } + for (const monitorError of errors) { + results.push({ + ok: false, + data: new MonitorError(500, monitorError), + path: monitorError.path, + }); + } + + const outputString = results + .map((res) => { + if (res.ok) { + return res.data; + } + + const errorMessage = + res.data && res.data.userMessage + ? chalk.bold.red(res.data.userMessage) + : res.data + ? res.data.message + : 'Unknown error occurred.'; + + return ( + chalk.bold.white('\nMonitoring ' + res.path + '...\n\n') + errorMessage + ); + }) + .join('\n' + SEPARATOR); + + if (results.every((res) => res.ok)) { + return outputString; + } + + throw new Error(outputString); +} diff --git a/src/lib/ecosystems/plugins.ts b/src/lib/ecosystems/plugins.ts new file mode 100644 index 00000000000..92845800233 --- /dev/null +++ b/src/lib/ecosystems/plugins.ts @@ -0,0 +1,15 @@ +import * as cppPlugin from 'snyk-cpp-plugin'; +import * as dockerPlugin from 'snyk-docker-plugin'; +import { Ecosystem, EcosystemPlugin } from './types'; + +const EcosystemPlugins: { + readonly [ecosystem in Ecosystem]: EcosystemPlugin; +} = { + cpp: cppPlugin, + // TODO: not any + docker: dockerPlugin as any, +}; + +export function getPlugin(ecosystem: Ecosystem): EcosystemPlugin { + return EcosystemPlugins[ecosystem]; +} diff --git a/src/lib/ecosystems/policy.ts b/src/lib/ecosystems/policy.ts new file mode 100644 index 00000000000..386ec9e0db5 --- /dev/null +++ b/src/lib/ecosystems/policy.ts @@ -0,0 +1,33 @@ +import * as path from 'path'; + +import { SupportedPackageManagers } from '../package-managers'; +import { findAndLoadPolicy } from '../policy'; +import { Options, PolicyOptions } from '../types'; +import { ScanResult } from './types'; + +export async function findAndLoadPolicyForScanResult( + scanResult: ScanResult, + options: Options & PolicyOptions, +): Promise { + const targetFileRelativePath = scanResult.identity.targetFile + ? path.join(path.resolve(`${options.path}`), scanResult.identity.targetFile) + : undefined; + const targetFileDir = targetFileRelativePath + ? path.parse(targetFileRelativePath).dir + : undefined; + const scanType = options.docker + ? 'docker' + : (scanResult.identity.type as SupportedPackageManagers); + // TODO: fix this and send only send when we used resolve-deps for node + // it should be a ExpandedPkgTree type instead + const packageExpanded = undefined; + + const policy = (await findAndLoadPolicy( + options.path, + scanType, + options, + packageExpanded, + targetFileDir, + )) as object | undefined; // TODO: findAndLoadPolicy() does not return a string! + return policy; +} diff --git a/src/lib/ecosystems/test.ts b/src/lib/ecosystems/test.ts new file mode 100644 index 00000000000..16a46d28afa --- /dev/null +++ b/src/lib/ecosystems/test.ts @@ -0,0 +1,89 @@ +import * as snyk from '../index'; +import * as config from '../config'; +import { isCI } from '../is-ci'; +import { makeRequest } from '../request/promise'; +import { Options } from '../types'; +import { TestCommandResult } from '../../cli/commands/types'; +import * as spinner from '../../lib/spinner'; +import { Ecosystem, ScanResult, TestResult } from './types'; +import { getPlugin } from './plugins'; +import { TestDependenciesResponse } from '../snyk-test/legacy'; +import { assembleQueryString } from '../snyk-test/common'; + +export async function testEcosystem( + ecosystem: Ecosystem, + paths: string[], + options: Options, +): Promise { + const plugin = getPlugin(ecosystem); + const scanResultsByPath: { [dir: string]: ScanResult[] } = {}; + for (const path of paths) { + options.path = path; + const pluginResponse = await plugin.scan(options); + scanResultsByPath[path] = pluginResponse.scanResults; + } + const [testResults, errors] = await testDependencies( + scanResultsByPath, + options, + ); + const stringifiedData = JSON.stringify(testResults, null, 2); + if (options.json) { + return TestCommandResult.createJsonTestCommandResult(stringifiedData); + } + const emptyResults: ScanResult[] = []; + const scanResults = emptyResults.concat(...Object.values(scanResultsByPath)); + const readableResult = await plugin.display( + scanResults, + testResults, + errors, + options, + ); + + return TestCommandResult.createHumanReadableTestCommandResult( + readableResult, + stringifiedData, + ); +} + +async function testDependencies( + scans: { + [dir: string]: ScanResult[]; + }, + options: Options, +): Promise<[TestResult[], string[]]> { + const results: TestResult[] = []; + const errors: string[] = []; + for (const [path, scanResults] of Object.entries(scans)) { + await spinner(`Testing dependencies in ${path}`); + for (const scanResult of scanResults) { + const payload = { + method: 'POST', + url: `${config.API}/test-dependencies`, + json: true, + headers: { + 'x-is-ci': isCI(), + authorization: 'token ' + snyk.api, + }, + body: { + scanResult, + }, + qs: assembleQueryString(options), + }; + try { + const response = await makeRequest(payload); + results.push({ + issues: response.result.issues, + issuesData: response.result.issuesData, + depGraphData: response.result.depGraphData, + }); + } catch (error) { + if (error.code >= 400 && error.code < 500) { + throw new Error(error.message); + } + errors.push('Could not test dependencies in ' + path); + } + } + } + spinner.clearAll(); + return [results, errors]; +} diff --git a/src/lib/ecosystems/types.ts b/src/lib/ecosystems/types.ts new file mode 100644 index 00000000000..c0cdffa9cca --- /dev/null +++ b/src/lib/ecosystems/types.ts @@ -0,0 +1,118 @@ +import { DepGraphData } from '@snyk/dep-graph'; +import { Options } from '../types'; + +export type Ecosystem = 'cpp' | 'docker'; + +export interface PluginResponse { + scanResults: ScanResult[]; +} + +export interface GitTarget { + remoteUrl: string; + branch: string; +} + +export interface ContainerTarget { + image: string; +} + +export interface ScanResult { + identity: Identity; + facts: Facts[]; + name?: string; + policy?: string; + target?: GitTarget | ContainerTarget; +} + +export interface Identity { + type: string; + targetFile?: string; + args?: { [key: string]: string }; +} + +export interface Facts { + type: string; + data: any; +} + +interface UpgradePathItem { + name: string; + version: string; + newVersion?: string; + isDropped?: boolean; +} + +interface UpgradePath { + path: UpgradePathItem[]; +} + +interface FixInfo { + upgradePaths: UpgradePath[]; + isPatchable: boolean; + nearestFixedInVersion?: string; +} + +export interface Issue { + pkgName: string; + pkgVersion?: string; + issueId: string; + fixInfo: FixInfo; +} + +export interface IssuesData { + [issueId: string]: { + id: string; + severity: string; + title: string; + }; +} + +export interface TestResult { + issues: Issue[]; + issuesData: IssuesData; + depGraphData: DepGraphData; +} + +export interface EcosystemPlugin { + scan: (options: Options) => Promise; + display: ( + scanResults: ScanResult[], + testResults: TestResult[], + errors: string[], + options: Options, + ) => Promise; +} + +export interface EcosystemMonitorError { + error: string; + path: string; + scanResult: ScanResult; +} + +export interface MonitorDependenciesResponse { + ok: boolean; + org: string; + id: string; + isMonitored: boolean; + licensesPolicy: any; + uri: string; + trialStarted: boolean; + path: string; + projectName: string; +} + +export interface EcosystemMonitorResult extends MonitorDependenciesResponse { + scanResult: ScanResult; +} + +export interface MonitorDependenciesRequest { + scanResult: ScanResult; + + /** + * If provided, overrides the default project name (usually equivalent to the root package). + * @deprecated Must not be set by new code! Prefer to set the "scanResult.name" within your plugin! + */ + projectName?: string; + policy?: string; + method?: 'cli'; +} diff --git a/src/lib/plugins/index.ts b/src/lib/plugins/index.ts index 76e279168e4..1fc945f8e03 100644 --- a/src/lib/plugins/index.ts +++ b/src/lib/plugins/index.ts @@ -1,4 +1,4 @@ -import * as dockerPlugin from 'snyk-docker-plugin'; +import * as dockerPlugin from 'legacy-snyk-docker-plugin'; import * as rubygemsPlugin from './rubygems'; import * as mvnPlugin from 'snyk-mvn-plugin'; import * as gradlePlugin from 'snyk-gradle-plugin'; diff --git a/src/lib/print-deps.ts b/src/lib/print-deps.ts index f86cedbe3a0..7aa63fbbf24 100644 --- a/src/lib/print-deps.ts +++ b/src/lib/print-deps.ts @@ -62,7 +62,11 @@ function printDepsForTree(depDict: DepDict, prefix = '') { branch = '└─ '; } console.log( - prefix + (prefix ? branch : '') + dep.name + ' @ ' + dep.version, + prefix + + (prefix ? branch : '') + + dep.name + + ' @ ' + + (dep.version ? dep.version : ''), ); if (dep.dependencies) { printDepsForTree(dep.dependencies, prefix + (last ? ' ' : '│ ')); diff --git a/src/lib/snyk-test/assemble-payloads.ts b/src/lib/snyk-test/assemble-payloads.ts new file mode 100644 index 00000000000..ee4a28e35eb --- /dev/null +++ b/src/lib/snyk-test/assemble-payloads.ts @@ -0,0 +1,69 @@ +import * as path from 'path'; +import * as snyk from '../'; +import * as config from '../config'; +import { isCI } from '../is-ci'; +import { getPlugin } from '../ecosystems'; +import { Ecosystem } from '../ecosystems/types'; +import { Options, PolicyOptions, TestOptions } from '../types'; +import { Payload } from './types'; +import { assembleQueryString } from './common'; +import spinner = require('../spinner'); +import { findAndLoadPolicyForScanResult } from '../ecosystems/policy'; + +export async function assembleEcosystemPayloads( + ecosystem: Ecosystem, + options: Options & TestOptions & PolicyOptions, +): Promise { + // For --all-projects packageManager is yet undefined here. Use 'all' + let analysisTypeText = 'all dependencies for '; + if (options.docker) { + analysisTypeText = 'container dependencies for '; + } else if (options.iac) { + analysisTypeText = 'Infrastructure as code configurations for '; + } else if (options.packageManager) { + analysisTypeText = options.packageManager + ' dependencies for '; + } + + const spinnerLbl = + 'Analyzing ' + + analysisTypeText + + (path.relative('.', path.join(options.path, options.file || '')) || + path.relative('..', '.') + ' project dir'); + + spinner.clear(spinnerLbl)(); + await spinner(spinnerLbl); + + const plugin = getPlugin(ecosystem); + const pluginResponse = await plugin.scan(options); + + const payloads: Payload[] = []; + + // TODO: This is a temporary workaround until the plugins themselves can read policy files and set names! + for (const scanResult of pluginResponse.scanResults) { + // WARNING! This mutates the payload. Policy logic should be in the plugin. + const policy = await findAndLoadPolicyForScanResult(scanResult, options); + if (policy !== undefined) { + scanResult.policy = policy.toString(); + } + + // WARNING! This mutates the payload. The project name logic should be handled in the plugin. + scanResult.name = + options['project-name'] || config.PROJECT_NAME || scanResult.name; + + payloads.push({ + method: 'POST', + url: `${config.API}/test-dependencies`, + json: true, + headers: { + 'x-is-ci': isCI(), + authorization: 'token ' + snyk.api, + }, + body: { + scanResult, + }, + qs: assembleQueryString(options), + }); + } + + return payloads; +} diff --git a/src/lib/snyk-test/legacy.ts b/src/lib/snyk-test/legacy.ts index 870265d1ff7..31ed187f342 100644 --- a/src/lib/snyk-test/legacy.ts +++ b/src/lib/snyk-test/legacy.ts @@ -201,21 +201,20 @@ interface FixInfo { nearestFixedInVersion?: string; } +export interface AffectedPackages { + [pkgId: string]: { + pkg: Pkg; + issues: { + [issueId: string]: Issue; + }; + }; +} + interface TestDepGraphResult { issuesData: { [issueId: string]: IssueData; }; - affectedPkgs: { - [pkgId: string]: { - pkg: Pkg; - issues: { - [issueId: string]: { - issueId: string; - fixInfo: FixInfo; - }; - }; - }; - }; + affectedPkgs: AffectedPackages; docker: { binariesVulns?: TestDepGraphResult; baseImage?: any; @@ -223,6 +222,27 @@ interface TestDepGraphResult { remediation?: RemediationChanges; } +interface Issue { + pkgName: string; + pkgVersion?: string; + issueId: string; + fixInfo: FixInfo; +} + +interface TestDependenciesResult { + issuesData: { + [issueId: string]: IssueData; + }; + issues: Issue[]; + docker?: { + baseImage: string; + baseImageRemediation: BaseImageRemediation; + binariesVulns: TestDepGraphResult; + }; + remediation?: RemediationChanges; + depGraphData: depGraphLib.DepGraphData; +} + export interface TestDepGraphMeta { isPublic: boolean; isLicensesEnabled: boolean; @@ -242,6 +262,11 @@ export interface TestDepGraphResponse { meta: TestDepGraphMeta; } +export interface TestDependenciesResponse { + result: TestDependenciesResult; + meta: TestDepGraphMeta; +} + export interface Ignores { [path: string]: { paths: string[][]; diff --git a/src/lib/snyk-test/run-test.ts b/src/lib/snyk-test/run-test.ts index 5b06d633315..a430a00fd7a 100644 --- a/src/lib/snyk-test/run-test.ts +++ b/src/lib/snyk-test/run-test.ts @@ -15,6 +15,8 @@ import { TestDepGraphResponse, convertTestDepGraphResultToLegacy, LegacyVulnApiResult, + TestDependenciesResponse, + AffectedPackages, } from './legacy'; import { IacTestResponse } from './iac-test-result'; import { @@ -56,16 +58,159 @@ import { serializeCallGraphWithMetrics } from '../reachable-vulns'; import { validateOptions } from '../options-validator'; import { findAndLoadPolicy } from '../policy'; import { assembleIacLocalPayloads, parseIacTestResult } from './run-iac-test'; -import { Payload, PayloadBody, DepTreeFromResolveDeps } from './types'; +import { + Payload, + PayloadBody, + DepTreeFromResolveDeps, + TestDependenciesRequest, +} from './types'; import { CallGraphError, CallGraph } from '@snyk/cli-interface/legacy/common'; import * as alerts from '../alerts'; import { abridgeErrorMessage } from '../error-format'; import { getDockerToken } from '../api-token'; +import { getEcosystem } from '../ecosystems'; +import { Issue } from '../ecosystems/types'; +import { assembleEcosystemPayloads } from './assemble-payloads'; const debug = debugModule('snyk:run-test'); const ANALYTICS_PAYLOAD_MAX_LENGTH = 1024; +function prepareResponseForParsing( + payload: Payload, + response: TestDependenciesResponse, + options: Options & TestOptions, +): any { + const ecosystem = getEcosystem(options); + return ecosystem + ? prepareEcosystemResponseForParsing(payload, response, options) + : prepareLanguagesResponseForParsing(payload); +} + +function prepareEcosystemResponseForParsing( + payload: Payload, + response: TestDependenciesResponse, + options: Options & TestOptions, +) { + const testDependenciesRequest = payload.body as + | TestDependenciesRequest + | undefined; + const payloadBody = testDependenciesRequest?.scanResult; + const depGraphData: depGraphLib.DepGraphData | undefined = + response?.result?.depGraphData; + const depGraph = + depGraphData !== undefined + ? depGraphLib.createFromJSON(depGraphData) + : undefined; + const dockerfileAnalysisFact = payloadBody?.facts.find( + (fact) => fact.type === 'dockerfileAnalysis', + ); + const dockerfilePackages = dockerfileAnalysisFact?.data?.dockerfilePackages; + const projectName = payloadBody?.name || depGraph?.rootPkg.name; + const packageManager = payloadBody?.identity?.type as SupportedProjectTypes; + const targetFile = payloadBody?.identity?.targetFile || options.file; + return { + depGraph, + dockerfilePackages, + projectName, + targetFile, + pkgManager: packageManager, + displayTargetFile: targetFile, + foundProjectCount: undefined, + payloadPolicy: payloadBody?.policy, + }; +} + +function prepareLanguagesResponseForParsing(payload: Payload) { + const payloadBody = payload.body as PayloadBody | undefined; + const payloadPolicy = payloadBody && payloadBody.policy; + const depGraph = payloadBody && payloadBody.depGraph; + const pkgManager = + depGraph && + depGraph.pkgManager && + (depGraph.pkgManager.name as SupportedProjectTypes); + const targetFile = payloadBody && payloadBody.targetFile; + const projectName = + payloadBody?.projectNameOverride || payloadBody?.originalProjectName; + const foundProjectCount = payloadBody?.foundProjectCount; + const displayTargetFile = payloadBody?.displayTargetFile; + let dockerfilePackages; + if ( + payloadBody && + payloadBody.docker && + payloadBody.docker.dockerfilePackages + ) { + dockerfilePackages = payloadBody.docker.dockerfilePackages; + } + analytics.add('depGraph', !!depGraph); + analytics.add('isDocker', !!(payloadBody && payloadBody.docker)); + return { + depGraph, + payloadPolicy, + pkgManager, + targetFile, + projectName, + foundProjectCount, + displayTargetFile, + dockerfilePackages, + }; +} + +function isTestDependenciesResponse( + response: + | IacTestResponse + | TestDepGraphResponse + | TestDependenciesResponse + | LegacyVulnApiResult, +): response is TestDependenciesResponse { + const assumedTestDependenciesResponse = response as TestDependenciesResponse; + return assumedTestDependenciesResponse?.result?.issues !== undefined; +} + +function convertIssuesToAffectedPkgs( + response: + | IacTestResponse + | TestDepGraphResponse + | TestDependenciesResponse + | LegacyVulnApiResult, +): + | IacTestResponse + | TestDepGraphResponse + | TestDependenciesResponse + | LegacyVulnApiResult { + if (!(response as any).result) { + return response; + } + + if (!isTestDependenciesResponse(response)) { + return response; + } + + response.result['affectedPkgs'] = getAffectedPkgsFromIssues( + response.result.issues, + ); + return response; +} + +function getAffectedPkgsFromIssues(issues: Issue[]): AffectedPackages { + const result: AffectedPackages = {}; + + for (const issue of issues) { + const packageId = `${issue.pkgName}@${issue.pkgVersion || ''}`; + + if (result[packageId] === undefined) { + result[packageId] = { + pkg: { name: issue.pkgName, version: issue.pkgVersion }, + issues: {}, + }; + } + + result[packageId].issues[issue.issueId] = issue; + } + + return result; +} + async function sendAndParseResults( payloads: Payload[], spinnerLbl: string, @@ -91,36 +236,36 @@ async function sendAndParseResults( ); results.push(result); } else { - const payloadBody: PayloadBody = payload.body as PayloadBody; - const payloadPolicy = payloadBody && payloadBody.policy; - const depGraph = payloadBody && payloadBody.depGraph; - const pkgManager = - depGraph && - depGraph.pkgManager && - (depGraph.pkgManager.name as SupportedProjectTypes); - const targetFile = payloadBody && payloadBody.targetFile; - const projectName = - _.get(payload, 'body.projectNameOverride') || - _.get(payload, 'body.originalProjectName'); - const foundProjectCount = _.get(payload, 'body.foundProjectCount'); - const displayTargetFile = _.get(payload, 'body.displayTargetFile'); - let dockerfilePackages; - if ( - payloadBody && - payloadBody.docker && - payloadBody.docker.dockerfilePackages - ) { - dockerfilePackages = payloadBody.docker.dockerfilePackages; + /** TODO: comment why */ + const payloadCopy = Object.assign({}, payload); + const res = await sendTestPayload(payload); + const { + depGraph, + payloadPolicy, + pkgManager, + targetFile, + projectName, + foundProjectCount, + displayTargetFile, + dockerfilePackages, + } = prepareResponseForParsing( + payloadCopy, + res as TestDependenciesResponse, + options, + ); + + const ecosystem = getEcosystem(options); + if (ecosystem && options['print-deps']) { + await spinner.clear(spinnerLbl)(); + await maybePrintDepGraph(options, depGraph); } - analytics.add('depGraph', !!depGraph); - analytics.add('isDocker', !!(payloadBody && payloadBody.docker)); - // Type assertion might be a lie, but we are correcting that below - const res = (await sendTestPayload(payload)) as LegacyVulnApiResult; + + const legacyRes = convertIssuesToAffectedPkgs(res); const result = await parseRes( depGraph, pkgManager, - res, + legacyRes as LegacyVulnApiResult, options, payload, payloadPolicy, @@ -267,8 +412,15 @@ async function parseRes( function sendTestPayload( payload: Payload, -): Promise { - const filesystemPolicy = payload.body && !!payload.body.policy; +): Promise< + | LegacyVulnApiResult + | TestDepGraphResponse + | IacTestResponse + | TestDependenciesResponse +> { + const payloadBody = payload.body as any; + const filesystemPolicy = + payload.body && !!(payloadBody?.policy || payloadBody?.scanResult?.policy); return new Promise((resolve, reject) => { request(payload, (error, res, body) => { if (error) { @@ -322,6 +474,11 @@ function assemblePayloads( isLocal = fs.existsSync(root); } analytics.add('local', isLocal); + + const ecosystem = getEcosystem(options); + if (ecosystem) { + return assembleEcosystemPayloads(ecosystem, options); + } if (isLocal) { return assembleLocalPayloads(root, options); } diff --git a/src/lib/snyk-test/types.ts b/src/lib/snyk-test/types.ts index 23b39c131d6..f4ccb77e663 100644 --- a/src/lib/snyk-test/types.ts +++ b/src/lib/snyk-test/types.ts @@ -1,4 +1,5 @@ import * as depGraphLib from '@snyk/dep-graph'; +import { ScanResult } from '../ecosystems/types'; import { GitTarget, ContainerTarget } from '../project-metadata/types'; import { DepTree } from '../types'; import { IacScan } from './payload-schema'; @@ -18,6 +19,10 @@ export interface PayloadBody { target?: GitTarget | ContainerTarget | null; } +export interface TestDependenciesRequest { + scanResult: ScanResult; +} + export interface DepTreeFromResolveDeps extends DepTree { numDependencies: number; pluck: any; @@ -31,7 +36,7 @@ export interface Payload { 'x-is-ci': boolean; authorization: string; }; - body?: PayloadBody | IacScan; + body?: PayloadBody | IacScan | TestDependenciesRequest; qs?: object | null; modules?: DepTreeFromResolveDeps; } diff --git a/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts b/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts index c4c8eae73e2..1fb76674e3f 100644 --- a/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts +++ b/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts @@ -33,6 +33,7 @@ const after = tap.runOnly ? only : test; // Should be after `process.env` setup. import * as plugins from '../../../src/lib/plugins/index'; +import * as ecosystemPlugins from '../../../src/lib/ecosystems/plugins'; import { createCallGraph } from '../../utils'; import { DepGraphBuilder } from '@snyk/dep-graph'; @@ -1571,16 +1572,19 @@ if (!isWindows) { }); test('`monitor foo:latest --docker`', async (t) => { - const dockerImageId = - 'sha256:' + - '578c3e61a98cb5720e7c8fc152017be1dff373ebd72a32bbe6e328234efc8d1a'; const spyPlugin = stubDockerPluginResponse( { - plugin: { - packageManager: 'rpm', - dockerImageId, - }, - package: {}, + scanResults: [ + { + identity: { + type: 'rpm', + }, + target: { + image: 'docker-image|foo', + }, + facts: [{ type: 'depGraph', data: {} }], + }, + ], }, t, ); @@ -1596,23 +1600,29 @@ if (!isWindows) { versionNumber, 'sends version number', ); - t.match( - req.url, - '/monitor/rpm', - 'puts at correct url (uses package manager from plugin response)', + t.deepEqual( + req.body, + { + method: 'cli', + scanResult: { + identity: { + type: 'rpm', + }, + target: { + image: 'docker-image|foo', + }, + facts: [{ type: 'depGraph', data: {} }], + }, + }, + 'sends correct payload', ); - t.equal(req.body.meta.dockerImageId, dockerImageId, 'sends dockerImageId'); + t.match(req.url, '/monitor-dependencies', 'puts at correct url'); t.same( spyPlugin.getCall(0).args, [ - 'foo:latest', - null, { - args: null, docker: true, - file: null, org: 'explicit-org', - packageManager: null, path: 'foo:latest', }, ], @@ -1621,16 +1631,22 @@ if (!isWindows) { }); test('`monitor foo:latest --docker --file=Dockerfile`', async (t) => { - const dockerImageId = - 'sha256:' + - '578c3e61a98cb5720e7c8fc152017be1dff373ebd72a32bbe6e328234efc8d1a'; const spyPlugin = stubDockerPluginResponse( { - plugin: { - packageManager: 'rpm', - dockerImageId, - }, - package: { docker: 'base-image-name' }, + scanResults: [ + { + identity: { + type: 'rpm', + }, + target: { + image: 'docker-image|foo', + }, + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + ], + }, + ], }, t, ); @@ -1647,24 +1663,34 @@ if (!isWindows) { versionNumber, 'sends version number', ); - t.match( - req.url, - '/monitor/rpm', - 'puts at correct url (uses package manager from plugin response)', + t.match(req.url, '/monitor-dependencies', 'puts at correct url'); + + t.deepEqual( + req.body, + { + method: 'cli', + scanResult: { + identity: { + type: 'rpm', + }, + target: { + image: 'docker-image|foo', + }, + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + ], + }, + }, + 'sends correct payload', ); - t.equal(req.body.meta.dockerImageId, dockerImageId, 'sends dockerImageId'); - t.equal(req.body.package.docker, 'base-image-name', 'sends base image'); t.same( spyPlugin.getCall(0).args, [ - 'foo:latest', - 'Dockerfile', { - args: null, docker: true, file: 'Dockerfile', org: 'explicit-org', - packageManager: null, path: 'foo:latest', }, ], @@ -1674,12 +1700,19 @@ if (!isWindows) { test('`monitor foo:latest --docker` doesnt send policy from cwd', async (t) => { chdirWorkspaces('npm-package-policy'); - const spyPlugin = stubDockerPluginResponse( + stubDockerPluginResponse( { - plugin: { - packageManager: 'rpm', - }, - package: {}, + scanResults: [ + { + identity: { + type: 'rpm', + }, + target: { + image: 'docker-image|foo', + }, + facts: [{ type: 'depGraph', data: {} }], + }, + ], }, t, ); @@ -1689,57 +1722,27 @@ if (!isWindows) { org: 'explicit-org', }); const req = server.popRequest(); - t.equal(req.method, 'PUT', 'makes PUT request'); - t.equal( - req.headers['x-snyk-cli-version'], - versionNumber, - 'sends version number', - ); - t.match( - req.url, - '/monitor/rpm', - 'puts at correct url (uses package manager from plugin response)', - ); - t.same( - spyPlugin.getCall(0).args, - [ - 'foo:latest', - null, - { - args: null, - docker: true, - file: null, - org: 'explicit-org', - packageManager: null, - path: 'foo:latest', - }, - ], - 'calls docker plugin with expected arguments', - ); - - t.deepEqual(req.body.policy, undefined, 'no policy is sent'); + t.deepEqual(req.body.scanResult.policy, undefined, 'no policy is sent'); }); test('`monitor foo:latest --docker` with custom policy path', async (t) => { chdirWorkspaces('npm-package-policy'); - const plugin = { - async inspect() { - return { - plugin: { - packageManager: 'rpm', - name: 'docker', + const spyPlugin = stubDockerPluginResponse( + { + scanResults: [ + { + identity: { + type: 'rpm', + }, + target: { + image: 'docker-image|foo', + }, + facts: [{ type: 'depGraph', data: {} }], }, - package: {}, - }; + ], }, - }; - const spyPlugin = sinon.spy(plugin, 'inspect'); - - const loadPlugin = sinon.stub(plugins, 'loadPlugin'); - loadPlugin - .withArgs(sinon.match.any, sinon.match({ docker: true })) - .returns(plugin); - t.teardown(loadPlugin.restore); + t, + ); await cli.monitor('foo:latest', { docker: true, @@ -1747,29 +1750,13 @@ if (!isWindows) { 'policy-path': 'custom-location', }); const req = server.popRequest(); - t.equal(req.method, 'PUT', 'makes PUT request'); - t.equal( - req.headers['x-snyk-cli-version'], - versionNumber, - 'sends version number', - ); - t.match( - req.url, - '/monitor/rpm', - 'puts at correct url (uses package manager from plugin response)', - ); t.same( spyPlugin.getCall(0).args, [ - 'foo:latest', - null, { - args: null, docker: true, - file: null, org: 'explicit-org', 'policy-path': 'custom-location', - packageManager: null, path: 'foo:latest', }, ], @@ -1779,84 +1766,24 @@ if (!isWindows) { path.join('custom-location', '.snyk'), 'utf8', ); - const policyString = req.body.policy; + const policyString = req.body.scanResult.policy; t.deepEqual(policyString, expected, 'sends correct policy'); }); - test('`monitor docker-archive:foo.tar --docker --experimental`', async (t) => { - const dockerImageId = - 'sha256:' + - '578c3e61a98cb5720e7c8fc152017be1dff373ebd72a32bbe6e328234efc8d1a'; - const imageName = 'my-image'; - const spyPlugin = stubDockerPluginResponse( - { - plugin: { - packageManager: 'rpm', - dockerImageId, - }, - package: {}, - meta: { - imageName, - }, - }, - t, - ); - - await cli.monitor('docker-archive:foo.tar', { - docker: true, - org: 'experimental-org', - experimental: true, - }); - const req = server.popRequest(); - t.equal(req.method, 'PUT', 'makes PUT request'); - t.equal( - req.headers['x-snyk-cli-version'], - versionNumber, - 'sends version number', - ); - t.match( - req.url, - '/monitor/rpm', - 'puts at correct url (uses package manager from plugin response)', - ); - t.equal(req.body.meta.dockerImageId, dockerImageId, 'sends dockerImageId'); - t.equal(req.body.meta.projectName, imageName, 'sends projectName'); - t.equal(req.body.meta.name, imageName, 'sends name'); - t.same( - spyPlugin.getCall(0).args, - [ - 'docker-archive:foo.tar', - null, - { - args: null, - docker: true, - file: null, - org: 'experimental-org', - packageManager: null, - path: 'docker-archive:foo.tar', - experimental: true, - }, - ], - 'calls docker plugin with expected arguments', - ); - }); - - test('`monitor foo:latest --docker --experimental --platform=linux/arm64`', async (t) => { - const dockerImageId = - 'ca0b6709748d024a67c502558ea88dc8a1f8a858d380f5ddafa1504126a3b018'; + test('`monitor foo:latest --docker --platform=linux/arm64`', async (t) => { const platform = 'linux/arm64'; const spyPlugin = stubDockerPluginResponse( { - plugin: { - dockerImageId, - }, - scannedProjects: [ + scanResults: [ { - packageManager: 'apk', - depTree: {}, - meta: { - platform, + identity: { + type: 'rpm', + args: { platform }, }, + target: { + image: 'docker-image|foo', + }, + facts: [{ type: 'depGraph', data: {} }], }, ], }, @@ -1866,27 +1793,30 @@ if (!isWindows) { await cli.monitor('foo:latest', { platform, docker: true, - experimental: true, }); const req = server.popRequest(); - t.equal(req.method, 'PUT', 'makes PUT request'); - t.equal( - req.headers['x-snyk-cli-version'], - versionNumber, - 'sends version number', + t.deepEqual( + req.body, + { + method: 'cli', + scanResult: { + identity: { + type: 'rpm', + args: { platform }, + }, + target: { + image: 'docker-image|foo', + }, + facts: [{ type: 'depGraph', data: {} }], + }, + }, + 'sends correct payload', ); - t.equal(req.body.meta.platform, platform, 'sends platform'); t.same( spyPlugin.getCall(0).args, [ - 'foo:latest', - null, { - args: null, docker: true, - experimental: true, - file: null, - packageManager: null, path: 'foo:latest', platform, }, @@ -1962,15 +1892,16 @@ if (!isWindows) { // fixture can be fixture path or object function stubDockerPluginResponse(fixture: string | object, t) { const plugin = { - async inspect() { + async scan() { return typeof fixture === 'object' ? fixture : require(fixture); }, + async display() { + return ''; + }, }; - const spyPlugin = sinon.spy(plugin, 'inspect'); - const loadPlugin = sinon.stub(plugins, 'loadPlugin'); - loadPlugin - .withArgs(sinon.match.any, sinon.match({ docker: true })) - .returns(plugin); + const spyPlugin = sinon.spy(plugin, 'scan'); + const loadPlugin = sinon.stub(ecosystemPlugins, 'getPlugin'); + loadPlugin.withArgs(sinon.match.any).returns(plugin); t.teardown(loadPlugin.restore); return spyPlugin; diff --git a/test/acceptance/cli-test/cli-test.acceptance.test.ts b/test/acceptance/cli-test/cli-test.acceptance.test.ts index ee5ef061c18..b814391c063 100644 --- a/test/acceptance/cli-test/cli-test.acceptance.test.ts +++ b/test/acceptance/cli-test/cli-test.acceptance.test.ts @@ -64,6 +64,7 @@ const after = tap.runOnly ? only : test; // Should be after `process.env` setup. import * as plugins from '../../../src/lib/plugins/index'; +import * as ecoSystemPlugins from '../../../src/lib/ecosystems/plugins'; /* TODO: enable these tests, once we switch from node-tap @@ -141,7 +142,7 @@ if (!isWindows) { tt.test( testName, languageTest.tests[testName]( - { server, plugins, versionNumber, cli }, + { server, plugins, ecoSystemPlugins, versionNumber, cli }, { chdirWorkspaces }, ), ); diff --git a/test/acceptance/cli-test/cli-test.docker.spec.ts b/test/acceptance/cli-test/cli-test.docker.spec.ts index f4f284253ca..0772b7f1a4b 100644 --- a/test/acceptance/cli-test/cli-test.docker.spec.ts +++ b/test/acceptance/cli-test/cli-test.docker.spec.ts @@ -9,12 +9,22 @@ export const DockerTests: AcceptanceTests = { tests: { '`test foo:latest --docker`': (params) => async (t) => { const spyPlugin = stubDockerPluginResponse( - params.plugins, + params.ecoSystemPlugins, { - plugin: { - packageManager: 'deb', - }, - package: {}, + scanResults: [ + { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, + }, + ], }, t, ); @@ -30,71 +40,36 @@ export const DockerTests: AcceptanceTests = { params.versionNumber, 'sends version number', ); - t.match(req.url, '/test-dep-graph', 'posts to correct url'); - t.equal(req.body.depGraph.pkgManager.name, 'deb'); - t.same( - spyPlugin.getCall(0).args, - [ - 'foo:latest', - null, - { - args: null, - file: null, - docker: true, - org: 'explicit-org', - projectName: null, - packageManager: null, - path: 'foo:latest', - showVulnPaths: 'some', - }, - ], - 'calls docker plugin with expected arguments', - ); - }, - - '`test docker-archive:foo.tar --docker --experimental`': (params) => async ( - t, - ) => { - const spyPlugin = stubDockerPluginResponse( - params.plugins, + t.match(req.url, '/test-dependencies', 'posts to correct url'); + t.deepEqual( + req.body, { - plugin: { - packageManager: 'deb', + scanResult: { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, }, - package: {}, }, - t, - ); - - await params.cli.test('docker-archive:foo.tar', { - docker: true, - org: 'experimental-org', - experimental: true, - }); - const req = params.server.popRequest(); - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', + 'sends correct payload', ); - t.match(req.url, '/test-dep-graph', 'posts to correct url'); - t.equal(req.body.depGraph.pkgManager.name, 'deb'); t.same( spyPlugin.getCall(0).args, [ - 'docker-archive:foo.tar', - null, { - args: null, - file: null, docker: true, - org: 'experimental-org', + org: 'explicit-org', projectName: null, packageManager: null, - path: 'docker-archive:foo.tar', + pinningSupported: null, + path: 'foo:latest', showVulnPaths: 'some', - experimental: true, }, ], 'calls docker plugin with expected arguments', @@ -103,27 +78,22 @@ export const DockerTests: AcceptanceTests = { '`test foo:latest --docker vulnerable paths`': (params) => async (t) => { stubDockerPluginResponse( - params.plugins, + params.ecoSystemPlugins, { - plugin: { - packageManager: 'deb', - }, - package: { - name: 'docker-image', - dependencies: { - 'apt/libapt-pkg5.0': { - version: '1.6.3ubuntu0.1', - dependencies: { - 'bzip2/libbz2-1.0': { - version: '1.0.6-8.1', - }, - }, + scanResults: [ + { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + ], + identity: { + type: 'deb', }, - 'bzip2/libbz2-1.0': { - version: '1.0.6-8.1', + target: { + image: 'docker-image|ubuntu', }, }, - }, + ], }, t, ); @@ -159,16 +129,37 @@ export const DockerTests: AcceptanceTests = { t, ) => { const spyPlugin = stubDockerPluginResponse( - params.plugins, + params.ecoSystemPlugins, { - plugin: { - packageManager: 'deb', - }, - package: { - docker: { - baseImage: 'ubuntu:14.04', + scanResults: [ + { + facts: [ + { type: 'depGraph', data: {} }, + { + type: 'dockerfileAnalysis', + data: { + baseImage: 'nginx:1.18.0', + dockerfilePackages: { + 'openssl@1.5.0': { + instruction: 'RUN apk add openssl@1.5.0', + }, + }, + dockerfileLayers: { + 'UlVOIGFwayBhZGQgb3BlbnNzbEAxLjUuMA==': { + instruction: 'RUN apk add openssl@1.5.0', + }, + }, + }, + }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, }, - }, + ], }, t, ); @@ -186,25 +177,50 @@ export const DockerTests: AcceptanceTests = { params.versionNumber, 'sends version number', ); - t.match(req.url, '/test-dep-graph', 'posts to correct url'); - t.equal(req.body.depGraph.pkgManager.name, 'deb'); - t.equal( - req.body.docker.baseImage, - 'ubuntu:14.04', - 'posts docker baseImage', + t.match(req.url, '/test-dependencies', 'posts to correct url'); + t.deepEqual( + req.body, + { + scanResult: { + facts: [ + { type: 'depGraph', data: {} }, + { + type: 'dockerfileAnalysis', + data: { + baseImage: 'nginx:1.18.0', + dockerfilePackages: { + 'openssl@1.5.0': { + instruction: 'RUN apk add openssl@1.5.0', + }, + }, + dockerfileLayers: { + 'UlVOIGFwayBhZGQgb3BlbnNzbEAxLjUuMA==': { + instruction: 'RUN apk add openssl@1.5.0', + }, + }, + }, + }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, + }, + }, + 'sends correct payload', ); t.same( spyPlugin.getCall(0).args, [ - 'foo:latest', - 'Dockerfile', { - args: null, file: 'Dockerfile', docker: true, org: 'explicit-org', projectName: null, packageManager: null, + pinningSupported: null, path: 'foo:latest', showVulnPaths: 'some', }, @@ -217,8 +233,23 @@ export const DockerTests: AcceptanceTests = { params, ) => async (t) => { stubDockerPluginResponse( - params.plugins, - '../fixtures/docker/plugin-multiple-deps', + params.ecoSystemPlugins, + { + scanResults: [ + { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, + }, + ], + }, t, ); const vulns = require('../fixtures/docker/find-result-remediation.json'); @@ -244,12 +275,22 @@ export const DockerTests: AcceptanceTests = { ) => async (t) => { utils.chdirWorkspaces('npm-package-policy'); const spyPlugin = stubDockerPluginResponse( - params.plugins, + params.ecoSystemPlugins, { - plugin: { - packageManager: 'deb', - }, - package: {}, + scanResults: [ + { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, + }, + ], }, t, ); @@ -265,27 +306,41 @@ export const DockerTests: AcceptanceTests = { params.versionNumber, 'sends version number', ); - t.match(req.url, '/test-dep-graph', 'posts to correct url'); - t.equal(req.body.depGraph.pkgManager.name, 'deb'); + t.match(req.url, '/test-dependencies', 'posts to correct url'); + t.deepEqual( + req.body, + { + scanResult: { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, + }, + }, + 'sends correct payload', + ); t.same( spyPlugin.getCall(0).args, [ - 'foo:latest', - null, { - args: null, - file: null, docker: true, org: 'explicit-org', projectName: null, packageManager: null, + pinningSupported: null, path: 'foo:latest', showVulnPaths: 'some', }, ], 'calls docker plugin with expected arguments', ); - const policyString = req.body.policy; + const policyString = req.body.scanResult.policy; t.false(policyString, 'policy not sent'); }, @@ -294,13 +349,28 @@ export const DockerTests: AcceptanceTests = { utils, ) => async (t) => { utils.chdirWorkspaces(); + const policyString = fs.readFileSync( + path.join('npm-package-policy/custom-location', '.snyk'), + 'utf8', + ); const spyPlugin = stubDockerPluginResponse( - params.plugins, + params.ecoSystemPlugins, { - plugin: { - packageManager: 'deb', - }, - package: {}, + scanResults: [ + { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, + policy: policyString, + }, + ], }, t, ); @@ -311,20 +381,35 @@ export const DockerTests: AcceptanceTests = { 'policy-path': 'npm-package-policy/custom-location', }); const req = params.server.popRequest(); - t.match(req.url, '/test-dep-graph', 'posts to correct url'); - t.equal(req.body.depGraph.pkgManager.name, 'deb'); + t.match(req.url, '/test-dependencies', 'posts to correct url'); + t.deepEqual( + req.body, + { + scanResult: { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, + policy: policyString, + }, + }, + 'sends correct payload', + ); t.same( spyPlugin.getCall(0).args, [ - 'foo:latest', - null, { - args: null, - file: null, docker: true, org: 'explicit-org', projectName: null, packageManager: null, + pinningSupported: null, path: 'foo:latest', showVulnPaths: 'some', 'policy-path': 'npm-package-policy/custom-location', @@ -332,27 +417,32 @@ export const DockerTests: AcceptanceTests = { ], 'calls docker plugin with expected arguments', ); - - const expected = fs.readFileSync( - path.join('npm-package-policy/custom-location', '.snyk'), - 'utf8', - ); - const policyString = req.body.policy; - t.equal(policyString, expected, 'sends correct policy'); }, '`test foo:latest --docker with binaries`': (params) => async (t) => { const spyPlugin = stubDockerPluginResponse( - params.plugins, + params.ecoSystemPlugins, { - plugin: { - packageManager: 'deb', - }, - package: { - docker: { - binaries: [{ name: 'node', version: '5.10.1' }], + scanResults: [ + { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + { + type: 'keyBinariesHashes', + data: [ + '9191fbcdcc737314df97c5016a841199b743ac3fa9959dfade38e17bfdaf30b5', + ], + }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, }, - }, + ], }, t, ); @@ -368,25 +458,40 @@ export const DockerTests: AcceptanceTests = { params.versionNumber, 'sends version number', ); - t.match(req.url, '/test-dep-graph', 'posts to correct url'); - t.equal(req.body.depGraph.pkgManager.name, 'deb'); - t.same( - req.body.docker.binaries, - [{ name: 'node', version: '5.10.1' }], - 'posts docker binaries', + t.match(req.url, '/test-dependencies', 'posts to correct url'); + t.deepEqual( + req.body, + { + scanResult: { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + { + type: 'keyBinariesHashes', + data: [ + '9191fbcdcc737314df97c5016a841199b743ac3fa9959dfade38e17bfdaf30b5', + ], + }, + ], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|ubuntu', + }, + }, + }, + 'sends correct payload', ); t.same( spyPlugin.getCall(0).args, [ - 'foo:latest', - null, { - args: null, - file: null, docker: true, org: 'explicit-org', projectName: null, packageManager: null, + pinningSupported: null, path: 'foo:latest', showVulnPaths: 'some', }, @@ -399,35 +504,28 @@ export const DockerTests: AcceptanceTests = { params, ) => async (t) => { stubDockerPluginResponse( - params.plugins, + params.ecoSystemPlugins, { - plugin: { - packageManager: 'deb', - }, - package: { - name: 'docker-image', - dependencies: { - 'apt/libapt-pkg5.0': { - version: '1.6.3ubuntu0.1', - dependencies: { - 'bzip2/libbz2-1.0': { - version: '1.0.6-8.1', - }, + scanResults: [ + { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + { + type: 'keyBinariesHashes', + data: [ + '9191fbcdcc737314df97c5016a841199b743ac3fa9959dfade38e17bfdaf30b5', + ], }, + ], + identity: { + type: 'deb', }, - 'bzip2/libbz2-1.0': { - version: '1.0.6-8.1', - }, - 'bzr/libbz2-1.0': { - version: '1.0.6-8.1', - }, - }, - docker: { - binaries: { - Analysis: [{ name: 'node', version: '5.10.1' }], + target: { + image: 'docker-image|ubuntu', }, }, - }, + ], }, t, ); @@ -502,15 +600,16 @@ export const DockerTests: AcceptanceTests = { // fixture can be fixture path or object function stubDockerPluginResponse(plugins, fixture: string | object, t) { const plugin = { - async inspect() { + async scan(_) { return typeof fixture === 'object' ? fixture : require(fixture); }, + async display() { + return ''; + }, }; - const spyPlugin = sinon.spy(plugin, 'inspect'); - const loadPlugin = sinon.stub(plugins, 'loadPlugin'); - loadPlugin - .withArgs(sinon.match.any, sinon.match({ docker: true })) - .returns(plugin); + const spyPlugin = sinon.spy(plugin, 'scan'); + const loadPlugin = sinon.stub(plugins, 'getPlugin'); + loadPlugin.withArgs(sinon.match.any).returns(plugin); t.teardown(loadPlugin.restore); return spyPlugin; @@ -518,35 +617,28 @@ function stubDockerPluginResponse(plugins, fixture: string | object, t) { async function testSarif(t, utils, params, flags) { stubDockerPluginResponse( - params.plugins, + params.ecoSystemPlugins, { - plugin: { - packageManager: 'deb', - }, - package: { - name: 'docker-image', - dependencies: { - 'apt/libapt-pkg5.0': { - version: '1.6.3ubuntu0.1', - dependencies: { - 'bzip2/libbz2-1.0': { - version: '1.0.6-8.1', - }, + scanResults: [ + { + facts: [ + { type: 'depGraph', data: {} }, + { type: 'dockerfileAnalysis', data: {} }, + { + type: 'keyBinariesHashes', + data: [ + '9191fbcdcc737314df97c5016a841199b743ac3fa9959dfade38e17bfdaf30b5', + ], }, + ], + identity: { + type: 'deb', }, - 'bzip2/libbz2-1.0': { - version: '1.0.6-8.1', - }, - 'bzr/libbz2-1.0': { - version: '1.0.6-8.1', - }, - }, - docker: { - binaries: { - Analysis: [{ name: 'node', version: '5.10.1' }], + target: { + image: 'docker-image|ubuntu', }, }, - }, + ], }, t, ); diff --git a/test/acceptance/docker-token.test.ts b/test/acceptance/docker-token.test.ts index 4eca128c07f..a00bbb2abec 100644 --- a/test/acceptance/docker-token.test.ts +++ b/test/acceptance/docker-token.test.ts @@ -18,6 +18,7 @@ const server = fakeServer(BASE_API, apiKey); // This import needs to come after the server init // it causes the configured API url to be incorrect. import * as plugins from '../../src/lib/plugins/index'; +import * as ecosystemPlugins from '../../src/lib/ecosystems/plugins'; test('setup', async (t) => { t.plan(3); @@ -51,7 +52,7 @@ test('prime config', async (t) => { }); test('`snyk test` with docker flag - docker token and no api key', async (t) => { - stubDockerPluginResponse( + stubLegacyDockerPluginResponse( plugins, { plugin: { @@ -75,12 +76,18 @@ test('`snyk test` with docker flag - docker token and no api key', async (t) => test('`snyk test` with docker flag - docker token and api key', async (t) => { stubDockerPluginResponse( - plugins, { - plugin: { - packageManager: 'deb', - }, - package: {}, + scanResults: [ + { + facts: [{ type: 'depGraph', data: {} }], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|foo', + }, + }, + ], }, t, ); @@ -91,7 +98,7 @@ test('`snyk test` with docker flag - docker token and api key', async (t) => { }); const req = server.popRequest(); t.equal(req.method, 'POST', 'makes POST request'); - t.match(req.url, 'test-dep-graph', 'posts to correct url'); + t.match(req.url, 'test-dependencies', 'posts to correct url'); } catch (err) { t.fail('did not expect exception to be thrown ' + err); } @@ -101,12 +108,18 @@ test('`snyk test` with docker flag - docker token and api key', async (t) => { test('`snyk test` without docker flag - docker token and no api key', async (t) => { stubDockerPluginResponse( - plugins, { - plugin: { - packageManager: 'deb', - }, - package: {}, + scanResults: [ + { + facts: [{ type: 'depGraph', data: {} }], + identity: { + type: 'deb', + }, + target: { + image: 'docker-image|foo', + }, + }, + ], }, t, ); @@ -121,7 +134,7 @@ test('`snyk test` without docker flag - docker token and no api key', async (t) }); test('`snyk test` with docker flag - displays CTA', async (t) => { - stubDockerPluginResponse( + stubLegacyDockerPluginResponse( plugins, { plugin: { @@ -165,27 +178,18 @@ test('`snyk test` with docker flag - displays CTA', async (t) => { test('`snyk test` with docker flag - does not display CTA', async (t) => { stubDockerPluginResponse( - plugins, { - plugin: { - packageManager: 'deb', - }, - package: { - name: 'docker-image', - dependencies: { - 'apt/libapt-pkg5.0': { - version: '1.6.3ubuntu0.1', - dependencies: { - 'bzip2/libbz2-1.0': { - version: '1.0.6-8.1', - }, - }, + scanResults: [ + { + facts: [{ type: 'depGraph', data: {} }], + identity: { + type: 'deb', }, - 'bzip2/libbz2-1.0': { - version: '1.0.6-8.1', + target: { + image: 'docker-image|foo', }, }, - }, + ], }, t, ); @@ -238,7 +242,7 @@ test('teardown', async (t) => { t.end(); }); -function stubDockerPluginResponse(plugins, fixture: string | object, t) { +function stubLegacyDockerPluginResponse(plugins, fixture: string | object, t) { const plugin = { async inspect() { return typeof fixture === 'object' ? fixture : require(fixture); @@ -253,3 +257,20 @@ function stubDockerPluginResponse(plugins, fixture: string | object, t) { return spyPlugin; } + +function stubDockerPluginResponse(fixture: string | object, t) { + const plugin = { + async scan() { + return typeof fixture === 'object' ? fixture : require(fixture); + }, + async display() { + return ''; + }, + }; + const spyPlugin = sinon.spy(plugin, 'scan'); + const loadPlugin = sinon.stub(ecosystemPlugins, 'getPlugin'); + loadPlugin.withArgs(sinon.match.any).returns(plugin); + t.teardown(loadPlugin.restore); + + return spyPlugin; +} diff --git a/test/acceptance/fake-server.ts b/test/acceptance/fake-server.ts index 6ff875beef8..0f302c15f9b 100644 --- a/test/acceptance/fake-server.ts +++ b/test/acceptance/fake-server.ts @@ -134,6 +134,79 @@ export function fakeServer(root, apikey) { return next(); }); + server.post(root + '/test-dependencies', (req, res, next) => { + if (req.query.org && req.query.org === 'missing-org') { + res.status(404); + res.send({ + code: 404, + userMessage: 'cli error message', + }); + return next(); + } + + res.send({ + result: { + issues: [], + issuesData: {}, + depGraphData: { + schemaVersion: '1.2.0', + pkgManager: { + name: 'rpm', + repositories: [{ alias: 'rhel:8.2' }], + }, + pkgs: [ + { + id: 'docker-image|foo@1.2.3', + info: { + name: 'docker-image|foo', + version: '1.2.3', + }, + }, + ], + graph: { + rootNodeId: 'root-node', + nodes: [ + { + nodeId: 'root-node', + pkgId: 'docker-image|foo@1.2.3', + deps: [], + }, + ], + }, + }, + }, + meta: { + org: 'test-org', + isPublic: false, + }, + }); + return next(); + }); + + server.put(root + '/monitor-dependencies', (req, res, next) => { + if (req.query.org && req.query.org === 'missing-org') { + res.status(404); + res.send({ + code: 404, + userMessage: 'cli error message', + }); + return next(); + } + + res.send({ + ok: true, + org: 'test-org', + id: 'project-public-id', + isMonitored: true, + trialStarted: true, + licensesPolicy: {}, + uri: + 'http://example-url/project/project-public-id/history/snapshot-public-id', + projectName: 'test-project', + }); + return next(); + }); + server.post(root + '/test-iac', (req, res, next) => { if (req.query.org && req.query.org === 'missing-org') { res.status(404); diff --git a/test/acceptance/fixtures/docker/find-result-binaries.json b/test/acceptance/fixtures/docker/find-result-binaries.json index ab743ae25af..19ddcbb94bd 100644 --- a/test/acceptance/fixtures/docker/find-result-binaries.json +++ b/test/acceptance/fixtures/docker/find-result-binaries.json @@ -1,365 +1,323 @@ { - "result": { - "affectedPkgs": { - "bzip2/libbz2-1.0@1.0.6-8.1": { - "pkg": { - "version": "1.0.6-8.1", - "name": "bzip2/libbz2-1.0" + "result": { + "issues": [ + { + "pkgName": "bzip2/libbz2-1.0", + "pkgVersion": "1.0.6-8.1", + "issueId": "SNYK-LINUX-BZIP2-106947", + "fixInfo": { + "upgradePaths": [], + "isPatchable": false + } + } + ], + "issuesData": { + "SNYK-LINUX-BZIP2-106947": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "alternativeIds": [], + "creationTime": "2018-06-27T16:12:23.571063Z", + "credit": [""], + "cvssScore": 6.5, + "description": "## Overview\nUse-after-free vulnerability in bzip2recover in bzip2 1.0.6 allows remote attackers to cause a denial of service (crash) via a crafted bzip2 file, related to block ends set to before the start of the block.\n\n## References\n- [GENTOO](https://security.gentoo.org/glsa/201708-08)\n- [CONFIRM](https://bugzilla.redhat.com/show_bug.cgi?id=1319648)\n- [SECTRACK](http://www.securitytracker.com/id/1036132)\n- [BID](http://www.securityfocus.com/bid/91297)\n- [CONFIRM](http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html)\n- [MLIST](http://www.openwall.com/lists/oss-security/2016/06/20/1)\n", + "disclosureTime": null, + "id": "SNYK-LINUX-BZIP2-106947", + "identifiers": { + "CVE": ["CVE-2016-3189"], + "CWE": [] + }, + "internal": {}, + "language": "linux", + "modificationTime": "2018-10-22T04:31:58.564093Z", + "packageManager": "linux", + "packageName": "bzip2", + "patches": [], + "publicationTime": "2016-06-30T17:59:00Z", + "references": [ + { + "title": "GENTOO", + "url": "https://security.gentoo.org/glsa/201708-08" }, - "issues": { - "SNYK-LINUX-BZIP2-106947": { - "issueId": "SNYK-LINUX-BZIP2-106947", - "fixInfo": { - "upgradePaths": [], - "isPatchable": false - } - } + { + "title": "CONFIRM", + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1319648" + }, + { + "title": "SECTRACK", + "url": "http://www.securitytracker.com/id/1036132" + }, + { + "title": "BID", + "url": "http://www.securityfocus.com/bid/91297" + }, + { + "title": "CONFIRM", + "url": "http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html" + }, + { + "title": "MLIST", + "url": "http://www.openwall.com/lists/oss-security/2016/06/20/1" } + ], + "semver": { + "vulnerableByDistro": { + "alpine:3.4": ["<1.0.6-r5"], + "alpine:3.5": ["<1.0.6-r5"], + "alpine:3.6": ["<1.0.6-r5"], + "alpine:3.7": ["<1.0.6-r5"], + "alpine:3.8": ["<1.0.6-r5"], + "debian:10": ["<1.0.6-8.1"], + "debian:8": ["*"], + "debian:9": ["<1.0.6-8.1"], + "debian:unstable": ["<1.0.6-8.1"], + "ubuntu:12.04": ["*"], + "ubuntu:14.04": ["*"], + "ubuntu:16.04": ["*"], + "ubuntu:18.04": ["*"] + }, + "vulnerable": ["*"] + }, + "severity": "low", + "title": "Denial of Service (DoS)" + }, + "SNYK-LINUX-BZR-133048": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "alternativeIds": [], + "creationTime": "2018-06-27T16:12:23.571063Z", + "credit": [""], + "cvssScore": 6.5, + "description": "## Overview\nUse-after-free vulnerability in bzip2recover in bzip2 1.0.6 allows remote attackers to cause a denial of service (crash) via a crafted bzip2 file, related to block ends set to before the start of the block.\n\n## References\n- [GENTOO](https://security.gentoo.org/glsa/201708-08)\n- [CONFIRM](https://bugzilla.redhat.com/show_bug.cgi?id=1319648)\n- [SECTRACK](http://www.securitytracker.com/id/1036132)\n- [BID](http://www.securityfocus.com/bid/91297)\n- [CONFIRM](http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html)\n- [MLIST](http://www.openwall.com/lists/oss-security/2016/06/20/1)\n", + "disclosureTime": null, + "id": "SNYK-LINUX-BZIP2-106947", + "identifiers": { + "CVE": ["CVE-2016-3189"], + "CWE": [] }, - "bzr/libbz2-1.0@1.0.6-8.1": { - "pkg": { - "version": "1.0.6-8.1", - "name": "bzr/libbz2-1.0" + "internal": {}, + "language": "linux", + "modificationTime": "2018-10-22T04:31:58.564093Z", + "packageManager": "linux", + "packageName": "bzip2", + "patches": [], + "publicationTime": "2016-06-30T17:59:00Z", + "references": [ + { + "title": "GENTOO", + "url": "https://security.gentoo.org/glsa/201708-08" + }, + { + "title": "CONFIRM", + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1319648" + }, + { + "title": "SECTRACK", + "url": "http://www.securitytracker.com/id/1036132" }, - "issues": { - "SNYK-LINUX-BZR-133048": { - "issueId": "SNYK-LINUX-BZR-133048", - "fixInfo": { - "upgradePaths": [], - "isPatchable": false - } + { + "title": "BID", + "url": "http://www.securityfocus.com/bid/91297" + }, + { + "title": "CONFIRM", + "url": "http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html" + }, + { + "title": "MLIST", + "url": "http://www.openwall.com/lists/oss-security/2016/06/20/1" + } + ], + "semver": { + "vulnerableByDistro": { + "alpine:3.4": ["<1.0.6-r5"], + "alpine:3.5": ["<1.0.6-r5"], + "alpine:3.6": ["<1.0.6-r5"], + "alpine:3.7": ["<1.0.6-r5"], + "alpine:3.8": ["<1.0.6-r5"], + "debian:10": ["<1.0.6-8.1"], + "debian:8": ["*"], + "debian:9": ["<1.0.6-8.1"], + "debian:unstable": ["<1.0.6-8.1"], + "ubuntu:12.04": ["*"], + "ubuntu:14.04": ["*"], + "ubuntu:16.04": ["*"], + "ubuntu:18.04": ["*"] + }, + "vulnerable": ["*"] + }, + "severity": "low", + "title": "Denial of Service (DoS)" + } + }, + "docker": { + "binariesVulns": { + "affectedPkgs": { + "node/5.10.1": { + "pkg": { + "version": "5.10.1", + "name": "node" + }, + "issues": { + "SNYK-UPSTREAM-BZIP2-106947": { + "issueId": "SNYK-UPSTREAM-BZIP2-106947", + "fixInfo": { + "upgradePaths": [], + "isPatchable": false, + "nearestFixedInVersion": "5.13.1" + } + }, + "SNYK-UPSTREAM-NODE-72328": { + "issueId": "SNYK-UPSTREAM-NODE-72328", + "fixInfo": { + "upgradePaths": [], + "isPatchable": false, + "nearestFixedInVersion": "5.15.1" + } } + } } - } - }, + }, "issuesData": { - "SNYK-LINUX-BZIP2-106947": { - "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", - "alternativeIds": [], - "creationTime": "2018-06-27T16:12:23.571063Z", - "credit": [ - "" - ], - "cvssScore": 6.5, - "description": "## Overview\nUse-after-free vulnerability in bzip2recover in bzip2 1.0.6 allows remote attackers to cause a denial of service (crash) via a crafted bzip2 file, related to block ends set to before the start of the block.\n\n## References\n- [GENTOO](https://security.gentoo.org/glsa/201708-08)\n- [CONFIRM](https://bugzilla.redhat.com/show_bug.cgi?id=1319648)\n- [SECTRACK](http://www.securitytracker.com/id/1036132)\n- [BID](http://www.securityfocus.com/bid/91297)\n- [CONFIRM](http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html)\n- [MLIST](http://www.openwall.com/lists/oss-security/2016/06/20/1)\n", - "disclosureTime": null, - "id": "SNYK-LINUX-BZIP2-106947", - "identifiers": { - "CVE": [ - "CVE-2016-3189" - ], - "CWE": [] - }, - "internal": {}, - "language": "linux", - "modificationTime": "2018-10-22T04:31:58.564093Z", - "packageManager": "linux", - "packageName": "bzip2", - "patches": [], - "publicationTime": "2016-06-30T17:59:00Z", - "references": [ - { - "title": "GENTOO", - "url": "https://security.gentoo.org/glsa/201708-08" - }, - { - "title": "CONFIRM", - "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1319648" - }, - { - "title": "SECTRACK", - "url": "http://www.securitytracker.com/id/1036132" - }, - { - "title": "BID", - "url": "http://www.securityfocus.com/bid/91297" - }, - { - "title": "CONFIRM", - "url": "http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html" - }, - { - "title": "MLIST", - "url": "http://www.openwall.com/lists/oss-security/2016/06/20/1" - } - ], - "semver": { - "vulnerableByDistro": { - "alpine:3.4": [ - "<1.0.6-r5" - ], - "alpine:3.5": [ - "<1.0.6-r5" - ], - "alpine:3.6": [ - "<1.0.6-r5" - ], - "alpine:3.7": [ - "<1.0.6-r5" - ], - "alpine:3.8": [ - "<1.0.6-r5" - ], - "debian:10": [ - "<1.0.6-8.1" - ], - "debian:8": [ - "*" - ], - "debian:9": [ - "<1.0.6-8.1" - ], - "debian:unstable": [ - "<1.0.6-8.1" - ], - "ubuntu:12.04": [ - "*" - ], - "ubuntu:14.04": [ - "*" - ], - "ubuntu:16.04": [ - "*" - ], - "ubuntu:18.04": [ - "*" - ] - }, - "vulnerable": [ - "*" - ] - }, - "severity": "low", - "title": "Denial of Service (DoS)" + "SNYK-UPSTREAM-BZIP2-106947": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", + "alternativeIds": [], + "creationTime": "2018-09-12T13:04:09.124530Z", + "credit": ["Unknown"], + "cvssScore": 7.3, + "description": "## Overview\n[node](https://nodejs.org/en/) is a JavaScript runtime built on Chrome's V8 JavaScript engine.\r\n\r\nAffected versions of this package are vulnerable to Denial of Service (out-of-bounds read) due to improperly loading array elements.\r\n\r\n## Details\r\nDenial of Service (DoS) describes a family of attacks, all aimed at making a system inaccessible to its intended and legitimate users.\r\n\r\nUnlike other vulnerabilities, DoS attacks usually do not aim at breaching security. Rather, they are focused on making websites and services unavailable to genuine users resulting in downtime.\r\n\r\nOne popular Denial of Service vulnerability is DDoS (a Distributed Denial of Service), an attack that attempts to clog network pipes to the system by generating a large volume of traffic from many machines.\r\n\r\nWhen it comes to open source libraries, DoS vulnerabilities allow attackers to trigger such a crash or crippling of the service by using a flaw either in the application code or from the use of open source libraries.\r\n\r\nTwo common types of DoS vulnerabilities:\r\n\r\n* High CPU/Memory Consumption- An attacker sending crafted requests that could cause the system to take a disproportionate amount of time to process. For example, [commons-fileupload:commons-fileupload](SNYK-JAVA-COMMONSFILEUPLOAD-30082).\r\n\r\n* Crash - An attacker sending crafted requests that could cause the system to crash. For Example, [npm `ws` package](npm:ws:20171108)\r\n\r\n## Remediation\r\nUpgrade `node` to versions 5.1.1, 4.2.3 or higher.\n\n## References\n- [NVD](https://nvd.nist.gov/vuln/detail/CVE-2015-6764)\n", + "disclosureTime": null, + "functions": [], + "id": "SNYK-UPSTREAM-NODE-72359", + "identifiers": { + "CVE": ["CVE-2015-6764"], + "CWE": [] }, - "SNYK-LINUX-BZR-133048": { - "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", - "alternativeIds": [], - "creationTime": "2018-06-27T16:12:23.571063Z", - "credit": [ - "" - ], - "cvssScore": 6.5, - "description": "## Overview\nUse-after-free vulnerability in bzip2recover in bzip2 1.0.6 allows remote attackers to cause a denial of service (crash) via a crafted bzip2 file, related to block ends set to before the start of the block.\n\n## References\n- [GENTOO](https://security.gentoo.org/glsa/201708-08)\n- [CONFIRM](https://bugzilla.redhat.com/show_bug.cgi?id=1319648)\n- [SECTRACK](http://www.securitytracker.com/id/1036132)\n- [BID](http://www.securityfocus.com/bid/91297)\n- [CONFIRM](http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html)\n- [MLIST](http://www.openwall.com/lists/oss-security/2016/06/20/1)\n", - "disclosureTime": null, - "id": "SNYK-LINUX-BZIP2-106947", - "identifiers": { - "CVE": [ - "CVE-2016-3189" - ], - "CWE": [] + "internal": { + "content": "templated", + "flags": { + "premium": false }, - "internal": {}, - "language": "linux", - "modificationTime": "2018-10-22T04:31:58.564093Z", - "packageManager": "linux", - "packageName": "bzip2", - "patches": [], - "publicationTime": "2016-06-30T17:59:00Z", - "references": [ - { - "title": "GENTOO", - "url": "https://security.gentoo.org/glsa/201708-08" - }, - { - "title": "CONFIRM", - "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1319648" - }, - { - "title": "SECTRACK", - "url": "http://www.securitytracker.com/id/1036132" - }, - { - "title": "BID", - "url": "http://www.securityfocus.com/bid/91297" - }, - { - "title": "CONFIRM", - "url": "http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html" - }, - { - "title": "MLIST", - "url": "http://www.openwall.com/lists/oss-security/2016/06/20/1" - } - ], - "semver": { - "vulnerableByDistro": { - "alpine:3.4": [ - "<1.0.6-r5" - ], - "alpine:3.5": [ - "<1.0.6-r5" - ], - "alpine:3.6": [ - "<1.0.6-r5" - ], - "alpine:3.7": [ - "<1.0.6-r5" - ], - "alpine:3.8": [ - "<1.0.6-r5" - ], - "debian:10": [ - "<1.0.6-8.1" - ], - "debian:8": [ - "*" - ], - "debian:9": [ - "<1.0.6-8.1" - ], - "debian:unstable": [ - "<1.0.6-8.1" - ], - "ubuntu:12.04": [ - "*" - ], - "ubuntu:14.04": [ - "*" - ], - "ubuntu:16.04": [ - "*" - ], - "ubuntu:18.04": [ - "*" - ] - }, - "vulnerable": [ - "*" - ] + "source": "external" + }, + "language": "upstream", + "methods": [], + "modificationTime": "2018-12-12T17:12:34.427465Z", + "moduleName": "node", + "packageManager": "upstream", + "packageName": "node", + "patches": [], + "publicationTime": "2018-12-12T17:12:34.375733Z", + "references": [ + { + "title": "NVD", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2015-6764" + } + ], + "semver": { + "vulnerable": ["[4.0.0, 4.2.3)", "[5.0.0, 5.13.1)"] + }, + "severity": "high", + "title": "Denial of Service" + }, + "SNYK-UPSTREAM-NODE-72328": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", + "alternativeIds": [], + "creationTime": "2018-09-12T13:04:09.124530Z", + "credit": ["Unknown"], + "cvssScore": 7.3, + "description": "## Overview\n[node](https://nodejs.org/en/) is a JavaScript runtime built on Chrome's V8 JavaScript engine.\r\n\r\nAffected versions of this package are vulnerable to Denial of Service (out-of-bounds read) due to improperly loading array elements.\r\n\r\n## Details\r\nDenial of Service (DoS) describes a family of attacks, all aimed at making a system inaccessible to its intended and legitimate users.\r\n\r\nUnlike other vulnerabilities, DoS attacks usually do not aim at breaching security. Rather, they are focused on making websites and services unavailable to genuine users resulting in downtime.\r\n\r\nOne popular Denial of Service vulnerability is DDoS (a Distributed Denial of Service), an attack that attempts to clog network pipes to the system by generating a large volume of traffic from many machines.\r\n\r\nWhen it comes to open source libraries, DoS vulnerabilities allow attackers to trigger such a crash or crippling of the service by using a flaw either in the application code or from the use of open source libraries.\r\n\r\nTwo common types of DoS vulnerabilities:\r\n\r\n* High CPU/Memory Consumption- An attacker sending crafted requests that could cause the system to take a disproportionate amount of time to process. For example, [commons-fileupload:commons-fileupload](SNYK-JAVA-COMMONSFILEUPLOAD-30082).\r\n\r\n* Crash - An attacker sending crafted requests that could cause the system to crash. For Example, [npm `ws` package](npm:ws:20171108)\r\n\r\n## Remediation\r\nUpgrade `node` to versions 5.1.1, 4.2.3 or higher.\n\n## References\n- [NVD](https://nvd.nist.gov/vuln/detail/CVE-2015-6764)\n", + "disclosureTime": null, + "functions": [], + "id": "SNYK-UPSTREAM-NODE-72328", + "identifiers": { + "CVE": ["CVE-2015-6764"], + "CWE": [] + }, + "internal": { + "content": "templated", + "flags": { + "premium": false }, - "severity": "low", - "title": "Denial of Service (DoS)" - } - }, - "docker": { - "binariesVulns": { - "affectedPkgs": { - "node/5.10.1": { - "pkg": { - "version": "5.10.1", - "name": "node" - }, - "issues": { - "SNYK-UPSTREAM-BZIP2-106947": { - "issueId": "SNYK-UPSTREAM-BZIP2-106947", - "fixInfo": { - "upgradePaths": [], - "isPatchable": false, - "nearestFixedInVersion": "5.13.1" - } - }, - "SNYK-UPSTREAM-NODE-72328": { - "issueId": "SNYK-UPSTREAM-NODE-72328", - "fixInfo": { - "upgradePaths": [], - "isPatchable": false, - "nearestFixedInVersion": "5.15.1" - } - } - } + "source": "external" + }, + "language": "upstream", + "methods": [], + "modificationTime": "2018-12-12T17:12:34.427465Z", + "moduleName": "node", + "packageManager": "upstream", + "packageName": "node", + "patches": [], + "publicationTime": "2018-12-12T17:12:34.375733Z", + "references": [ + { + "title": "NVD", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2015-6764" } + ], + "semver": { + "vulnerable": ["[4.0.0, 4.2.3)", "[5.0.0, 5.15.1)"] }, - "issuesData": { - "SNYK-UPSTREAM-BZIP2-106947": { - "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", - "alternativeIds": [], - "creationTime": "2018-09-12T13:04:09.124530Z", - "credit": [ - "Unknown" - ], - "cvssScore": 7.3, - "description": "## Overview\n[node](https://nodejs.org/en/) is a JavaScript runtime built on Chrome's V8 JavaScript engine.\r\n\r\nAffected versions of this package are vulnerable to Denial of Service (out-of-bounds read) due to improperly loading array elements.\r\n\r\n## Details\r\nDenial of Service (DoS) describes a family of attacks, all aimed at making a system inaccessible to its intended and legitimate users.\r\n\r\nUnlike other vulnerabilities, DoS attacks usually do not aim at breaching security. Rather, they are focused on making websites and services unavailable to genuine users resulting in downtime.\r\n\r\nOne popular Denial of Service vulnerability is DDoS (a Distributed Denial of Service), an attack that attempts to clog network pipes to the system by generating a large volume of traffic from many machines.\r\n\r\nWhen it comes to open source libraries, DoS vulnerabilities allow attackers to trigger such a crash or crippling of the service by using a flaw either in the application code or from the use of open source libraries.\r\n\r\nTwo common types of DoS vulnerabilities:\r\n\r\n* High CPU/Memory Consumption- An attacker sending crafted requests that could cause the system to take a disproportionate amount of time to process. For example, [commons-fileupload:commons-fileupload](SNYK-JAVA-COMMONSFILEUPLOAD-30082).\r\n\r\n* Crash - An attacker sending crafted requests that could cause the system to crash. For Example, [npm `ws` package](npm:ws:20171108)\r\n\r\n## Remediation\r\nUpgrade `node` to versions 5.1.1, 4.2.3 or higher.\n\n## References\n- [NVD](https://nvd.nist.gov/vuln/detail/CVE-2015-6764)\n", - "disclosureTime": null, - "functions": [], - "id": "SNYK-UPSTREAM-NODE-72359", - "identifiers": { - "CVE": [ - "CVE-2015-6764" - ], - "CWE": [] - }, - "internal": { - "content": "templated", - "flags": { - "premium": false - }, - "source": "external" - }, - "language": "upstream", - "methods": [], - "modificationTime": "2018-12-12T17:12:34.427465Z", - "moduleName": "node", - "packageManager": "upstream", - "packageName": "node", - "patches": [], - "publicationTime": "2018-12-12T17:12:34.375733Z", - "references": [ - { - "title": "NVD", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2015-6764" - } - ], - "semver": { - "vulnerable": [ - "[4.0.0, 4.2.3)", - "[5.0.0, 5.13.1)" - ] - }, - "severity": "high", - "title": "Denial of Service" - }, - "SNYK-UPSTREAM-NODE-72328": { - "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", - "alternativeIds": [], - "creationTime": "2018-09-12T13:04:09.124530Z", - "credit": [ - "Unknown" - ], - "cvssScore": 7.3, - "description": "## Overview\n[node](https://nodejs.org/en/) is a JavaScript runtime built on Chrome's V8 JavaScript engine.\r\n\r\nAffected versions of this package are vulnerable to Denial of Service (out-of-bounds read) due to improperly loading array elements.\r\n\r\n## Details\r\nDenial of Service (DoS) describes a family of attacks, all aimed at making a system inaccessible to its intended and legitimate users.\r\n\r\nUnlike other vulnerabilities, DoS attacks usually do not aim at breaching security. Rather, they are focused on making websites and services unavailable to genuine users resulting in downtime.\r\n\r\nOne popular Denial of Service vulnerability is DDoS (a Distributed Denial of Service), an attack that attempts to clog network pipes to the system by generating a large volume of traffic from many machines.\r\n\r\nWhen it comes to open source libraries, DoS vulnerabilities allow attackers to trigger such a crash or crippling of the service by using a flaw either in the application code or from the use of open source libraries.\r\n\r\nTwo common types of DoS vulnerabilities:\r\n\r\n* High CPU/Memory Consumption- An attacker sending crafted requests that could cause the system to take a disproportionate amount of time to process. For example, [commons-fileupload:commons-fileupload](SNYK-JAVA-COMMONSFILEUPLOAD-30082).\r\n\r\n* Crash - An attacker sending crafted requests that could cause the system to crash. For Example, [npm `ws` package](npm:ws:20171108)\r\n\r\n## Remediation\r\nUpgrade `node` to versions 5.1.1, 4.2.3 or higher.\n\n## References\n- [NVD](https://nvd.nist.gov/vuln/detail/CVE-2015-6764)\n", - "disclosureTime": null, - "functions": [], - "id": "SNYK-UPSTREAM-NODE-72328", - "identifiers": { - "CVE": [ - "CVE-2015-6764" - ], - "CWE": [] - }, - "internal": { - "content": "templated", - "flags": { - "premium": false - }, - "source": "external" - }, - "language": "upstream", - "methods": [], - "modificationTime": "2018-12-12T17:12:34.427465Z", - "moduleName": "node", - "packageManager": "upstream", - "packageName": "node", - "patches": [], - "publicationTime": "2018-12-12T17:12:34.375733Z", - "references": [ - { - "title": "NVD", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2015-6764" - } - ], - "semver": { - "vulnerable": [ - "[4.0.0, 4.2.3)", - "[5.0.0, 5.15.1)" - ] - }, - "severity": "high", - "title": "Denial of Service" - } - } + "severity": "high", + "title": "Denial of Service" } } + } }, - "meta": {} - } + "depGraphData": { + "schemaVersion": "1.2.0", + "pkgManager": { + "name": "rpm", + "repositories": [{ "alias": "rhel:8.2" }] + }, + "pkgs": [ + { + "id": "docker-image|snyk/kubernetes-monitor@1.32.2", + "info": { + "name": "docker-image|snyk/kubernetes-monitor", + "version": "1.32.2" + } + }, + { + "id": "apt/libapt-pkg5.0@1.6.3ubuntu0.1", + "info": { "name": "apt/libapt-pkg5.0", "version": "1.6.3ubuntu0.1" } + }, + { + "id": "bzip2/libbz2-1.0@1.0.6-8.1", + "info": { "name": "bzip2/libbz2-1.0", "version": "1.0.6-8.1" } + }, + { + "id": "bzip/libbz2-1.0@1.0.6-8.1", + "info": { "name": "bzip/libbz2-1.0", "version": "1.0.6-8.1" } + } + ], + "graph": { + "rootNodeId": "root-node", + "nodes": [ + { + "nodeId": "root-node", + "pkgId": "docker-image|snyk/kubernetes-monitor@1.32.2", + "deps": [ + { "nodeId": "apt/libapt-pkg5.0@1.6.3ubuntu0.1" }, + { "nodeId": "bzip2/libbz2-1.0@1.0.6-8.1" }, + { "nodeId": "bzip/libbz2-1.0@1.0.6-8.1" } + ] + }, + { + "nodeId": "apt/libapt-pkg5.0@1.6.3ubuntu0.1", + "pkgId": "apt/libapt-pkg5.0@1.6.3ubuntu0.1", + "deps": [{ "nodeId": "bzip2/libbz2-1.0@1.0.6-8.1" }] + }, + { + "nodeId": "bzip2/libbz2-1.0@1.0.6-8.1", + "pkgId": "bzip2/libbz2-1.0@1.0.6-8.1", + "deps": [] + }, + { + "nodeId": "bzip/libbz2-1.0@1.0.6-8.1", + "pkgId": "bzip/libbz2-1.0@1.0.6-8.1", + "deps": [] + } + ] + } + } + }, + "meta": {} +} diff --git a/test/acceptance/fixtures/docker/find-result-remediation.json b/test/acceptance/fixtures/docker/find-result-remediation.json index 8e380a4f667..ea95ab57b59 100644 --- a/test/acceptance/fixtures/docker/find-result-remediation.json +++ b/test/acceptance/fixtures/docker/find-result-remediation.json @@ -18,54 +18,29 @@ ] } }, - "affectedPkgs": { - "bzip2/libbz2-1.0@1.0.6-8.1": { - "pkg": { - "version": "1.0.6-8.1", - "name": "bzip2/libbz2-1.0" - }, - "issues": { - "SNYK-LINUX-BZIP2-106947": { - "issueId": "SNYK-LINUX-BZIP2-106947", - "fixInfo": { - "upgradePaths": [], - "isPatchable": false - } - } - } - }, - "curl@7.38.0-4+deb8u11": { - "pkg": { - "name": "curl", - "version": "7.38.0-4+deb8u11" - }, - "issues": { - "SNYK-LINUX-CURL-100548": { - "issueId": "SNYK-LINUX-CURL-100548", - "fixInfo": { - "isPatchable": false, - "upgradePaths": [] - } - } + "issues": [ + { + "pkgName": "bzip2/libbz2-1.0", + "pkgVersion": "1.0.6-8.1", + "issueId": "SNYK-LINUX-BZIP2-106947", + "fixInfo": { + "upgradePaths": [], + "isPatchable": false } } - }, + ], "issuesData": { "SNYK-LINUX-BZIP2-106947": { "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", "alternativeIds": [], "creationTime": "2018-06-27T16:12:23.571063Z", - "credit": [ - "" - ], + "credit": [""], "cvssScore": 6.5, "description": "## Overview\nUse-after-free vulnerability in bzip2recover in bzip2 1.0.6 allows remote attackers to cause a denial of service (crash) via a crafted bzip2 file, related to block ends set to before the start of the block.\n\n## References\n- [GENTOO](https://security.gentoo.org/glsa/201708-08)\n- [CONFIRM](https://bugzilla.redhat.com/show_bug.cgi?id=1319648)\n- [SECTRACK](http://www.securitytracker.com/id/1036132)\n- [BID](http://www.securityfocus.com/bid/91297)\n- [CONFIRM](http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html)\n- [MLIST](http://www.openwall.com/lists/oss-security/2016/06/20/1)\n", "disclosureTime": null, "id": "SNYK-LINUX-BZIP2-106947", "identifiers": { - "CVE": [ - "CVE-2016-3189" - ], + "CVE": ["CVE-2016-3189"], "CWE": [] }, "internal": {}, @@ -103,49 +78,21 @@ ], "semver": { "vulnerableByDistro": { - "alpine:3.4": [ - "<1.0.6-r5" - ], - "alpine:3.5": [ - "<1.0.6-r5" - ], - "alpine:3.6": [ - "<1.0.6-r5" - ], - "alpine:3.7": [ - "<1.0.6-r5" - ], - "alpine:3.8": [ - "<1.0.6-r5" - ], - "debian:10": [ - "<1.0.6-8.1" - ], - "debian:8": [ - "*" - ], - "debian:9": [ - "<1.0.6-8.1" - ], - "debian:unstable": [ - "<1.0.6-8.1" - ], - "ubuntu:12.04": [ - "*" - ], - "ubuntu:14.04": [ - "*" - ], - "ubuntu:16.04": [ - "*" - ], - "ubuntu:18.04": [ - "*" - ] - }, - "vulnerable": [ - "*" - ] + "alpine:3.4": ["<1.0.6-r5"], + "alpine:3.5": ["<1.0.6-r5"], + "alpine:3.6": ["<1.0.6-r5"], + "alpine:3.7": ["<1.0.6-r5"], + "alpine:3.8": ["<1.0.6-r5"], + "debian:10": ["<1.0.6-8.1"], + "debian:8": ["*"], + "debian:9": ["<1.0.6-8.1"], + "debian:unstable": ["<1.0.6-8.1"], + "ubuntu:12.04": ["*"], + "ubuntu:14.04": ["*"], + "ubuntu:16.04": ["*"], + "ubuntu:18.04": ["*"] + }, + "vulnerable": ["*"] }, "severity": "low", "title": "Denial of Service (DoS)" @@ -154,20 +101,14 @@ "CVSSv3": "CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N", "alternativeIds": [], "creationTime": "2019-02-06T14:28:30.599712Z", - "credit": [ - "" - ], + "credit": [""], "cvssScore": 5.3, "description": "## Overview\nThe (1) mbed_connect_step1 function in lib/vtls/mbedtls.c and (2) polarssl_connect_step1 function in lib/vtls/polarssl.c in cURL and libcurl before 7.49.0, when using SSLv3 or making a TLS connection to a URL that uses a numerical IP address, allow remote attackers to spoof servers via an arbitrary valid certificate.\n\n## References\n- [BID](http://www.securityfocus.com/bid/90726)\n- [CONFIRM](https://curl.haxx.se/CVE-2016-3739.patch)\n- [CONFIRM](https://curl.haxx.se/changes.html#7_49_0)\n- [CONFIRM](https://curl.haxx.se/docs/adv_20160518.html)\n- [CONFIRM](https://h20566.www2.hpe.com/portal/site/hpsc/public/kb/docDisplay?docId=emr_na-c05320149)\n- [CONFIRM](https://h20566.www2.hpe.com/portal/site/hpsc/public/kb/docDisplay?docId=emr_na-c05390722)\n- [GENTOO](https://security.gentoo.org/glsa/201701-47)\n- [SECTRACK](http://www.securitytracker.com/id/1035907)\n- [SLACKWARE](http://www.slackware.com/security/viewer.php?l=slackware-security&y=2016&m=slackware-security.495349)\n", "disclosureTime": null, "id": "SNYK-LINUX-CURL-100548", "identifiers": { - "CVE": [ - "CVE-2016-3739" - ], - "CWE": [ - "CWE-20" - ] + "CVE": ["CVE-2016-3739"], + "CWE": ["CWE-20"] }, "language": "linux", "modificationTime": "2019-03-17T06:12:22.801004Z", @@ -215,26 +156,63 @@ ], "semver": { "vulnerableByDistro": { - "debian:10": [ - "<7.50.1-1" - ], - "debian:8": [ - "*" - ], - "debian:9": [ - "<7.50.1-1" - ], - "debian:unstable": [ - "<7.50.1-1" - ] + "debian:10": ["<7.50.1-1"], + "debian:8": ["*"], + "debian:9": ["<7.50.1-1"], + "debian:unstable": ["<7.50.1-1"] }, - "vulnerable": [ - "*" - ] + "vulnerable": ["*"] }, "severity": "medium", "title": "Improper Input Validation" } + }, + "depGraphData": { + "schemaVersion": "1.2.0", + "pkgManager": { + "name": "rpm", + "repositories": [{ "alias": "rhel:8.2" }] + }, + "pkgs": [ + { + "id": "docker-image|snyk/kubernetes-monitor@1.32.2", + "info": { + "name": "docker-image|snyk/kubernetes-monitor", + "version": "1.32.2" + } + }, + { + "id": "apt/libapt-pkg5.0@1.6.3ubuntu0.1", + "info": { "name": "apt/libapt-pkg5.0", "version": "1.6.3ubuntu0.1" } + }, + { + "id": "bzip2/libbz2-1.0@1.0.6-8.1", + "info": { "name": "bzip2/libbz2-1.0", "version": "1.0.6-8.1" } + } + ], + "graph": { + "rootNodeId": "root-node", + "nodes": [ + { + "nodeId": "root-node", + "pkgId": "docker-image|snyk/kubernetes-monitor@1.32.2", + "deps": [ + { "nodeId": "apt/libapt-pkg5.0@1.6.3ubuntu0.1" }, + { "nodeId": "bzip2/libbz2-1.0@1.0.6-8.1" } + ] + }, + { + "nodeId": "apt/libapt-pkg5.0@1.6.3ubuntu0.1", + "pkgId": "apt/libapt-pkg5.0@1.6.3ubuntu0.1", + "deps": [{ "nodeId": "bzip2/libbz2-1.0@1.0.6-8.1" }] + }, + { + "nodeId": "bzip2/libbz2-1.0@1.0.6-8.1", + "pkgId": "bzip2/libbz2-1.0@1.0.6-8.1", + "deps": [] + } + ] + } } }, "meta": {} diff --git a/test/acceptance/fixtures/docker/find-result.json b/test/acceptance/fixtures/docker/find-result.json index 8f1bf0c27fa..27d0745afe1 100644 --- a/test/acceptance/fixtures/docker/find-result.json +++ b/test/acceptance/fixtures/docker/find-result.json @@ -1,123 +1,132 @@ { - "result": { - "affectedPkgs": { - "bzip2/libbz2-1.0@1.0.6-8.1": { - "pkg": { - "version": "1.0.6-8.1", - "name": "bzip2/libbz2-1.0" - }, - "issues": { - "SNYK-LINUX-BZIP2-106947": { - "issueId": "SNYK-LINUX-BZIP2-106947", - "fixInfo": { - "upgradePaths": [], - "isPatchable": false - } - } - } - } - }, - "issuesData": { - "SNYK-LINUX-BZIP2-106947": { - "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", - "alternativeIds": [], - "creationTime": "2018-06-27T16:12:23.571063Z", - "credit": [ - "" - ], - "cvssScore": 6.5, - "description": "## Overview\nUse-after-free vulnerability in bzip2recover in bzip2 1.0.6 allows remote attackers to cause a denial of service (crash) via a crafted bzip2 file, related to block ends set to before the start of the block.\n\n## References\n- [GENTOO](https://security.gentoo.org/glsa/201708-08)\n- [CONFIRM](https://bugzilla.redhat.com/show_bug.cgi?id=1319648)\n- [SECTRACK](http://www.securitytracker.com/id/1036132)\n- [BID](http://www.securityfocus.com/bid/91297)\n- [CONFIRM](http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html)\n- [MLIST](http://www.openwall.com/lists/oss-security/2016/06/20/1)\n", - "disclosureTime": null, - "id": "SNYK-LINUX-BZIP2-106947", - "identifiers": { - "CVE": [ - "CVE-2016-3189" - ], - "CWE": [] - }, - "internal": {}, - "language": "linux", - "modificationTime": "2018-10-22T04:31:58.564093Z", - "packageManager": "linux", - "packageName": "bzip2", - "patches": [], - "publicationTime": "2016-06-30T17:59:00Z", - "references": [ - { - "title": "GENTOO", - "url": "https://security.gentoo.org/glsa/201708-08" - }, - { - "title": "CONFIRM", - "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1319648" - }, - { - "title": "SECTRACK", - "url": "http://www.securitytracker.com/id/1036132" - }, - { - "title": "BID", - "url": "http://www.securityfocus.com/bid/91297" - }, - { - "title": "CONFIRM", - "url": "http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html" - }, - { - "title": "MLIST", - "url": "http://www.openwall.com/lists/oss-security/2016/06/20/1" - } - ], - "semver": { - "vulnerableByDistro": { - "alpine:3.4": [ - "<1.0.6-r5" - ], - "alpine:3.5": [ - "<1.0.6-r5" - ], - "alpine:3.6": [ - "<1.0.6-r5" - ], - "alpine:3.7": [ - "<1.0.6-r5" - ], - "alpine:3.8": [ - "<1.0.6-r5" - ], - "debian:10": [ - "<1.0.6-8.1" - ], - "debian:8": [ - "*" - ], - "debian:9": [ - "<1.0.6-8.1" - ], - "debian:unstable": [ - "<1.0.6-8.1" - ], - "ubuntu:12.04": [ - "*" - ], - "ubuntu:14.04": [ - "*" - ], - "ubuntu:16.04": [ - "*" - ], - "ubuntu:18.04": [ - "*" - ] - }, - "vulnerable": [ - "*" - ] - }, - "severity": "low", - "title": "Denial of Service (DoS)" - } + "result": { + "issues": [ + { + "pkgName": "bzip2/libbz2-1.0", + "pkgVersion": "1.0.6-8.1", + "issueId": "SNYK-LINUX-BZIP2-106947", + "fixInfo": { + "upgradePaths": [], + "isPatchable": false } + } + ], + "issuesData": { + "SNYK-LINUX-BZIP2-106947": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "alternativeIds": [], + "creationTime": "2018-06-27T16:12:23.571063Z", + "credit": [""], + "cvssScore": 6.5, + "description": "## Overview\nUse-after-free vulnerability in bzip2recover in bzip2 1.0.6 allows remote attackers to cause a denial of service (crash) via a crafted bzip2 file, related to block ends set to before the start of the block.\n\n## References\n- [GENTOO](https://security.gentoo.org/glsa/201708-08)\n- [CONFIRM](https://bugzilla.redhat.com/show_bug.cgi?id=1319648)\n- [SECTRACK](http://www.securitytracker.com/id/1036132)\n- [BID](http://www.securityfocus.com/bid/91297)\n- [CONFIRM](http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html)\n- [MLIST](http://www.openwall.com/lists/oss-security/2016/06/20/1)\n", + "disclosureTime": null, + "id": "SNYK-LINUX-BZIP2-106947", + "identifiers": { + "CVE": ["CVE-2016-3189"], + "CWE": [] + }, + "internal": {}, + "language": "linux", + "modificationTime": "2018-10-22T04:31:58.564093Z", + "packageManager": "linux", + "packageName": "bzip2", + "patches": [], + "publicationTime": "2016-06-30T17:59:00Z", + "references": [ + { + "title": "GENTOO", + "url": "https://security.gentoo.org/glsa/201708-08" + }, + { + "title": "CONFIRM", + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1319648" + }, + { + "title": "SECTRACK", + "url": "http://www.securitytracker.com/id/1036132" + }, + { + "title": "BID", + "url": "http://www.securityfocus.com/bid/91297" + }, + { + "title": "CONFIRM", + "url": "http://www.oracle.com/technetwork/topics/security/bulletinjul2016-3090568.html" + }, + { + "title": "MLIST", + "url": "http://www.openwall.com/lists/oss-security/2016/06/20/1" + } + ], + "semver": { + "vulnerableByDistro": { + "alpine:3.4": ["<1.0.6-r5"], + "alpine:3.5": ["<1.0.6-r5"], + "alpine:3.6": ["<1.0.6-r5"], + "alpine:3.7": ["<1.0.6-r5"], + "alpine:3.8": ["<1.0.6-r5"], + "debian:10": ["<1.0.6-8.1"], + "debian:8": ["*"], + "debian:9": ["<1.0.6-8.1"], + "debian:unstable": ["<1.0.6-8.1"], + "ubuntu:12.04": ["*"], + "ubuntu:14.04": ["*"], + "ubuntu:16.04": ["*"], + "ubuntu:18.04": ["*"] + }, + "vulnerable": ["*"] + }, + "severity": "low", + "title": "Denial of Service (DoS)" + } }, - "meta": {} + "depGraphData": { + "schemaVersion": "1.2.0", + "pkgManager": { + "name": "rpm", + "repositories": [{ "alias": "rhel:8.2" }] + }, + "pkgs": [ + { + "id": "docker-image|snyk/kubernetes-monitor@1.32.2", + "info": { + "name": "docker-image|snyk/kubernetes-monitor", + "version": "1.32.2" + } + }, + { + "id": "apt/libapt-pkg5.0@1.6.3ubuntu0.1", + "info": { "name": "apt/libapt-pkg5.0", "version": "1.6.3ubuntu0.1" } + }, + { + "id": "bzip2/libbz2-1.0@1.0.6-8.1", + "info": { "name": "bzip2/libbz2-1.0", "version": "1.0.6-8.1" } + } + ], + "graph": { + "rootNodeId": "root-node", + "nodes": [ + { + "nodeId": "root-node", + "pkgId": "docker-image|snyk/kubernetes-monitor@1.32.2", + "deps": [ + { "nodeId": "apt/libapt-pkg5.0@1.6.3ubuntu0.1" }, + { "nodeId": "bzip2/libbz2-1.0@1.0.6-8.1" } + ] + }, + { + "nodeId": "apt/libapt-pkg5.0@1.6.3ubuntu0.1", + "pkgId": "apt/libapt-pkg5.0@1.6.3ubuntu0.1", + "deps": [{ "nodeId": "bzip2/libbz2-1.0@1.0.6-8.1" }] + }, + { + "nodeId": "bzip2/libbz2-1.0@1.0.6-8.1", + "pkgId": "bzip2/libbz2-1.0@1.0.6-8.1", + "deps": [] + } + ] + } + } + }, + "meta": {} } diff --git a/test/ecosystems.spec.ts b/test/ecosystems.spec.ts index 4b15702de76..20fd12c9681 100644 --- a/test/ecosystems.spec.ts +++ b/test/ecosystems.spec.ts @@ -2,6 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as cppPlugin from 'snyk-cpp-plugin'; import * as ecosystems from '../src/lib/ecosystems'; +import * as ecosystemsTypes from '../src/lib/ecosystems/types'; import * as request from '../src/lib/request/promise'; import { Options } from '../src/lib/types'; import { TestCommandResult } from '../src/cli/commands/types'; @@ -72,7 +73,7 @@ describe('ecosystems', () => { const errorTxt = readFixture('error.txt'); const testResult = readJsonFixture( 'testResults.json', - ) as ecosystems.TestResult; + ) as ecosystemsTypes.TestResult; const stringifyTestResults = JSON.stringify([testResult], null, 2); beforeAll(() => { @@ -90,7 +91,7 @@ describe('ecosystems', () => { it('should return human readable result when no json option given', async () => { const makeRequestSpy = jest .spyOn(request, 'makeRequest') - .mockResolvedValue(testResult); + .mockResolvedValue({ result: testResult }); const expected = TestCommandResult.createHumanReadableTestCommandResult( displayTxt, stringifyTestResults, @@ -100,27 +101,29 @@ describe('ecosystems', () => { }); expect(makeRequestSpy.mock.calls[0][0]).toEqual({ body: { - facts: [ - { - type: 'cpp-fingerprints', - data: [ - { - filePath: 'add.cpp', - hash: '52d1b046047db9ea0c581cafd4c68fe5', - }, - { - filePath: 'add.h', - hash: 'aeca71a6e39f99a24ecf4c088eee9cb8', - }, - { - filePath: 'main.cpp', - hash: 'ad3365b3370ef6b1c3e778f875055f19', - }, - ], + scanResult: { + facts: [ + { + type: 'cpp-fingerprints', + data: [ + { + filePath: 'add.cpp', + hash: '52d1b046047db9ea0c581cafd4c68fe5', + }, + { + filePath: 'add.h', + hash: 'aeca71a6e39f99a24ecf4c088eee9cb8', + }, + { + filePath: 'main.cpp', + hash: 'ad3365b3370ef6b1c3e778f875055f19', + }, + ], + }, + ], + identity: { + type: 'cpp', }, - ], - identity: { - type: 'cpp', }, }, headers: { @@ -130,6 +133,7 @@ describe('ecosystems', () => { json: true, method: 'POST', url: expect.stringContaining('/test-dependencies'), + qs: expect.any(Object), }); expect(actual).toEqual(expected); }); @@ -137,7 +141,7 @@ describe('ecosystems', () => { it('should return json result when json option', async () => { const makeRequestSpy = jest .spyOn(request, 'makeRequest') - .mockResolvedValue(testResult); + .mockResolvedValue({ result: testResult }); const expected = TestCommandResult.createJsonTestCommandResult( stringifyTestResults, ); @@ -147,27 +151,29 @@ describe('ecosystems', () => { }); expect(makeRequestSpy.mock.calls[0][0]).toEqual({ body: { - facts: [ - { - type: 'cpp-fingerprints', - data: [ - { - filePath: 'add.cpp', - hash: '52d1b046047db9ea0c581cafd4c68fe5', - }, - { - filePath: 'add.h', - hash: 'aeca71a6e39f99a24ecf4c088eee9cb8', - }, - { - filePath: 'main.cpp', - hash: 'ad3365b3370ef6b1c3e778f875055f19', - }, - ], + scanResult: { + facts: [ + { + type: 'cpp-fingerprints', + data: [ + { + filePath: 'add.cpp', + hash: '52d1b046047db9ea0c581cafd4c68fe5', + }, + { + filePath: 'add.h', + hash: 'aeca71a6e39f99a24ecf4c088eee9cb8', + }, + { + filePath: 'main.cpp', + hash: 'ad3365b3370ef6b1c3e778f875055f19', + }, + ], + }, + ], + identity: { + type: 'cpp', }, - ], - identity: { - type: 'cpp', }, }, headers: { @@ -177,6 +183,7 @@ describe('ecosystems', () => { json: true, method: 'POST', url: expect.stringContaining('/test-dependencies'), + qs: expect.any(Object), }); expect(actual).toEqual(expected); }); @@ -184,7 +191,7 @@ describe('ecosystems', () => { it('should return fingerprints when debug option is set', async () => { const mock = jest .spyOn(request, 'makeRequest') - .mockResolvedValue(testResult); + .mockResolvedValue({ result: testResult }); const expected = TestCommandResult.createHumanReadableTestCommandResult( debugDisplayTxt, stringifyTestResults,