Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add legal instructions #726

Merged
merged 3 commits into from Aug 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -56,6 +56,7 @@
"@types/restify": "^4.3.6",
"abbrev": "^1.1.1",
"ansi-escapes": "3.2.0",
"wrap-ansi": "^5.1.0",
"chalk": "^2.4.2",
"configstore": "^3.1.2",
"debug": "^3.1.0",
Expand Down
6 changes: 5 additions & 1 deletion src/cli/commands/test/formatters/legacy-format-issue.ts
@@ -1,5 +1,6 @@
import * as _ from 'lodash';
import chalk from 'chalk';
import * as wrap from 'wrap-ansi';
import * as config from '../../../../lib/config';
import {Options, TestOptions} from '../../../../lib/types';
import {isLocalFolder} from '../../../../lib/detect';
Expand Down Expand Up @@ -42,6 +43,8 @@ export function formatIssues(vuln: GroupedVuln, options: Options & TestOptions)
: '',
fixedIn: options.docker ? createFixedInText(vuln) : '',
dockerfilePackage: options.docker ? dockerfileInstructionText(vuln) : '',
legalInstructions: vuln.legalInstructions ? '\n Legal instructions:\n '
+ wrap(vuln.legalInstructions, 100).split('\n').join('\n ') : '',
};

return (
Expand All @@ -54,7 +57,8 @@ export function formatIssues(vuln: GroupedVuln, options: Options & TestOptions)
vulnOutput.remediationInfo +
vulnOutput.dockerfilePackage +
vulnOutput.fixedIn +
vulnOutput.extraInfo
vulnOutput.extraInfo +
vulnOutput.legalInstructions
);
}

Expand Down
35 changes: 22 additions & 13 deletions src/cli/commands/test/formatters/remediation-based-format-issues.ts
@@ -1,5 +1,6 @@
import * as _ from 'lodash';
import chalk from 'chalk';
import * as wrap from 'wrap-ansi';
import * as config from '../../../../lib/config';
import { TestOptions } from '../../../../lib/types';
import { RemediationResult, PatchRemediation,
Expand All @@ -13,13 +14,14 @@ interface BasicVulnInfo {
name: string;
version: string;
fixedIn: string[];
legalInstructions?: string;
}

export function formatIssuesWithRemediation(
vulns: GroupedVuln[],
remediationInfo: RemediationResult,
options: TestOptions,
): string[] {
): string[] {

const basicVulnInfo: {
[name: string]: BasicVulnInfo,
Expand All @@ -33,6 +35,7 @@ export function formatIssuesWithRemediation(
name: vuln.name,
version: vuln.version,
fixedIn: vuln.fixedIn,
legalInstructions: vuln.legalInstructions,
};
}
const results = [chalk.bold.white('Remediation advice')];
Expand Down Expand Up @@ -68,7 +71,7 @@ function constructPatchesText(
basicVulnInfo: {
[name: string]: BasicVulnInfo;
},
): string[] {
): string[] {

if (!(Object.keys(patches).length > 0)) {
return [];
Expand All @@ -85,7 +88,8 @@ function constructPatchesText(
basicVulnInfo[id].title,
basicVulnInfo[id].severity,
basicVulnInfo[id].isNew,
`${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`);
`${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`,
basicVulnInfo[id].legalInstructions);
patchedTextArray.push(patchedText + thisPatchFixes);
}

Expand All @@ -97,7 +101,7 @@ function constructUpgradesText(
basicVulnInfo: {
[name: string]: BasicVulnInfo;
},
): string[] {
): string[] {

if (!(Object.keys(upgrades).length > 0)) {
return [];
Expand All @@ -108,15 +112,16 @@ function constructUpgradesText(
const upgradeDepTo = _.get(upgrades, [upgrade, 'upgradeTo']);
const vulnIds = _.get(upgrades, [upgrade, 'vulns']);
const upgradeText =
`\n Upgrade ${chalk.bold.whiteBright(upgrade)} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix\n`;
`\n Upgrade ${chalk.bold.whiteBright(upgrade)} to ${chalk.bold.whiteBright(upgradeDepTo)} to fix\n`;
const thisUpgradeFixes = vulnIds
.sort((a, b) => getSeverityValue(basicVulnInfo[a].severity) - getSeverityValue(basicVulnInfo[b].severity))
.map((id) => formatIssue(
id,
basicVulnInfo[id].title,
basicVulnInfo[id].severity,
basicVulnInfo[id].isNew,
`${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`))
id,
basicVulnInfo[id].title,
basicVulnInfo[id].severity,
basicVulnInfo[id].isNew,
`${basicVulnInfo[id].name}@${basicVulnInfo[id].version}`,
basicVulnInfo[id].legalInstructions))
.join('\n');
upgradeTextArray.push(upgradeText + thisUpgradeFixes);
}
Expand All @@ -140,8 +145,8 @@ function constructUnfixableText(unresolved: IssueData[]) {
issue.id,
issue.title,
issue.severity,
issue.isNew) + `${extraInfo}`,
);
issue.isNew,
issue.legalInstructions) + `${extraInfo}`);
}

