Commit d0a1e96a authored by Fatih Acet's avatar Fatih Acet

Merge branch '5656-new-syntax-support-FE' into 'master'

Support new syntax for SAST/Dependency Scanning

See merge request gitlab-org/gitlab-ee!8869
parents aa2e8afa fb3b0edf
......@@ -62,7 +62,7 @@ function fileUrl(location, pathPrefix) {
* @param {Object} issue
* @returns {Object}
*/
function adaptDeprecatedFormat(issue) {
function adaptDeprecatedIssueFormat(issue) {
// Skip issue with new format (old format does not have a location property)
if (issue.location) {
return issue;
......@@ -90,18 +90,35 @@ function adaptDeprecatedFormat(issue) {
return adapted;
}
/**
*
* Wraps old report formats (plain array of vulnerabilities).
*
* @param {Array|Object} report
* @returns {Object}
*/
function adaptDeprecatedReportFormat(report) {
if (Array.isArray(report)) {
return {
vulnerabilities: report,
};
}
return report;
}
/**
* Parses SAST results into a common format to allow to use the same Vue component.
*
* @param {Array} issues
* @param {Array|Object} report
* @param {Array} feedback
* @param {String} path
* @returns {Array}
*/
export const parseSastIssues = (issues = [], feedback = [], path = '') =>
issues.map(issue => {
export const parseSastIssues = (report = [], feedback = [], path = '') =>
adaptDeprecatedReportFormat(report).vulnerabilities.map(issue => {
const parsed = {
...adaptDeprecatedFormat(issue),
...adaptDeprecatedIssueFormat(issue),
category: 'sast',
project_fingerprint: sha1(issue.cve),
title: issue.message,
......@@ -118,15 +135,15 @@ export const parseSastIssues = (issues = [], feedback = [], path = '') =>
/**
* Parses Dependency Scanning results into a common format to allow to use the same Vue component.
*
* @param {Array} issues
* @param {Array|Object} report
* @param {Array} feedback
* @param {String} path
* @returns {Array}
*/
export const parseDependencyScanningIssues = (issues = [], feedback = [], path = '') =>
issues.map(issue => {
export const parseDependencyScanningIssues = (report = [], feedback = [], path = '') =>
adaptDeprecatedReportFormat(report).vulnerabilities.map(issue => {
const parsed = {
...adaptDeprecatedFormat(issue),
...adaptDeprecatedIssueFormat(issue),
category: 'dependency_scanning',
project_fingerprint: sha1(issue.cve || issue.message),
title: issue.message,
......
---
title: Support for new SAST and dependency scanning report format
merge_request: 8869
author:
type: other
......@@ -113,6 +113,11 @@ export const sastIssues = [
},
];
export const sastIssuesMajor2 = {
version: '2.0',
vulnerabilities: sastIssues,
};
export const oldSastIssues = [
{
tool: 'bundler_audit',
......@@ -359,7 +364,7 @@ export const parsedSastBaseStore = [
},
];
export const dependencyScanningIssues = [
export const dependencyScanningIssuesOld = [
{
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
......@@ -389,6 +394,111 @@ export const dependencyScanningIssues = [
},
];
export const dependencyScanningIssues = [
{
category: 'dependency_scanning',
message: 'ruby-ffi DDL loading issue on Windows OS',
cve: 'sast-sample-rails/Gemfile.lock:ffi:cve:CVE-2018-1000201',
severity: 'High',
solution: 'upgrade to \u003e= 1.9.24',
scanner: {
id: 'bundler_audit',
name: 'bundler-audit',
},
location: {
file: 'sast-sample-rails/Gemfile.lock',
dependency: {
package: {
name: 'ffi',
},
version: '1.9.18',
},
},
identifiers: [
{
type: 'cve',
name: 'CVE-2018-1000201',
value: 'CVE-2018-1000201',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201',
},
],
links: [
{
url: 'https://github.com/ffi/ffi/releases/tag/1.9.24',
},
],
},
{
category: 'dependency_scanning',
message: 'XSS vulnerability in rails-html-sanitizer',
cve: 'sast-sample-rails/Gemfile.lock:rails-html-sanitizer:cve:CVE-2018-3741',
severity: 'Unknown',
solution: 'upgrade to \u003e= 1.0.4',
scanner: {
id: 'bundler_audit',
name: 'bundler-audit',
},
location: {
file: 'sast-sample-rails/Gemfile.lock',
dependency: {
package: {
name: 'rails-html-sanitizer',
},
version: '1.0.3',
},
},
identifiers: [
{
type: 'cve',
name: 'CVE-2018-3741',
value: 'CVE-2018-3741',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-3741',
},
],
links: [
{
url: 'https://groups.google.com/d/msg/rubyonrails-security/tP7W3kLc5u4/uDy2Br7xBgAJ',
},
],
},
{
category: 'dependency_scanning',
message: 'Vulnerability in ansi2html',
cve: ':ansi2html:npm:51',
severity: 'High',
scanner: {
id: 'retire.js',
name: 'Retire.js',
},
location: {
dependency: {
package: {
name: 'ansi2html',
},
version: '0.0.1',
},
},
identifiers: [
{
type: 'npm',
name: 'NPM-51',
value: '51',
url: 'https://www.npmjs.com/advisories/51',
},
],
links: [
{
url: 'https://nodesecurity.io/advisories/51',
},
],
},
];
export const dependencyScanningIssuesMajor2 = {
version: '2.0',
vulnerabilities: dependencyScanningIssues,
};
export const dependencyScanningIssuesBase = [
{
tool: 'bundler_audit',
......
......@@ -7,7 +7,7 @@ import {
parsedSastIssuesHead,
parsedSastBaseStore,
parsedSastIssuesStore,
dependencyScanningIssues,
dependencyScanningIssuesOld,
dependencyScanningIssuesBase,
parsedDependencyScanningIssuesHead,
parsedDependencyScanningBaseStore,
......@@ -303,7 +303,7 @@ describe('security reports mutations', () => {
mutations[types.SET_BASE_BLOB_PATH](stateCopy, 'path');
mutations[types.SET_HEAD_BLOB_PATH](stateCopy, 'path');
mutations[types.RECEIVE_DEPENDENCY_SCANNING_REPORTS](stateCopy, {
head: dependencyScanningIssues,
head: dependencyScanningIssuesOld,
base: dependencyScanningIssuesBase,
});
......@@ -321,7 +321,7 @@ describe('security reports mutations', () => {
it('should set new issues', () => {
mutations[types.SET_HEAD_BLOB_PATH](stateCopy, 'path');
mutations[types.RECEIVE_DEPENDENCY_SCANNING_REPORTS](stateCopy, {
head: dependencyScanningIssues,
head: dependencyScanningIssuesOld,
});
expect(stateCopy.dependencyScanning.isLoading).toEqual(false);
......
......@@ -13,8 +13,11 @@ import {
import {
oldSastIssues,
sastIssues,
sastIssuesMajor2,
sastFeedbacks,
dependencyScanningIssuesOld,
dependencyScanningIssues,
dependencyScanningIssuesMajor2,
dependencyScanningFeedbacks,
dockerReport,
containerScanningFeedbacks,
......@@ -75,6 +78,18 @@ describe('security reports utils', () => {
expect(parsed.project_fingerprint).toEqual(sha1(sastIssues[0].cve));
});
it('should parse the received issues with new JSON format (2.0)', () => {
const raw = sastIssues[0];
const parsed = parseSastIssues(sastIssuesMajor2, [], 'path')[0];
expect(parsed.title).toEqual(raw.message);
expect(parsed.path).toEqual(raw.location.file);
expect(parsed.location.start_line).toEqual(raw.location.start_line);
expect(parsed.location.end_line).toEqual(raw.location.end_line);
expect(parsed.urlPath).toEqual('path/Gemfile.lock#L5-10');
expect(parsed.project_fingerprint).toEqual(sha1(raw.cve));
});
it('generate correct path to file when there is no line', () => {
const parsed = parseSastIssues(sastIssues, [], 'path')[1];
......@@ -93,35 +108,59 @@ describe('security reports utils', () => {
describe('parseDependencyScanningIssues', () => {
it('should parse the received issues', () => {
const parsed = parseDependencyScanningIssues(dependencyScanningIssues, [], 'path')[0];
const parsed = parseDependencyScanningIssues(dependencyScanningIssuesOld, [], 'path')[0];
expect(parsed.title).toEqual(dependencyScanningIssues[0].message);
expect(parsed.path).toEqual(dependencyScanningIssues[0].file);
expect(parsed.location.start_line).toEqual(sastIssues[0].location.start_line);
expect(parsed.title).toEqual(dependencyScanningIssuesOld[0].message);
expect(parsed.path).toEqual(dependencyScanningIssuesOld[0].file);
expect(parsed.location.start_line).toEqual(parseInt(dependencyScanningIssuesOld[0].line, 10));
expect(parsed.location.end_line).toBeUndefined();
expect(parsed.urlPath).toEqual('path/Gemfile.lock#L5');
expect(parsed.project_fingerprint).toEqual(sha1(dependencyScanningIssues[0].cve));
expect(parsed.project_fingerprint).toEqual(sha1(dependencyScanningIssuesOld[0].cve));
});
it('should parse the received issues with new JSON format', () => {
const raw = dependencyScanningIssues[0];
const parsed = parseDependencyScanningIssues(dependencyScanningIssues, [], 'path')[0];
expect(parsed.title).toEqual(raw.message);
expect(parsed.path).toEqual(raw.location.file);
expect(parsed.location.start_line).toBeUndefined();
expect(parsed.location.end_line).toBeUndefined();
expect(parsed.urlPath).toEqual(`path/${raw.location.file}`);
expect(parsed.project_fingerprint).toEqual(sha1(raw.cve));
});
it('should parse the received issues with new JSON format (2.0)', () => {
const raw = dependencyScanningIssues[0];
const parsed = parseDependencyScanningIssues(dependencyScanningIssuesMajor2, [], 'path')[0];
expect(parsed.title).toEqual(raw.message);
expect(parsed.path).toEqual(raw.location.file);
expect(parsed.location.start_line).toBeUndefined();
expect(parsed.location.end_line).toBeUndefined();
expect(parsed.urlPath).toEqual(`path/${raw.location.file}`);
expect(parsed.project_fingerprint).toEqual(sha1(raw.cve));
});
it('generate correct path to file when there is no line', () => {
const parsed = parseDependencyScanningIssues(dependencyScanningIssues, [], 'path')[1];
const parsed = parseDependencyScanningIssues(dependencyScanningIssuesOld, [], 'path')[1];
expect(parsed.urlPath).toEqual('path/Gemfile.lock');
});
it('uses message to generate sha1 when cve is undefined', () => {
const issuesWithoutCve = dependencyScanningIssues.map(issue => ({
const issuesWithoutCve = dependencyScanningIssuesOld.map(issue => ({
...issue,
cve: undefined,
}));
const parsed = parseDependencyScanningIssues(issuesWithoutCve, [], 'path')[0];
expect(parsed.project_fingerprint).toEqual(sha1(dependencyScanningIssues[0].message));
expect(parsed.project_fingerprint).toEqual(sha1(dependencyScanningIssuesOld[0].message));
});
it('includes vulnerability feedbacks', () => {
const parsed = parseDependencyScanningIssues(
dependencyScanningIssues,
dependencyScanningIssuesOld,
dependencyScanningFeedbacks,
'path',
)[0];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment