From a8be76486bfc17dda643d18a6fa9475744ddbd5c Mon Sep 17 00:00:00 2001 From: Luke Watts Date: Tue, 16 Apr 2024 11:35:32 +0200 Subject: [PATCH] feat: include additional policy properties, when provided, in plain text output (#5142) --- .../remediation-based-format-issues.ts | 142 +++++--- .../formatters/test/format-test-results.ts | 2 + src/lib/formatters/types.ts | 24 ++ src/lib/snyk-test/legacy.ts | 2 + .../npm-package-single-vuln/package-lock.json | 14 + .../npm-package-single-vuln/package.json | 9 + .../test-graph-results-with-annotation.json | 116 ++++++ .../test-graph-results.json | 104 ++++++ .../test-graph-results.json | 337 ++++++++++++++++++ .../test-graph-user-note-results.json | 333 +++++++++++++++++ .../snyk-test/human-formatted-output.spec.ts | 66 ++++ ...mediation-based-format-issues.spec.ts.snap | 8 +- .../remediation-based-format-issues.spec.ts | 261 +++++++++----- 13 files changed, 1263 insertions(+), 155 deletions(-) create mode 100644 test/acceptance/workspaces/npm-package-single-vuln/package-lock.json create mode 100644 test/acceptance/workspaces/npm-package-single-vuln/package.json create mode 100644 test/acceptance/workspaces/npm-package-single-vuln/test-graph-results-with-annotation.json create mode 100644 test/acceptance/workspaces/npm-package-single-vuln/test-graph-results.json create mode 100644 test/fixtures/sca-dep-graph-with-annotation/test-graph-results.json create mode 100644 test/fixtures/sca-dep-graph-with-annotation/test-graph-user-note-results.json create mode 100644 test/jest/acceptance/snyk-test/human-formatted-output.spec.ts diff --git a/src/lib/formatters/remediation-based-format-issues.ts b/src/lib/formatters/remediation-based-format-issues.ts index b9042489d09..88df5f4280b 100644 --- a/src/lib/formatters/remediation-based-format-issues.ts +++ b/src/lib/formatters/remediation-based-format-issues.ts @@ -15,7 +15,11 @@ import { } from '../../lib/snyk-test/legacy'; import { colorTextBySeverity } from '../../lib/snyk-test/common'; import { formatLegalInstructions } from './legal-license-instructions'; -import { BasicVulnInfo, UpgradesByAffectedPackage } from './types'; +import { + AppliedPolicyRules, + BasicVulnInfo, + UpgradesByAffectedPackage, +} from './types'; import { PATH_SEPARATOR } from '../constants'; import { getSeverityValue } from './get-severity-value'; import { getVulnerabilityUrl } from './get-vuln-url'; @@ -45,6 +49,7 @@ export function formatIssuesWithRemediation( note: vuln.note, legalInstructions: vuln.legalInstructionsArray, paths: vuln.list.map((v) => v.from), + appliedPolicyRules: vuln.appliedPolicyRules, }; if (vulnData.type === 'license') { @@ -140,18 +145,19 @@ function constructLicenseText( const licenseTextArray = [chalk.bold.green('\nLicense issues:')]; for (const id of Object.keys(basicLicenseInfo)) { - const licenseText = formatIssue( + const licenseText = formatIssue({ id, - basicLicenseInfo[id].title, - basicLicenseInfo[id].severity, - basicLicenseInfo[id].isNew, - `${basicLicenseInfo[id].name}@${basicLicenseInfo[id].version}`, - basicLicenseInfo[id].paths, + title: basicLicenseInfo[id].title, + severity: basicLicenseInfo[id].severity, + isNew: basicLicenseInfo[id].isNew, + vulnerableModule: `${basicLicenseInfo[id].name}@${basicLicenseInfo[id].version}`, + paths: basicLicenseInfo[id].paths, testOptions, - basicLicenseInfo[id].note, - undefined, // We can never override license rules, so no originalSeverity here - basicLicenseInfo[id].legalInstructions, - ); + note: basicLicenseInfo[id].note, + originalSeverity: undefined, // We can never override license rules, so no originalSeverity here + legalInstructions: basicLicenseInfo[id].legalInstructions, + appliedPolicyRules: basicLicenseInfo[id].appliedPolicyRules, + }); licenseTextArray.push('\n' + licenseText); } return licenseTextArray; @@ -183,17 +189,19 @@ function constructPatchesText( const patchedText = `\n Patch available for ${chalk.bold.whiteBright( packageAtVersion, )}\n`; - const thisPatchFixes = formatIssue( + const thisPatchFixes = formatIssue({ id, - basicVulnInfo[id].title, - basicVulnInfo[id].severity, - basicVulnInfo[id].isNew, - `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`, - basicVulnInfo[id].paths, + title: basicVulnInfo[id].title, + severity: basicVulnInfo[id].severity, + isNew: basicVulnInfo[id].isNew, + vulnerableModule: `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`, + paths: basicVulnInfo[id].paths, testOptions, - basicVulnInfo[id].note, - basicVulnInfo[id].originalSeverity, - ); + note: basicVulnInfo[id].note, + originalSeverity: basicVulnInfo[id].originalSeverity, + legalInstructions: [], + appliedPolicyRules: basicVulnInfo[id]?.appliedPolicyRules, + }); patchedTextArray.push(patchedText + thisPatchFixes); } @@ -214,18 +222,19 @@ function thisUpgradeFixes( ) .filter((id) => basicVulnInfo[id].type !== 'license') .map((id) => - formatIssue( + formatIssue({ id, - basicVulnInfo[id].title, - basicVulnInfo[id].severity, - basicVulnInfo[id].isNew, - `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`, - basicVulnInfo[id].paths, + title: basicVulnInfo[id].title, + severity: basicVulnInfo[id].severity, + isNew: basicVulnInfo[id].isNew, + vulnerableModule: `${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`, + paths: basicVulnInfo[id].paths, testOptions, - basicVulnInfo[id].note, - basicVulnInfo[id].originalSeverity, - [], - ), + note: basicVulnInfo[id].note, + originalSeverity: basicVulnInfo[id].originalSeverity, + legalInstructions: [], + appliedPolicyRules: basicVulnInfo[id]?.appliedPolicyRules, + }), ) .join('\n'); } @@ -366,18 +375,19 @@ function constructUnfixableText( )}` : '\n No upgrade or patch available'; unfixableIssuesTextArray.push( - formatIssue( - issue.id, - issue.title, - issue.severity, - issue.isNew, - `${issue.packageName}@${issue.version}`, - issueInfo.paths, + formatIssue({ + id: issue.id, + title: issue.title, + severity: issue.severity, + isNew: issue.isNew, + vulnerableModule: `${issue.packageName}@${issue.version}`, + paths: issueInfo.paths, testOptions, - issueInfo.note, - issueInfo.originalSeverity, - [], - ) + `${extraInfo}`, + note: issueInfo.note, + originalSeverity: issueInfo.originalSeverity, + legalInstructions: [], + appliedPolicyRules: issueInfo?.appliedPolicyRules, + }) + `${extraInfo}`, ); } @@ -394,18 +404,34 @@ export function printPath(path: string[], slice = 1) { return path.slice(slice).join(PATH_SEPARATOR); } -export function formatIssue( - id: string, - title: string, - severity: SEVERITY, - isNew: boolean, - vulnerableModule: string, - paths: string[][], - testOptions: TestOptions, - note: string | false, - originalSeverity?: SEVERITY, - legalInstructions?: LegalInstruction[], -): string { +interface IssueForDisplay { + id: string; + title: string; + severity: SEVERITY; + isNew: boolean; + vulnerableModule: string; + paths: string[][]; + testOptions: TestOptions; + note: string | false; + originalSeverity?: SEVERITY; + legalInstructions?: LegalInstruction[]; + appliedPolicyRules?: AppliedPolicyRules; +} + +function formatIssue(issue: IssueForDisplay): string { + const { + id, + title, + severity, + isNew, + vulnerableModule, + paths, + testOptions, + note, + originalSeverity, + legalInstructions, + appliedPolicyRules, + } = issue; const newBadge = isNew ? ' (new)' : ''; const name = vulnerableModule ? ` in ${chalk.bold(vulnerableModule)}` : ''; let legalLicenseInstructionsText; @@ -447,6 +473,11 @@ export function formatIssue( originalSeverityStr = ` (originally ${titleCaseText(originalSeverity)})`; } + const { value: userNote, reason: userNoteReason } = + appliedPolicyRules?.annotation || {}; + + const { reason: severityReason } = appliedPolicyRules?.severityChange || {}; + return ( colorTextBySeverity( severity, @@ -462,7 +493,10 @@ export function formatIssue( '\n Legal instructions', )}:\n ${legalLicenseInstructionsText}` : '') + - (note ? `${chalk.bold('\n Note')}:\n ${note}` : '') + (note ? `${chalk.bold('\n Note')}:\n ${note}` : '') + + (severityReason ? '\n Severity reason: ' + severityReason : '') + + (userNote ? '\n User note: ' + userNote : '') + + (userNoteReason ? '\n Note reason: ' + userNoteReason : '') ); } diff --git a/src/lib/formatters/test/format-test-results.ts b/src/lib/formatters/test/format-test-results.ts index cb7c02193fb..dff3d27d183 100644 --- a/src/lib/formatters/test/format-test-results.ts +++ b/src/lib/formatters/test/format-test-results.ts @@ -380,6 +380,8 @@ export function groupVulnerabilities( map[curr.id].dockerBaseImage = curr.dockerBaseImage; map[curr.id].nearestFixedInVersion = curr.nearestFixedInVersion; map[curr.id].legalInstructionsArray = curr.legalInstructionsArray; + map[curr.id].severityReason = curr.severityReason; + map[curr.id].appliedPolicyRules = curr.appliedPolicyRules; } map[curr.id].list.push(curr); diff --git a/src/lib/formatters/types.ts b/src/lib/formatters/types.ts index 0ec9399854a..f8008840035 100644 --- a/src/lib/formatters/types.ts +++ b/src/lib/formatters/types.ts @@ -12,6 +12,30 @@ export interface BasicVulnInfo { legalInstructions?: LegalInstruction[]; paths: string[][]; note: string | false; + severityReason?: string; + userNote?: string; + appliedPolicyRules?: AppliedPolicyRules; +} + +export interface AppliedPolicyRules { + annotation?: { + value: string; + reason?: string; + }; + severityChange?: { + newSeverity?: SEVERITY; + originalSeverity?: SEVERITY; + reason?: string; + }; + ignore?: { + path: string[]; + source?: string; + created: string; + expires?: string; + reason: string; + disregardIfFixable: boolean; + reasonType: string; + }; } interface TopLevelPackageUpgrade { diff --git a/src/lib/snyk-test/legacy.ts b/src/lib/snyk-test/legacy.ts index 36a4ba2b013..df0bd062390 100644 --- a/src/lib/snyk-test/legacy.ts +++ b/src/lib/snyk-test/legacy.ts @@ -8,6 +8,7 @@ import { import { SupportedPackageManagers } from '../package-managers'; import { Options, SupportedProjectTypes, TestOptions } from '../types'; import { SEVERITIES } from './common'; +import { AppliedPolicyRules } from '../formatters/types'; interface Pkg { name: string; @@ -56,6 +57,7 @@ export interface GroupedVuln { isFixable: boolean; fixedIn: string[]; legalInstructionsArray?: LegalInstruction[]; + appliedPolicyRules?: AppliedPolicyRules; } export interface LegalInstruction { diff --git a/test/acceptance/workspaces/npm-package-single-vuln/package-lock.json b/test/acceptance/workspaces/npm-package-single-vuln/package-lock.json new file mode 100644 index 00000000000..4903c5d9ad2 --- /dev/null +++ b/test/acceptance/workspaces/npm-package-single-vuln/package-lock.json @@ -0,0 +1,14 @@ +{ + "name": "no-fix-app", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "cxct": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/cxct/-/cxct-0.0.1-security.tgz", + "integrity": "sha512-/ET+kx45P3MjvA/RUCFSW9aQOotUCnEzGfDbcC0HHtUGyVnv7yC/djSTL6ZZvY+NUIe3vpHRsNAYq76N+rsXKg==" + } + } + } + \ No newline at end of file diff --git a/test/acceptance/workspaces/npm-package-single-vuln/package.json b/test/acceptance/workspaces/npm-package-single-vuln/package.json new file mode 100644 index 00000000000..19d92513c79 --- /dev/null +++ b/test/acceptance/workspaces/npm-package-single-vuln/package.json @@ -0,0 +1,9 @@ +{ + "name": "no-fix-app", + "version": "1.0.0", + "description": "application with annotated vulns", + "dependencies": { + "cxct": "0.0.1-security" + }, + "devDependencies": {} +} diff --git a/test/acceptance/workspaces/npm-package-single-vuln/test-graph-results-with-annotation.json b/test/acceptance/workspaces/npm-package-single-vuln/test-graph-results-with-annotation.json new file mode 100644 index 00000000000..9fa78a77c0c --- /dev/null +++ b/test/acceptance/workspaces/npm-package-single-vuln/test-graph-results-with-annotation.json @@ -0,0 +1,116 @@ +{ + "result": { + "affectedPkgs": { + "cxct@0.0.1-security": { + "pkg": { "name": "cxct", "version": "0.0.1-security" }, + "issues": { + "SNYK-JS-CXCT-535487": { + "issueId": "SNYK-JS-CXCT-535487", + "fixInfo": { "isPatchable": false, "upgradePaths": [] } + } + } + } + }, + "issuesData": { + "SNYK-JS-CXCT-535487": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "alternativeIds": [], + "creationTime": "2019-11-24T13:10:43.888332Z", + "credit": ["npm 󠅮󠅰󠅭security"], + "cvssScore": 9.8, + "description": "## Overview\n\n[cxct](https://www.npmjs.com/package/cxct) is a malicious package.\n\n\nThe package finds and exfiltrates cryptocurrency wallets.\n\n## Remediation\n\nAvoid using `cxct` altogether.\n\n\n## References\n\n- [NPM Security Advisory](https://www.npmjs.com/advisories/1344)\n", + "disclosureTime": "2019-11-22T00:24:41Z", + "exploit": "Not Defined", + "fixedIn": [], + "functions": [], + "functions_new": [], + "id": "SNYK-JS-CXCT-535487", + "identifiers": { "CVE": [], "CWE": ["CWE-506"], "NSP": [1344] }, + "language": "js", + "modificationTime": "2019-11-24T16:16:16.630345Z", + "moduleName": "cxct", + "packageManager": "npm", + "packageName": "cxct", + "patches": [], + "publicationTime": "2019-11-24T13:11:04Z", + "references": [ + { + "title": "NPM Security Advisory", + "url": "https://www.npmjs.com/advisories/1344" + } + ], + "semver": { "vulnerable": ["*"] }, + "severity": "high", + "title": "Malicious 󠅮󠅰󠅭Package", + "isPinnable": false, + "appliedPolicyRules": { + "annotation": { + "value": "This is a test user note", + "reason": "This vulnerability is a papercut and can be ignored" + } + } + } + }, + "remediation": { + "unresolved": [ + { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "alternativeIds": [], + "creationTime": "2019-11-24T13:10:43.888332Z", + "credit": ["npm 󠅮󠅰󠅭security"], + "cvssScore": 9.8, + "description": "## Overview\n\n[cxct](https://www.npmjs.com/package/cxct) is a malicious package.\n\n\nThe package finds and exfiltrates cryptocurrency wallets.\n\n## Remediation\n\nAvoid using `cxct` altogether.\n\n\n## References\n\n- [NPM Security Advisory](https://www.npmjs.com/advisories/1344)\n", + "disclosureTime": "2019-11-22T00:24:41Z", + "exploit": "Not Defined", + "fixedIn": [], + "functions": [], + "functions_new": [], + "id": "SNYK-JS-CXCT-535487", + "identifiers": { "CVE": [], "CWE": ["CWE-506"], "NSP": [1344] }, + "language": "js", + "modificationTime": "2019-11-24T16:16:16.630345Z", + "moduleName": "cxct", + "packageManager": "npm", + "packageName": "cxct", + "patches": [], + "publicationTime": "2019-11-24T13:11:04Z", + "references": [ + { + "title": "NPM Security Advisory", + "url": "https://www.npmjs.com/advisories/1344" + } + ], + "semver": { "vulnerable": ["*"] }, + "severity": "high", + "title": "Malicious 󠅮󠅰󠅭Package for you", + "isPinnable": false, + "from": ["no-fix-app@1.0.0", "cxct@0.0.1-security"], + "upgradePath": [], + "isUpgradable": false, + "isPatchable": false, + "name": "cxct", + "version": "0.0.1-security", + "appliedPolicyRules": { + "annotation": { + "value": "This is a test user note", + "reason": "This vulnerability is a papercut and can be ignored" + } + } + } + ], + "upgrade": {}, + "patch": {}, + "ignore": {}, + "pin": {} + } + }, + "meta": { + "isPrivate": true, + "isLicensesEnabled": false, + "licensesPolicy": { "severities": {}, "orgLicenseRules": {} }, + "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.14.1\nignore: {}\npatch: {}\n", + "ignoreSettings": null, + "org": "gitphill" + }, + "filesystemPolicy": false +} diff --git a/test/acceptance/workspaces/npm-package-single-vuln/test-graph-results.json b/test/acceptance/workspaces/npm-package-single-vuln/test-graph-results.json new file mode 100644 index 00000000000..484a5e108b9 --- /dev/null +++ b/test/acceptance/workspaces/npm-package-single-vuln/test-graph-results.json @@ -0,0 +1,104 @@ +{ + "result": { + "affectedPkgs": { + "cxct@0.0.1-security": { + "pkg": { "name": "cxct", "version": "0.0.1-security" }, + "issues": { + "SNYK-JS-CXCT-535487": { + "issueId": "SNYK-JS-CXCT-535487", + "fixInfo": { "isPatchable": false, "upgradePaths": [] } + } + } + } + }, + "issuesData": { + "SNYK-JS-CXCT-535487": { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "alternativeIds": [], + "creationTime": "2019-11-24T13:10:43.888332Z", + "credit": ["npm 󠅮󠅰󠅭security"], + "cvssScore": 9.8, + "description": "## Overview\n\n[cxct](https://www.npmjs.com/package/cxct) is a malicious package.\n\n\nThe package finds and exfiltrates cryptocurrency wallets.\n\n## Remediation\n\nAvoid using `cxct` altogether.\n\n\n## References\n\n- [NPM Security Advisory](https://www.npmjs.com/advisories/1344)\n", + "disclosureTime": "2019-11-22T00:24:41Z", + "exploit": "Not Defined", + "fixedIn": [], + "functions": [], + "functions_new": [], + "id": "SNYK-JS-CXCT-535487", + "identifiers": { "CVE": [], "CWE": ["CWE-506"], "NSP": [1344] }, + "language": "js", + "modificationTime": "2019-11-24T16:16:16.630345Z", + "moduleName": "cxct", + "packageManager": "npm", + "packageName": "cxct", + "patches": [], + "publicationTime": "2019-11-24T13:11:04Z", + "references": [ + { + "title": "NPM Security Advisory", + "url": "https://www.npmjs.com/advisories/1344" + } + ], + "semver": { "vulnerable": ["*"] }, + "severity": "high", + "title": "Malicious 󠅮󠅰󠅭Package", + "isPinnable": false + } + }, + "remediation": { + "unresolved": [ + { + "CVSSv3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "alternativeIds": [], + "creationTime": "2019-11-24T13:10:43.888332Z", + "credit": ["npm 󠅮󠅰󠅭security"], + "cvssScore": 9.8, + "description": "## Overview\n\n[cxct](https://www.npmjs.com/package/cxct) is a malicious package.\n\n\nThe package finds and exfiltrates cryptocurrency wallets.\n\n## Remediation\n\nAvoid using `cxct` altogether.\n\n\n## References\n\n- [NPM Security Advisory](https://www.npmjs.com/advisories/1344)\n", + "disclosureTime": "2019-11-22T00:24:41Z", + "exploit": "Not Defined", + "fixedIn": [], + "functions": [], + "functions_new": [], + "id": "SNYK-JS-CXCT-535487", + "identifiers": { "CVE": [], "CWE": ["CWE-506"], "NSP": [1344] }, + "language": "js", + "modificationTime": "2019-11-24T16:16:16.630345Z", + "moduleName": "cxct", + "packageManager": "npm", + "packageName": "cxct", + "patches": [], + "publicationTime": "2019-11-24T13:11:04Z", + "references": [ + { + "title": "NPM Security Advisory", + "url": "https://www.npmjs.com/advisories/1344" + } + ], + "semver": { "vulnerable": ["*"] }, + "severity": "high", + "title": "Malicious 󠅮󠅰󠅭Package", + "isPinnable": false, + "from": ["no-fix-app@1.0.0", "cxct@0.0.1-security"], + "upgradePath": [], + "isUpgradable": false, + "isPatchable": false, + "name": "cxct", + "version": "0.0.1-security" + } + ], + "upgrade": {}, + "patch": {}, + "ignore": {}, + "pin": {} + } + }, + "meta": { + "isPrivate": true, + "isLicensesEnabled": false, + "licensesPolicy": { "severities": {}, "orgLicenseRules": {} }, + "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.14.1\nignore: {}\npatch: {}\n", + "ignoreSettings": null, + "org": "gitphill" + }, + "filesystemPolicy": false +} diff --git a/test/fixtures/sca-dep-graph-with-annotation/test-graph-results.json b/test/fixtures/sca-dep-graph-with-annotation/test-graph-results.json new file mode 100644 index 00000000000..4ec54e2d3b3 --- /dev/null +++ b/test/fixtures/sca-dep-graph-with-annotation/test-graph-results.json @@ -0,0 +1,337 @@ +{ + "vulnerabilities": [ + { + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", + "alternativeIds": [], + "creationTime": "2021-02-01T13:11:56.558734Z", + "credit": [ + "Wang Baohua" + ], + "cvssScore": 3.1, + "description": "## Overview\n[django](https://pypi.org/project/Django/) is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.\n\nAffected versions of this package are vulnerable to Directory Traversal via the `django.utils.archive.extract()` function, which is used by `startapp --template` and `startproject --template`. This can happen via an archive with absolute paths or relative paths with dot segments.\n\n## Details\n\nA Directory Traversal attack (also known as path traversal) aims to access files and directories that are stored outside the intended folder. By manipulating files with \"dot-dot-slash (../)\" sequences and its variations, or by using absolute file paths, it may be possible to access arbitrary files and directories stored on file system, including application source code, configuration, and other critical system files.\n\nDirectory Traversal vulnerabilities can be generally divided into two types:\n\n- **Information Disclosure**: Allows the attacker to gain information about the folder structure or read the contents of sensitive files on the system.\n\n`st` is a module for serving static files on web pages, and contains a [vulnerability of this type](https://snyk.io/vuln/npm:st:20140206). In our example, we will serve files from the `public` route.\n\nIf an attacker requests the following URL from our server, it will in turn leak the sensitive private key of the root user.\n\n```\ncurl http://localhost:8080/public/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/root/.ssh/id_rsa\n```\n**Note** `%2e` is the URL encoded version of `.` (dot).\n\n- **Writing arbitrary files**: Allows the attacker to create or replace existing files. This type of vulnerability is also known as `Zip-Slip`. \n\nOne way to achieve this is by using a malicious `zip` archive that holds path traversal filenames. When each filename in the zip archive gets concatenated to the target extraction folder, without validation, the final path ends up outside of the target folder. If an executable or a configuration file is overwritten with a file containing malicious code, the problem can turn into an arbitrary code execution issue quite easily.\n\nThe following is an example of a `zip` archive with one benign file and one malicious file. Extracting the malicious file will result in traversing out of the target folder, ending up in `/root/.ssh/` overwriting the `authorized_keys` file:\n\n```\n2018-04-15 22:04:29 ..... 19 19 good.txt\n2018-04-15 22:04:42 ..... 20 20 ../../../../../../root/.ssh/authorized_keys\n```\n\n## Remediation\nUpgrade `django` to version 2.2.18, 3.0.12, 3.1.6 or higher.\n## References\n- [Django Advisory](https://www.djangoproject.com/weblog/2021/feb/01/security-releases/)\n- [GitHub Commit](https://github.com/django/django/commit/05413afa8c18cdb978fcdf470e09f7a12b234a23)\n", + "disclosureTime": "2021-02-01T12:56:31Z", + "exploit": "Not Defined", + "fixedIn": [ + "2.2.18", + "3.0.12", + "3.1.6" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-DJANGO-1066259", + "identifiers": { + "CVE": [ + "CVE-2021-3281" + ], + "CWE": [ + "CWE-22" + ] + }, + "language": "python", + "modificationTime": "2021-02-01T15:11:08.053324Z", + "moduleName": "django", + "packageManager": "pip", + "packageName": "django", + "patches": [], + "proprietary": false, + "publicationTime": "2021-02-01T15:11:08.261009Z", + "references": [ + { + "title": "Django Advisory", + "url": "https://www.djangoproject.com/weblog/2021/feb/01/security-releases/" + }, + { + "title": "GitHub Commit", + "url": "https://github.com/django/django/commit/05413afa8c18cdb978fcdf470e09f7a12b234a23" + } + ], + "semver": { + "vulnerable": [ + "[1.4,2.2.18)", + "[3.0a1,3.0.12)", + "[3.1a1,3.1.6)" + ] + }, + "severity": "medium", + "originalSeverity": "low", + "severityWithCritical": "medium", + "title": "Directory Traversal", + "from": [ + "pip-app@0.0.0", + "django@1.6.1" + ], + "appliedPolicyRules": { + "annotation": { + }, + "severityChange": { + "newSeverity": "medium", + "originalSeverity": "low", + "reason": "Not a long running service" + } + }, + "upgradePath": [], + "isUpgradable": false, + "isPatchable": false, + "name": "django", + "version": "1.6.1" + }, + { + "CVSSv3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", + "alternativeIds": [], + "creationTime": "2019-01-08T15:45:12.317736Z", + "credit": [ + "Jerbi Nessim" + ], + "cvssScore": 4.3, + "description": "## Overview\n[django](https://pypi.org/project/Django/) is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.\n\nAffected versions of this package are vulnerable to Content Spoofing. The default 404 page did not properly handle user-supplied data, an attacker could supply content to the web application, typically via a parameter value, that is reflected back to the user. This presented the user with a modified page under the context of the trusted domain.\n## Remediation\nUpgrade `django` to version 1.11.18, 2.0.10, 2.1.5 or higher.\n## References\n- [Django Project Security Blog](https://www.djangoproject.com/weblog/2019/jan/04/security-releases/)\n- [GitHub Commit](https://github.com/django/django/commit/1ecc0a395)\n- [RedHat Bugzilla Bug](https://bugzilla.redhat.com/show_bug.cgi?id=1663722)\n", + "disclosureTime": "2019-01-04T22:34:17Z", + "exploit": "Not Defined", + "fixedIn": [ + "1.11.18", + "2.0.10", + "2.1.5" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-DJANGO-72888", + "identifiers": { + "CVE": [ + "CVE-2019-3498" + ], + "CWE": [ + "CWE-148" + ] + }, + "language": "python", + "modificationTime": "2020-06-12T14:36:55.736404Z", + "moduleName": "django", + "packageManager": "pip", + "packageName": "django", + "patches": [], + "proprietary": false, + "publicationTime": "2019-01-08T16:10:39.792267Z", + "references": [ + { + "title": "Django Project Security Blog", + "url": "https://www.djangoproject.com/weblog/2019/jan/04/security-releases/" + }, + { + "title": "GitHub Commit", + "url": "https://github.com/django/django/commit/1ecc0a395" + }, + { + "title": "RedHat Bugzilla Bug", + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1663722" + } + ], + "semver": { + "vulnerable": [ + "[,1.11.18)", + "[2.0.0, 2.0.10)", + "[2.1.0, 2.1.5)" + ] + }, + "severity": "medium", + "severityWithCritical": "medium", + "title": "Content Spoofing", + "from": [ + "pip-app@0.0.0", + "django@1.6.1" + ], + "upgradePath": [], + "isUpgradable": false, + "isPatchable": false, + "name": "django", + "version": "1.6.1" + } + ], + "ok": false, + "dependencyCount": 2, + "org": "lili", + "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.19.0\nignore: {}\npatch: {}\n", + "isPrivate": true, + "licensesPolicy": { + "severities": {}, + "orgLicenseRules": { + "AGPL-1.0": { + "licenseType": "AGPL-1.0", + "severity": "high", + "instructions": "" + }, + "AGPL-3.0": { + "licenseType": "AGPL-3.0", + "severity": "high", + "instructions": "" + }, + "Artistic-1.0": { + "licenseType": "Artistic-1.0", + "severity": "medium", + "instructions": "" + }, + "Artistic-2.0": { + "licenseType": "Artistic-2.0", + "severity": "medium", + "instructions": "" + }, + "CDDL-1.0": { + "licenseType": "CDDL-1.0", + "severity": "medium", + "instructions": "" + }, + "CPOL-1.02": { + "licenseType": "CPOL-1.02", + "severity": "high", + "instructions": "" + }, + "EPL-1.0": { + "licenseType": "EPL-1.0", + "severity": "medium", + "instructions": "" + }, + "GPL-2.0": { + "licenseType": "GPL-2.0", + "severity": "high", + "instructions": "" + }, + "GPL-3.0": { + "licenseType": "GPL-3.0", + "severity": "high", + "instructions": "" + }, + "LGPL-2.0": { + "licenseType": "LGPL-2.0", + "severity": "medium", + "instructions": "" + }, + "LGPL-2.1": { + "licenseType": "LGPL-2.1", + "severity": "medium", + "instructions": "" + }, + "LGPL-3.0": { + "licenseType": "LGPL-3.0", + "severity": "medium", + "instructions": "" + }, + "MPL-1.1": { + "licenseType": "MPL-1.1", + "severity": "medium", + "instructions": "" + }, + "MPL-2.0": { + "licenseType": "MPL-2.0", + "severity": "medium", + "instructions": "" + }, + "MS-RL": { + "licenseType": "MS-RL", + "severity": "medium", + "instructions": "" + }, + "SimPL-2.0": { + "licenseType": "SimPL-2.0", + "severity": "high", + "instructions": "" + }, + "MIT": { + "licenseType": "MIT", + "severity": "high", + "instructions": "Not suitable to use, please find a different package." + } + } + }, + "packageManager": "pip", + "ignoreSettings": null, + "summary": "32 vulnerable dependency paths", + "remediation": { + "unresolved": [ + { + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", + "alternativeIds": [], + "creationTime": "2021-02-01T13:11:56.558734Z", + "credit": [ + "Wang Baohua" + ], + "cvssScore": 3.1, + "description": "## Overview\n[django](https://pypi.org/project/Django/) is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.\n\nAffected versions of this package are vulnerable to Directory Traversal via the `django.utils.archive.extract()` function, which is used by `startapp --template` and `startproject --template`. This can happen via an archive with absolute paths or relative paths with dot segments.\n\n## Details\n\nA Directory Traversal attack (also known as path traversal) aims to access files and directories that are stored outside the intended folder. By manipulating files with \"dot-dot-slash (../)\" sequences and its variations, or by using absolute file paths, it may be possible to access arbitrary files and directories stored on file system, including application source code, configuration, and other critical system files.\n\nDirectory Traversal vulnerabilities can be generally divided into two types:\n\n- **Information Disclosure**: Allows the attacker to gain information about the folder structure or read the contents of sensitive files on the system.\n\n`st` is a module for serving static files on web pages, and contains a [vulnerability of this type](https://snyk.io/vuln/npm:st:20140206). In our example, we will serve files from the `public` route.\n\nIf an attacker requests the following URL from our server, it will in turn leak the sensitive private key of the root user.\n\n```\ncurl http://localhost:8080/public/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/root/.ssh/id_rsa\n```\n**Note** `%2e` is the URL encoded version of `.` (dot).\n\n- **Writing arbitrary files**: Allows the attacker to create or replace existing files. This type of vulnerability is also known as `Zip-Slip`. \n\nOne way to achieve this is by using a malicious `zip` archive that holds path traversal filenames. When each filename in the zip archive gets concatenated to the target extraction folder, without validation, the final path ends up outside of the target folder. If an executable or a configuration file is overwritten with a file containing malicious code, the problem can turn into an arbitrary code execution issue quite easily.\n\nThe following is an example of a `zip` archive with one benign file and one malicious file. Extracting the malicious file will result in traversing out of the target folder, ending up in `/root/.ssh/` overwriting the `authorized_keys` file:\n\n```\n2018-04-15 22:04:29 ..... 19 19 good.txt\n2018-04-15 22:04:42 ..... 20 20 ../../../../../../root/.ssh/authorized_keys\n```\n\n## Remediation\nUpgrade `django` to version 2.2.18, 3.0.12, 3.1.6 or higher.\n## References\n- [Django Advisory](https://www.djangoproject.com/weblog/2021/feb/01/security-releases/)\n- [GitHub Commit](https://github.com/django/django/commit/05413afa8c18cdb978fcdf470e09f7a12b234a23)\n", + "disclosureTime": "2021-02-01T12:56:31Z", + "exploit": "Not Defined", + "fixedIn": [ + "2.2.18", + "3.0.12", + "3.1.6" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-DJANGO-1066259", + "identifiers": { + "CVE": [ + "CVE-2021-3281" + ], + "CWE": [ + "CWE-22" + ] + }, + "language": "python", + "modificationTime": "2021-02-01T15:11:08.053324Z", + "moduleName": "django", + "packageManager": "pip", + "packageName": "django", + "patches": [], + "proprietary": false, + "publicationTime": "2021-02-01T15:11:08.261009Z", + "references": [ + { + "title": "Django Advisory", + "url": "https://www.djangoproject.com/weblog/2021/feb/01/security-releases/" + }, + { + "title": "GitHub Commit", + "url": "https://github.com/django/django/commit/05413afa8c18cdb978fcdf470e09f7a12b234a23" + } + ], + "semver": { + "vulnerable": [ + "[1.4,2.2.18)", + "[3.0a1,3.0.12)", + "[3.1a1,3.1.6)" + ] + }, + "severity": "low", + "severityWithCritical": "low", + "title": "Directory Traversal", + "from": [ + "pip-app@0.0.0", + "django@1.6.1" + ], + "upgradePath": [], + "isUpgradable": false, + "isPatchable": false, + "isPinnable": true, + "name": "django", + "version": "1.6.1" + } + ], + "upgrade": {}, + "patch": {}, + "ignore": {}, + "pin": { + "django@1.6.1": { + "upgradeTo": "django@2.2.18", + "vulns": [ + "SNYK-PYTHON-DJANGO-72888" + ], + "isTransitive": false + } + } + }, + "filesystemPolicy": false, + "filtered": { + "ignore": [], + "patch": [] + }, + "uniqueCount": 32, + "projectName": "pip-app", + "foundProjectCount": 2, + "displayTargetFile": "requirements.txt" +} diff --git a/test/fixtures/sca-dep-graph-with-annotation/test-graph-user-note-results.json b/test/fixtures/sca-dep-graph-with-annotation/test-graph-user-note-results.json new file mode 100644 index 00000000000..2773c2b4a1f --- /dev/null +++ b/test/fixtures/sca-dep-graph-with-annotation/test-graph-user-note-results.json @@ -0,0 +1,333 @@ +{ + "vulnerabilities": [ + { + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", + "alternativeIds": [], + "creationTime": "2021-02-01T13:11:56.558734Z", + "credit": [ + "Wang Baohua" + ], + "cvssScore": 3.1, + "description": "## Overview\n[django](https://pypi.org/project/Django/) is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.\n\nAffected versions of this package are vulnerable to Directory Traversal via the `django.utils.archive.extract()` function, which is used by `startapp --template` and `startproject --template`. This can happen via an archive with absolute paths or relative paths with dot segments.\n\n## Details\n\nA Directory Traversal attack (also known as path traversal) aims to access files and directories that are stored outside the intended folder. By manipulating files with \"dot-dot-slash (../)\" sequences and its variations, or by using absolute file paths, it may be possible to access arbitrary files and directories stored on file system, including application source code, configuration, and other critical system files.\n\nDirectory Traversal vulnerabilities can be generally divided into two types:\n\n- **Information Disclosure**: Allows the attacker to gain information about the folder structure or read the contents of sensitive files on the system.\n\n`st` is a module for serving static files on web pages, and contains a [vulnerability of this type](https://snyk.io/vuln/npm:st:20140206). In our example, we will serve files from the `public` route.\n\nIf an attacker requests the following URL from our server, it will in turn leak the sensitive private key of the root user.\n\n```\ncurl http://localhost:8080/public/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/root/.ssh/id_rsa\n```\n**Note** `%2e` is the URL encoded version of `.` (dot).\n\n- **Writing arbitrary files**: Allows the attacker to create or replace existing files. This type of vulnerability is also known as `Zip-Slip`. \n\nOne way to achieve this is by using a malicious `zip` archive that holds path traversal filenames. When each filename in the zip archive gets concatenated to the target extraction folder, without validation, the final path ends up outside of the target folder. If an executable or a configuration file is overwritten with a file containing malicious code, the problem can turn into an arbitrary code execution issue quite easily.\n\nThe following is an example of a `zip` archive with one benign file and one malicious file. Extracting the malicious file will result in traversing out of the target folder, ending up in `/root/.ssh/` overwriting the `authorized_keys` file:\n\n```\n2018-04-15 22:04:29 ..... 19 19 good.txt\n2018-04-15 22:04:42 ..... 20 20 ../../../../../../root/.ssh/authorized_keys\n```\n\n## Remediation\nUpgrade `django` to version 2.2.18, 3.0.12, 3.1.6 or higher.\n## References\n- [Django Advisory](https://www.djangoproject.com/weblog/2021/feb/01/security-releases/)\n- [GitHub Commit](https://github.com/django/django/commit/05413afa8c18cdb978fcdf470e09f7a12b234a23)\n", + "disclosureTime": "2021-02-01T12:56:31Z", + "exploit": "Not Defined", + "fixedIn": [ + "2.2.18", + "3.0.12", + "3.1.6" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-DJANGO-1066259", + "identifiers": { + "CVE": [ + "CVE-2021-3281" + ], + "CWE": [ + "CWE-22" + ] + }, + "language": "python", + "modificationTime": "2021-02-01T15:11:08.053324Z", + "moduleName": "django", + "packageManager": "pip", + "packageName": "django", + "patches": [], + "proprietary": false, + "publicationTime": "2021-02-01T15:11:08.261009Z", + "references": [ + { + "title": "Django Advisory", + "url": "https://www.djangoproject.com/weblog/2021/feb/01/security-releases/" + }, + { + "title": "GitHub Commit", + "url": "https://github.com/django/django/commit/05413afa8c18cdb978fcdf470e09f7a12b234a23" + } + ], + "semver": { + "vulnerable": [ + "[1.4,2.2.18)", + "[3.0a1,3.0.12)", + "[3.1a1,3.1.6)" + ] + }, + "severity": "low", + "severityWithCritical": "low", + "title": "Directory Traversal", + "from": [ + "pip-app@0.0.0", + "django@1.6.1" + ], + "appliedPolicyRules": { + "annotation": { + "value": "Papercut", + "reason": "This vulnerability is a papercut and can be ignored" + } + }, + "upgradePath": [], + "isUpgradable": false, + "isPatchable": false, + "name": "django", + "version": "1.6.1" + }, + { + "CVSSv3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", + "alternativeIds": [], + "creationTime": "2019-01-08T15:45:12.317736Z", + "credit": [ + "Jerbi Nessim" + ], + "cvssScore": 4.3, + "description": "## Overview\n[django](https://pypi.org/project/Django/) is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.\n\nAffected versions of this package are vulnerable to Content Spoofing. The default 404 page did not properly handle user-supplied data, an attacker could supply content to the web application, typically via a parameter value, that is reflected back to the user. This presented the user with a modified page under the context of the trusted domain.\n## Remediation\nUpgrade `django` to version 1.11.18, 2.0.10, 2.1.5 or higher.\n## References\n- [Django Project Security Blog](https://www.djangoproject.com/weblog/2019/jan/04/security-releases/)\n- [GitHub Commit](https://github.com/django/django/commit/1ecc0a395)\n- [RedHat Bugzilla Bug](https://bugzilla.redhat.com/show_bug.cgi?id=1663722)\n", + "disclosureTime": "2019-01-04T22:34:17Z", + "exploit": "Not Defined", + "fixedIn": [ + "1.11.18", + "2.0.10", + "2.1.5" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-DJANGO-72888", + "identifiers": { + "CVE": [ + "CVE-2019-3498" + ], + "CWE": [ + "CWE-148" + ] + }, + "language": "python", + "modificationTime": "2020-06-12T14:36:55.736404Z", + "moduleName": "django", + "packageManager": "pip", + "packageName": "django", + "patches": [], + "proprietary": false, + "publicationTime": "2019-01-08T16:10:39.792267Z", + "references": [ + { + "title": "Django Project Security Blog", + "url": "https://www.djangoproject.com/weblog/2019/jan/04/security-releases/" + }, + { + "title": "GitHub Commit", + "url": "https://github.com/django/django/commit/1ecc0a395" + }, + { + "title": "RedHat Bugzilla Bug", + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1663722" + } + ], + "semver": { + "vulnerable": [ + "[,1.11.18)", + "[2.0.0, 2.0.10)", + "[2.1.0, 2.1.5)" + ] + }, + "severity": "medium", + "severityWithCritical": "medium", + "title": "Content Spoofing", + "from": [ + "pip-app@0.0.0", + "django@1.6.1" + ], + "upgradePath": [], + "isUpgradable": false, + "isPatchable": false, + "name": "django", + "version": "1.6.1" + } + ], + "ok": false, + "dependencyCount": 2, + "org": "lili", + "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.19.0\nignore: {}\npatch: {}\n", + "isPrivate": true, + "licensesPolicy": { + "severities": {}, + "orgLicenseRules": { + "AGPL-1.0": { + "licenseType": "AGPL-1.0", + "severity": "high", + "instructions": "" + }, + "AGPL-3.0": { + "licenseType": "AGPL-3.0", + "severity": "high", + "instructions": "" + }, + "Artistic-1.0": { + "licenseType": "Artistic-1.0", + "severity": "medium", + "instructions": "" + }, + "Artistic-2.0": { + "licenseType": "Artistic-2.0", + "severity": "medium", + "instructions": "" + }, + "CDDL-1.0": { + "licenseType": "CDDL-1.0", + "severity": "medium", + "instructions": "" + }, + "CPOL-1.02": { + "licenseType": "CPOL-1.02", + "severity": "high", + "instructions": "" + }, + "EPL-1.0": { + "licenseType": "EPL-1.0", + "severity": "medium", + "instructions": "" + }, + "GPL-2.0": { + "licenseType": "GPL-2.0", + "severity": "high", + "instructions": "" + }, + "GPL-3.0": { + "licenseType": "GPL-3.0", + "severity": "high", + "instructions": "" + }, + "LGPL-2.0": { + "licenseType": "LGPL-2.0", + "severity": "medium", + "instructions": "" + }, + "LGPL-2.1": { + "licenseType": "LGPL-2.1", + "severity": "medium", + "instructions": "" + }, + "LGPL-3.0": { + "licenseType": "LGPL-3.0", + "severity": "medium", + "instructions": "" + }, + "MPL-1.1": { + "licenseType": "MPL-1.1", + "severity": "medium", + "instructions": "" + }, + "MPL-2.0": { + "licenseType": "MPL-2.0", + "severity": "medium", + "instructions": "" + }, + "MS-RL": { + "licenseType": "MS-RL", + "severity": "medium", + "instructions": "" + }, + "SimPL-2.0": { + "licenseType": "SimPL-2.0", + "severity": "high", + "instructions": "" + }, + "MIT": { + "licenseType": "MIT", + "severity": "high", + "instructions": "Not suitable to use, please find a different package." + } + } + }, + "packageManager": "pip", + "ignoreSettings": null, + "summary": "32 vulnerable dependency paths", + "remediation": { + "unresolved": [ + { + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", + "alternativeIds": [], + "creationTime": "2021-02-01T13:11:56.558734Z", + "credit": [ + "Wang Baohua" + ], + "cvssScore": 3.1, + "description": "## Overview\n[django](https://pypi.org/project/Django/) is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.\n\nAffected versions of this package are vulnerable to Directory Traversal via the `django.utils.archive.extract()` function, which is used by `startapp --template` and `startproject --template`. This can happen via an archive with absolute paths or relative paths with dot segments.\n\n## Details\n\nA Directory Traversal attack (also known as path traversal) aims to access files and directories that are stored outside the intended folder. By manipulating files with \"dot-dot-slash (../)\" sequences and its variations, or by using absolute file paths, it may be possible to access arbitrary files and directories stored on file system, including application source code, configuration, and other critical system files.\n\nDirectory Traversal vulnerabilities can be generally divided into two types:\n\n- **Information Disclosure**: Allows the attacker to gain information about the folder structure or read the contents of sensitive files on the system.\n\n`st` is a module for serving static files on web pages, and contains a [vulnerability of this type](https://snyk.io/vuln/npm:st:20140206). In our example, we will serve files from the `public` route.\n\nIf an attacker requests the following URL from our server, it will in turn leak the sensitive private key of the root user.\n\n```\ncurl http://localhost:8080/public/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/root/.ssh/id_rsa\n```\n**Note** `%2e` is the URL encoded version of `.` (dot).\n\n- **Writing arbitrary files**: Allows the attacker to create or replace existing files. This type of vulnerability is also known as `Zip-Slip`. \n\nOne way to achieve this is by using a malicious `zip` archive that holds path traversal filenames. When each filename in the zip archive gets concatenated to the target extraction folder, without validation, the final path ends up outside of the target folder. If an executable or a configuration file is overwritten with a file containing malicious code, the problem can turn into an arbitrary code execution issue quite easily.\n\nThe following is an example of a `zip` archive with one benign file and one malicious file. Extracting the malicious file will result in traversing out of the target folder, ending up in `/root/.ssh/` overwriting the `authorized_keys` file:\n\n```\n2018-04-15 22:04:29 ..... 19 19 good.txt\n2018-04-15 22:04:42 ..... 20 20 ../../../../../../root/.ssh/authorized_keys\n```\n\n## Remediation\nUpgrade `django` to version 2.2.18, 3.0.12, 3.1.6 or higher.\n## References\n- [Django Advisory](https://www.djangoproject.com/weblog/2021/feb/01/security-releases/)\n- [GitHub Commit](https://github.com/django/django/commit/05413afa8c18cdb978fcdf470e09f7a12b234a23)\n", + "disclosureTime": "2021-02-01T12:56:31Z", + "exploit": "Not Defined", + "fixedIn": [ + "2.2.18", + "3.0.12", + "3.1.6" + ], + "functions": [], + "functions_new": [], + "id": "SNYK-PYTHON-DJANGO-1066259", + "identifiers": { + "CVE": [ + "CVE-2021-3281" + ], + "CWE": [ + "CWE-22" + ] + }, + "language": "python", + "modificationTime": "2021-02-01T15:11:08.053324Z", + "moduleName": "django", + "packageManager": "pip", + "packageName": "django", + "patches": [], + "proprietary": false, + "publicationTime": "2021-02-01T15:11:08.261009Z", + "references": [ + { + "title": "Django Advisory", + "url": "https://www.djangoproject.com/weblog/2021/feb/01/security-releases/" + }, + { + "title": "GitHub Commit", + "url": "https://github.com/django/django/commit/05413afa8c18cdb978fcdf470e09f7a12b234a23" + } + ], + "semver": { + "vulnerable": [ + "[1.4,2.2.18)", + "[3.0a1,3.0.12)", + "[3.1a1,3.1.6)" + ] + }, + "severity": "low", + "severityWithCritical": "low", + "title": "Directory Traversal", + "from": [ + "pip-app@0.0.0", + "django@1.6.1" + ], + "upgradePath": [], + "isUpgradable": false, + "isPatchable": false, + "isPinnable": true, + "name": "django", + "version": "1.6.1" + } + ], + "upgrade": {}, + "patch": {}, + "ignore": {}, + "pin": { + "django@1.6.1": { + "upgradeTo": "django@2.2.18", + "vulns": [ + "SNYK-PYTHON-DJANGO-72888" + ], + "isTransitive": false + } + } + }, + "filesystemPolicy": false, + "filtered": { + "ignore": [], + "patch": [] + }, + "uniqueCount": 32, + "projectName": "pip-app", + "foundProjectCount": 2, + "displayTargetFile": "requirements.txt" +} diff --git a/test/jest/acceptance/snyk-test/human-formatted-output.spec.ts b/test/jest/acceptance/snyk-test/human-formatted-output.spec.ts new file mode 100644 index 00000000000..19e2a2d8072 --- /dev/null +++ b/test/jest/acceptance/snyk-test/human-formatted-output.spec.ts @@ -0,0 +1,66 @@ +import { fakeServer } from '../../../acceptance/fake-server'; +import { createProjectFromWorkspace } from '../../util/createProject'; +import { getServerPort } from '../../util/getServerPort'; +import { runSnykCLI } from '../../util/runSnykCLI'; +const stripAnsi = require('strip-ansi'); + +jest.setTimeout(1000 * 60); + +describe('test formatting for human consumption', () => { + let server: ReturnType; + let env: Record; + + beforeAll((done) => { + const apiPath = '/api/v1'; + const apiPort = getServerPort(process); + env = { + ...process.env, + SNYK_API: 'http://localhost:' + apiPort + apiPath, + SNYK_TOKEN: '123456789', + SNYK_DISABLE_ANALYTICS: '1', + }; + + server = fakeServer(apiPath, env.SNYK_TOKEN); + server.listen(apiPort, () => done()); + }); + + afterEach(() => { + server.restore(); + }); + + afterAll((done) => { + server.close(() => done()); + }); + + it('includes a summary of vulnerabilites and paths', async () => { + const project = await createProjectFromWorkspace('npm-package-single-vuln'); + server.setCustomResponse(await project.readJSON('test-graph-results.json')); + + const { code, stdout } = await runSnykCLI(`test`, { + cwd: project.path(), + env, + }); + + expect(code).toEqual(1); + expect(stripAnsi(stdout)).toContain( + 'Tested 1 dependencies for known issues, found 1 issue, 1 vulnerable path.', + ); + expect(server.getRequests().length).toBeGreaterThanOrEqual(1); + }); + + it('includes a user note and reason', async () => { + const project = await createProjectFromWorkspace('npm-package-single-vuln'); + server.setCustomResponse( + await project.readJSON('test-graph-results-with-annotation.json'), + ); + + const { code, stdout } = await runSnykCLI(`test`, { + cwd: project.path(), + env, + }); + + expect(code).toEqual(1); + expect(stripAnsi(stdout)).toContain('User note: This is a test user note'); + expect(server.getRequests().length).toBeGreaterThanOrEqual(1); + }); +}); diff --git a/test/jest/unit/lib/formatters/__snapshots__/remediation-based-format-issues.spec.ts.snap b/test/jest/unit/lib/formatters/__snapshots__/remediation-based-format-issues.spec.ts.snap index 2b73b1ae702..2b8c1c7bd66 100644 --- a/test/jest/unit/lib/formatters/__snapshots__/remediation-based-format-issues.spec.ts.snap +++ b/test/jest/unit/lib/formatters/__snapshots__/remediation-based-format-issues.spec.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`with license issues 1`] = ` +exports[`formatIssuesWithRemediation with license issues 1`] = ` " Issues to fix by upgrading: @@ -47,7 +47,7 @@ License issues: ○ for LGPL-3.0 license: I am legal license instruction" `; -exports[`with pins & unfixable & showVulnsPaths = all 1`] = ` +exports[`formatIssuesWithRemediation with pins & unfixable & showVulnsPaths = all 1`] = ` " Issues to fix by upgrading dependencies: @@ -64,7 +64,7 @@ Issues with no direct upgrade or patch: This issue was fixed in versions: 2.2.18, 3.0.12, 3.1.6" `; -exports[`with showVulnPaths = some 1`] = ` +exports[`formatIssuesWithRemediation with showVulnPaths = some 1`] = ` " Issues to fix by upgrading dependencies: @@ -79,7 +79,7 @@ Issues with no direct upgrade or patch: This issue was fixed in versions: 2.2.18, 3.0.12, 3.1.6" `; -exports[`with upgrades & patches 1`] = ` +exports[`formatIssuesWithRemediation with upgrades & patches 1`] = ` " Issues to fix by upgrading: diff --git a/test/jest/unit/lib/formatters/remediation-based-format-issues.spec.ts b/test/jest/unit/lib/formatters/remediation-based-format-issues.spec.ts index fb415b35098..ace1be59656 100644 --- a/test/jest/unit/lib/formatters/remediation-based-format-issues.spec.ts +++ b/test/jest/unit/lib/formatters/remediation-based-format-issues.spec.ts @@ -6,105 +6,172 @@ import { formatIssuesWithRemediation } from '../../../../../src/lib/formatters/r import { getFixturePath } from '../../../util/getFixturePath'; import { getWorkspacePath } from '../../../util/getWorkspacePath'; -it('with pins & unfixable & showVulnsPaths = all', () => { - const withRemediation = JSON.parse( - fs.readFileSync( - getFixturePath('pip-app-with-remediation/test-graph-results.json'), - 'utf8', - ), - ); - const groupedVulns = groupVulnerabilities(withRemediation.vulnerabilities); - - const sortedGroupedVulns = orderBy( - groupedVulns, - ['metadata.severityValue', 'metadata.name'], - ['asc', 'desc'], - ); - - const res = formatIssuesWithRemediation( - sortedGroupedVulns, - withRemediation.remediation, - { showVulnPaths: 'all' }, - ); - expect( - stripAnsi(res.join('\n').replace(/\[http.*\]/g, '[URL]')), - ).toMatchSnapshot(); -}); +describe('formatIssuesWithRemediation', () => { + it('with pins & unfixable & showVulnsPaths = all', () => { + const withRemediation = JSON.parse( + fs.readFileSync( + getFixturePath('pip-app-with-remediation/test-graph-results.json'), + 'utf8', + ), + ); + const groupedVulns = groupVulnerabilities(withRemediation.vulnerabilities); -it('with showVulnPaths = some', () => { - const withRemediation = JSON.parse( - fs.readFileSync( - getFixturePath('pip-app-with-remediation/test-graph-results.json'), - 'utf8', - ), - ); - const groupedVulns = groupVulnerabilities(withRemediation.vulnerabilities); - - const sortedGroupedVulns = orderBy( - groupedVulns, - ['metadata.severityValue', 'metadata.name'], - ['asc', 'desc'], - ); - - const res = formatIssuesWithRemediation( - sortedGroupedVulns, - withRemediation.remediation, - { showVulnPaths: 'some' }, - ); - expect( - stripAnsi(res.join('\n').replace(/\[http.*\]/g, '[URL]')), - ).toMatchSnapshot(); -}); -it('with upgrades & patches', () => { - const withRemediation = JSON.parse( - fs.readFileSync( - getFixturePath( - 'npm-package-with-severity-override/test-graph-result-patches.json', + const sortedGroupedVulns = orderBy( + groupedVulns, + ['metadata.severityValue', 'metadata.name'], + ['asc', 'desc'], + ); + + const res = formatIssuesWithRemediation( + sortedGroupedVulns, + withRemediation.remediation, + { showVulnPaths: 'all' }, + ); + expect( + stripAnsi(res.join('\n').replace(/\[http.*\]/g, '[URL]')), + ).toMatchSnapshot(); + }); + + it('with showVulnPaths = some', () => { + const withRemediation = JSON.parse( + fs.readFileSync( + getFixturePath('pip-app-with-remediation/test-graph-results.json'), + 'utf8', ), - 'utf8', - ), - ); - const groupedVulns = groupVulnerabilities(withRemediation.vulnerabilities); - - const sortedGroupedVulns = orderBy( - groupedVulns, - ['metadata.severityValue', 'metadata.name'], - ['asc', 'desc'], - ); - - const res = formatIssuesWithRemediation( - sortedGroupedVulns, - withRemediation.remediation, - { showVulnPaths: 'all' }, - ); - expect( - stripAnsi(res.join('\n').replace(/\[http.*\]/g, '[URL]')), - ).toMatchSnapshot(); -}); + ); + const groupedVulns = groupVulnerabilities(withRemediation.vulnerabilities); + + const sortedGroupedVulns = orderBy( + groupedVulns, + ['metadata.severityValue', 'metadata.name'], + ['asc', 'desc'], + ); -it('with license issues', () => { - const withRemediation = JSON.parse( - fs.readFileSync( - getWorkspacePath( - 'ruby-app/test-graph-response-with-legal-instruction.json', + const res = formatIssuesWithRemediation( + sortedGroupedVulns, + withRemediation.remediation, + { showVulnPaths: 'some' }, + ); + expect( + stripAnsi(res.join('\n').replace(/\[http.*\]/g, '[URL]')), + ).toMatchSnapshot(); + }); + + it('with upgrades & patches', () => { + const withRemediation = JSON.parse( + fs.readFileSync( + getFixturePath( + 'npm-package-with-severity-override/test-graph-result-patches.json', + ), + 'utf8', + ), + ); + const groupedVulns = groupVulnerabilities(withRemediation.vulnerabilities); + + const sortedGroupedVulns = orderBy( + groupedVulns, + ['metadata.severityValue', 'metadata.name'], + ['asc', 'desc'], + ); + + const res = formatIssuesWithRemediation( + sortedGroupedVulns, + withRemediation.remediation, + { showVulnPaths: 'all' }, + ); + expect( + stripAnsi(res.join('\n').replace(/\[http.*\]/g, '[URL]')), + ).toMatchSnapshot(); + }); + + it('with license issues', () => { + const withRemediation = JSON.parse( + fs.readFileSync( + getWorkspacePath( + 'ruby-app/test-graph-response-with-legal-instruction.json', + ), + 'utf8', ), - 'utf8', - ), - ); - const groupedVulns = groupVulnerabilities(withRemediation.vulnerabilities); - - const sortedGroupedVulns = orderBy( - groupedVulns, - ['metadata.severityValue', 'metadata.name'], - ['asc', 'desc'], - ); - - const res = formatIssuesWithRemediation( - sortedGroupedVulns, - withRemediation.remediation, - { showVulnPaths: 'all' }, - ); - expect( - stripAnsi(res.join('\n').replace(/\[http.*\]/g, '[URL]')), - ).toMatchSnapshot(); + ); + const groupedVulns = groupVulnerabilities(withRemediation.vulnerabilities); + + const sortedGroupedVulns = orderBy( + groupedVulns, + ['metadata.severityValue', 'metadata.name'], + ['asc', 'desc'], + ); + + const res = formatIssuesWithRemediation( + sortedGroupedVulns, + withRemediation.remediation, + { showVulnPaths: 'all' }, + ); + expect( + stripAnsi(res.join('\n').replace(/\[http.*\]/g, '[URL]')), + ).toMatchSnapshot(); + }); + + it('includes severity change reason', () => { + const withRemediation = JSON.parse( + fs.readFileSync( + getFixturePath('sca-dep-graph-with-annotation/test-graph-results.json'), + 'utf8', + ), + ); + const groupedVulns = groupVulnerabilities(withRemediation.vulnerabilities); + + const sortedGroupedVulns = orderBy( + groupedVulns, + ['metadata.severityValue', 'metadata.name'], + ['asc', 'desc'], + ); + + const res = formatIssuesWithRemediation( + sortedGroupedVulns, + withRemediation.remediation, + { showVulnPaths: 'all' }, + ); + const plainText = res.join('\n'); + expect(plainText).toContain('Severity reason: Not a long running service'); + + // Severity reason should only appear when attribute is present + const rgex = new RegExp(/Severity reason/, 'g'); + expect(plainText.match(rgex)).toHaveLength(1); + }); + + it('includes user note and reason when available', () => { + const withRemediation = JSON.parse( + fs.readFileSync( + getFixturePath( + 'sca-dep-graph-with-annotation/test-graph-user-note-results.json', + ), + 'utf8', + ), + ); + const groupedVulns = groupVulnerabilities(withRemediation.vulnerabilities); + + const sortedGroupedVulns = orderBy( + groupedVulns, + ['metadata.severityValue', 'metadata.name'], + ['asc', 'desc'], + ); + + const res = formatIssuesWithRemediation( + sortedGroupedVulns, + withRemediation.remediation, + { showVulnPaths: 'all' }, + ); + const plainText = res.join('\n'); + expect(plainText).toContain('User note: Papercut'); + expect(plainText).toContain( + 'Note reason: This vulnerability is a papercut and can be ignored', + ); + + // User note should only appear when attribute is present + const rgex = new RegExp(/User note/, 'g'); + expect(plainText.match(rgex)).toHaveLength(1); + + const rgex2 = new RegExp(/Note reason/, 'g'); + expect(plainText.match(rgex2)).toHaveLength(1); + }); });