return unfixableIssuesTextArray;
Expand All @@ -152,6 +157,7 @@ function formatIssue(
title: string,
severity: SEVERITY,
isNew: boolean,
legalInstructions?: string,
vulnerableModule?: string): string {
const severitiesColourMapping = {
low: {
Expand All @@ -172,10 +178,13 @@ function formatIssue(
};
const newBadge = isNew ? ' (new)' : '';
const name = vulnerableModule ? ` in ${chalk.bold(vulnerableModule)}` : '';
const wrapLegalText = wrap(`${legalInstructions}`, 100);
const formatLegalText = wrapLegalText.split('\n').join('\n ');

return severitiesColourMapping[severity].colorFunc(
` ✗ ${chalk.bold(title)}${newBadge} [${titleCaseText(severity)} Severity]`,
) + `[${config.ROOT}/vuln/${id}]` + name;
) + `[${config.ROOT}/vuln/${id}]` + name
+ (legalInstructions ? `${chalk.bold('\n Legal instructions')}:\n ${formatLegalText}` : '');
}

function titleCaseText(text) {
Expand Down
1 change: 1 addition & 0 deletions src/cli/commands/test/index.ts
Expand Up @@ -475,6 +475,7 @@ function groupVulnerabilities(vulns): GroupedVuln[] {
map[curr.id].dockerfileInstruction = curr.dockerfileInstruction;
map[curr.id].dockerBaseImage = curr.dockerBaseImage;
map[curr.id].nearestFixedInVersion = curr.nearestFixedInVersion;
map[curr.id].legalInstructions = curr.legalInstructions;
}

map[curr.id].list.push(curr);
Expand Down
2 changes: 2 additions & 0 deletions src/lib/snyk-test/legacy.ts
Expand Up @@ -49,6 +49,7 @@ export interface GroupedVuln {
dockerfileInstruction: string;
dockerBaseImage: string;
nearestFixedInVersion: string;
legalInstructions?: string;
}

export interface IssueData {
Expand All @@ -70,6 +71,7 @@ export interface IssueData {
title: string;
severity: SEVERITY;
fixedIn: string[];
legalInstructions?: string;
}

interface AnnotatedIssue extends IssueData {
Expand Down
35 changes: 35 additions & 0 deletions test/acceptance/display-test-results.test.ts
Expand Up @@ -30,6 +30,41 @@ test('`test ruby-app` remediation displayed', async (t) => {
t.end();
});


test('`test ruby-app` legal instructions displayed', async (t) => {
chdirWorkspaces();
const stubbedResponse = JSON.parse(
fs.readFileSync(__dirname + '/workspaces/ruby-app/test-graph-response-with-legal-instruction.json', 'utf8'),
);
const snykTestStub = sinon.stub(snyk, 'test').returns(stubbedResponse);
try {
await snykTest('ruby-app');
} catch (error) {
const res = error.message;
t.match(res, 'Legal instructions');
}

snykTestStub.restore();
t.end();
});

test('`test pip-app-license-issue` legal instructions displayed (legacy formatter)', async (t) => {
chdirWorkspaces();
const stubbedResponse = JSON.parse(
fs.readFileSync(__dirname + '/workspaces/pip-app-license-issue/test-pip-stub-with-legal-instructions.json', 'utf8'),
);
const snykTestStub = sinon.stub(snyk, 'test').returns(stubbedResponse);
try {
await snykTest('pip-app-license-issue');
} catch (error) {
const res = error.message;
t.match(res, 'Legal instructions');
}

snykTestStub.restore();
t.end();
});

function chdirWorkspaces(subdir: string = '') {
process.chdir(__dirname + '/workspaces' + (subdir ? '/' + subdir : ''));
}
@@ -0,0 +1,3 @@
# The following library requires Python >= 3.4.2
# For more see: https://pypi.python.org/pypi?:action=browse&show=all&c=595
aiohttp==2.2.2
@@ -0,0 +1,51 @@
{
"vulnerabilities": [
{
"license": "LGPL-3.0",
"semver": {
"vulnerable": [
"[0,)"
]
},
"id": "snyk:lic:pip:chardet:LGPL-3.0",
"type": "license",
"legalInstructions": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"packageManager": "pip",
"language": "python",
"packageName": "chardet",
"title": "LGPL-3.0 license",
"description": "LGPL-3.0 license",
"publicationTime": "2019-04-11T10:30:09.818Z",
"creationTime": "2019-04-11T10:30:09.818Z",
"patches": [],
"licenseTemplateUrl": "https://raw.githubusercontent.com/spdx/license-list/master/LGPL-3.0.txt",
"severity": "medium",
"from": [
"python-pip3-app-no-policy@0.0.0",
"aiohttp@2.2.2",
"chardet@3.0.4"
],
"upgradePath": [],
"isUpgradable": false,
"isPatchable": false,
"name": "chardet",
"version": "3.0.4"
}
],
"ok": false,
"dependencyCount": 6,
"org": "lwywoo",
"policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.13.5\nignore: {}\npatch: {}\n",
"isPrivate": true,
"licensesPolicy": null,
"packageManager": "pip",
"ignoreSettings": null,
"summary": "1 vulnerable dependency path",
"filesystemPolicy": false,
"filtered": {
"ignore": [],
"patch": []
},
"uniqueCount": 1,
"path": "/Users/snyk/laura/fixtures/python-pip3-app-no-policy"
}