Commit 05ef0599 authored by Robert Speicher's avatar Robert Speicher

Merge branch '5043_enrich_sast_reports' into 'master'

Enrich Security Reports with more data.

See merge request gitlab-org/gitlab-ee!5878
parents dd78ad02 27a821ba
<script>
/**
* Renders DAST body text
* [priority]: [name]
* [severity] ([confidence]): [name]
*/
import ModalOpenName from './modal_open_name.vue';
......@@ -27,7 +27,7 @@ export default {
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text">
<template v-if="issue.priority">{{ issue.priority }}:</template>
{{ issue.severity }} ({{ issue.confidence }}):
<modal-open-name
:issue="issue"
......
......@@ -35,9 +35,20 @@ export default {
this.dismissIssue();
}
},
isLastValue(index, values) {
return index < values.length - 1;
},
hasValue(field) {
return field.value && field.value.length > 0;
},
hasInstances(field, key) {
return key === 'instances' && field.value && field.value.length > 0;
return key === 'instances' && this.hasValue(field);
},
hasIdentifiers(field, key) {
return key === 'identifiers' && this.hasValue(field);
},
hasLinks(field, key) {
return key === 'links' && this.hasValue(field);
},
},
};
......@@ -51,7 +62,7 @@ export default {
<slot>
<div
v-for="(field, key, index) in modal.data"
v-if="field.value || hasInstances(field, key)"
v-if="field.value"
class="row prepend-top-10 append-bottom-10"
:key="index"
>
......@@ -99,6 +110,42 @@ export default {
</li>
</ul>
</div>
<template v-else-if="hasIdentifiers(field, key)">
<span
v-for="(identifier, i) in field.value"
:key="i"
>
<a
:class="`js-link-${key}`"
v-if="identifier.url"
target="_blank"
:href="identifier.url"
rel="noopener noreferrer"
>
{{ identifier.name }}
</a>
<span v-else>
{{ identifier.name }}
</span>
<span v-if="isLastValue(i, field.value)">,&nbsp;</span>
</span>
</template>
<template v-else-if="hasLinks(field, key)">
<span
v-for="(link, i) in field.value"
:key="i"
>
<a
:class="`js-link-${key}`"
target="_blank"
:href="link.url"
rel="noopener noreferrer"
>
{{ link.value || link.url }}
</a>
<span v-if="isLastValue(i, field.value)">,&nbsp;</span>
</span>
</template>
<template v-else>
<a
:class="`js-link-${key}`"
......
......@@ -22,6 +22,6 @@ export default {
@click="handleIssueClick()"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
>
{{ issue.name }}
{{ issue.title }}
</button>
</template>
......@@ -23,7 +23,7 @@ export default {
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text">
<template v-if="issue.priority">{{ issue.priority }}:</template>
<template v-if="issue.severity">{{ issue.severity }}:</template>
<modal-open-name :issue="issue" />
</div>
......
<script>
/**
* Renders SAST body text
* [priority]: [name] in [link] : [line]
* [severity] ([confidence]): [name] in [link] : [line]
*/
import ReportLink from './report_link.vue';
import ModalOpenName from './modal_open_name.vue';
......@@ -25,7 +25,10 @@ export default {
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text">
<template v-if="issue.priority">{{ issue.priority }}:</template>
<template v-if="issue.severity && issue.confidence">
{{ issue.severity }} ({{ issue.confidence }}):
</template>
<template v-else-if="issue.priority">{{ issue.priority }}:</template>
<modal-open-name :issue="issue" />
</div>
......
......@@ -248,28 +248,30 @@ export default {
},
[types.SET_ISSUE_MODAL_DATA](state, issue) {
state.modal.title = issue.name;
state.modal.title = issue.title;
state.modal.data.description.value = issue.description;
state.modal.data.file.value = issue.file;
state.modal.data.file.value = issue.location && issue.location.file;
state.modal.data.file.url = issue.urlPath;
state.modal.data.className.value = issue.location && issue.location.class;
state.modal.data.methodName.value = issue.location && issue.location.method;
state.modal.data.namespace.value = issue.namespace;
if (issue.identifiers && issue.identifiers.length > 0) {
state.modal.data.identifiers.value = issue.identifiers;
} else {
// Force a null value for identifiers to avoid showing an empty array
state.modal.data.identifiers.value = null;
}
state.modal.data.severity.value = issue.severity;
state.modal.data.confidence.value = issue.confidence;
state.modal.data.solution.value = issue.solution;
state.modal.data.confidenceLevel.value = issue.confidence;
state.modal.data.source.value = issue.source;
state.modal.data.instances.value = issue.instances;
state.modal.vulnerability = issue;
// Link to CVE-ID for Container Scanning
if (issue.nameLink) {
state.modal.data.identifier.value = issue.name;
state.modal.data.identifier.isLink = true;
state.modal.data.identifier.url = issue.nameLink;
if (issue.links && issue.links.length > 0) {
state.modal.data.links.value = issue.links;
} else {
state.modal.data.identifier.value = issue.identifier;
state.modal.data.identifier.isLink = false;
state.modal.data.identifier.url = null;
// Force a null value for links to avoid showing an empty array
state.modal.data.links.value = null;
}
state.modal.data.instances.value = issue.instances;
state.modal.vulnerability = issue;
// clear previous state
state.modal.error = null;
......
......@@ -77,21 +77,30 @@ export default () => ({
text: s__('ciReport|Description'),
isLink: false,
},
identifiers: {
value: [],
text: s__('ciReport|Identifiers'),
isLink: false,
},
file: {
value: null,
url: null,
text: s__('ciReport|File'),
isLink: true,
},
namespace: {
className: {
value: null,
text: s__('ciReport|Namespace'),
text: s__('ciReport|Class'),
isLink: false,
},
identifier: {
methodName: {
value: null,
url: null,
text: s__('ciReport|Identifier'),
text: s__('ciReport|Method'),
isLink: false,
},
namespace: {
value: null,
text: s__('ciReport|Namespace'),
isLink: false,
},
severity: {
......@@ -99,20 +108,20 @@ export default () => ({
text: s__('ciReport|Severity'),
isLink: false,
},
solution: {
confidence: {
value: null,
text: s__('ciReport|Solution'),
text: s__('ciReport|Confidence'),
isLink: false,
},
confidenceLevel: {
solution: {
value: null,
text: s__('ciReport|Confidence Level'),
text: s__('ciReport|Solution'),
isLink: false,
},
source: {
value: null,
text: s__('ciReport|Source'),
isLink: true,
links: {
value: [],
text: s__('ciReport|Links'),
isLink: false,
},
instances: {
value: [],
......
import sha1 from 'sha1';
import _ from 'underscore';
import { stripHtml } from '~/lib/utils/text_utility';
import { n__, s__, sprintf } from '~/locale';
......@@ -12,25 +13,25 @@ export const findIssueIndex = (issues, issue) =>
/**
* Returns given vulnerability enriched with the corresponding
* feedbacks (`dismissal` or `issue` type)
* feedback (`dismissal` or `issue` type)
* @param {Object} vulnerability
* @param {Array} feedbacks
* @param {Array} feedback
*/
function enrichVulnerabilityWithfeedbacks(vulnerability, feedbacks = []) {
return feedbacks.filter(
feedback => feedback.project_fingerprint === vulnerability.project_fingerprint,
).reduce((vuln, feedback) => {
if (feedback.feedback_type === 'dismissal') {
function enrichVulnerabilityWithfeedback(vulnerability, feedback = []) {
return feedback.filter(
fb => fb.project_fingerprint === vulnerability.project_fingerprint,
).reduce((vuln, fb) => {
if (fb.feedback_type === 'dismissal') {
return {
...vuln,
isDismissed: true,
dismissalFeedback: feedback,
dismissalFeedback: fb,
};
} else if (feedback.feedback_type === 'issue') {
} else if (fb.feedback_type === 'issue') {
return {
...vuln,
hasIssue: true,
issueFeedback: feedback,
issueFeedback: fb,
};
}
return vuln;
......@@ -38,107 +39,192 @@ function enrichVulnerabilityWithfeedbacks(vulnerability, feedbacks = []) {
}
/**
* Maps SAST issues:
* { tool: String, message: String, url: String , cve: String ,
* file: String , solution: String, priority: String }
* to contain:
* { name: String, path: String, line: String, urlPath: String, priority: String }
* Generates url to repository file and highlight section between start and end lines.
*
* @param {Object} location
* @param {String} pathPrefix
* @returns {String}
*/
function fileUrl(location, pathPrefix) {
let lineSuffix = '';
if (!_.isEmpty(location.start_line)) {
lineSuffix += `#L${location.start_line}`;
if (!_.isEmpty(location.end_line)) {
lineSuffix += `-${location.end_line}`;
}
}
return `${pathPrefix}/${location.file}${lineSuffix}`;
}
/**
* Parses issues with deprecated JSON format and adapts it to the new one.
*
* @param {Object} issue
* @returns {Object}
*/
function adaptDeprecatedFormat(issue) {
// Skip issue with new format (old format does not have a location property)
if (issue.location) {
return issue;
}
const adapted = {
...issue,
};
// Add the new links property
const links = [];
if (!_.isEmpty(adapted.url)) {
links.push({ url: adapted.url });
}
Object.assign(adapted, {
// Add the new location property
location: {
file: adapted.file,
start_line: adapted.line,
},
links,
});
return adapted;
}
/**
* Parses SAST results into a common format to allow to use the same Vue component.
*
* @param {Array} issues
* @param {Array} feedback
* @param {String} path
* @returns {Array}
*/
export const parseSastIssues = (issues = [], feedbacks = [], path = '') =>
export const parseSastIssues = (issues = [], feedback = [], path = '') =>
issues.map(issue => {
const parsed = {
...issue,
...adaptDeprecatedFormat(issue),
category: 'sast',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(issue.cve),
name: issue.message,
path: issue.file,
urlPath: issue.line ? `${path}/${issue.file}#L${issue.line}` : `${path}/${issue.file}`,
title: issue.message,
};
return {
...parsed,
...enrichVulnerabilityWithfeedbacks(parsed, feedbacks),
path: parsed.location.file,
urlPath: fileUrl(parsed.location, path),
...enrichVulnerabilityWithfeedback(parsed, feedback),
};
});
/**
* Maps Dependency scanning issues:
* { tool: String, message: String, url: String , cve: String ,
* file: String , solution: String, priority: String }
* to contain:
* { name: String, path: String, line: String, urlPath: String, priority: String }
* Parses Dependency Scanning results into a common format to allow to use the same Vue component.
*
* @param {Array} issues
* @param {Array} feedback
* @param {String} path
* @returns {Array}
*/
export const parseDependencyScanningIssues = (issues = [], feedbacks = [], path = '') =>
export const parseDependencyScanningIssues = (issues = [], feedback = [], path = '') =>
issues.map(issue => {
const parsed = {
...issue,
...adaptDeprecatedFormat(issue),
category: 'dependency_scanning',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(issue.cve || issue.message),
name: issue.message,
path: issue.file,
urlPath: issue.line ? `${path}/${issue.file}#L${issue.line}` : `${path}/${issue.file}`,
title: issue.message,
};
return {
...parsed,
...enrichVulnerabilityWithfeedbacks(parsed, feedbacks),
path: parsed.location.file,
urlPath: fileUrl(parsed.location, path),
...enrichVulnerabilityWithfeedback(parsed, feedback),
};
});
/**
* Parses Sast Container results into a common format to allow to use the same Vue component
* And adds an external link
* Parses Container Scanning results into a common format to allow to use the same Vue component.
* Container Scanning report is currently the straigh output from the underlying tool
* (clair scanner) hence the formatting happenning here.
*
* @param {Array} data
* @param {Array} issues
* @param {Array} feedback
* @param {String} path
* @returns {Array}
*/
export const parseSastContainer = (issues = [], feedbacks = []) =>
export const parseSastContainer = (issues = [], feedback = []) =>
issues.map(issue => {
const parsed = {
...issue,
category: 'container_scanning',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(`${issue.namespace}:${issue.vulnerability}:${issue.featurename}:${issue.featureversion}`),
name: issue.vulnerability,
priority: issue.severity,
title: issue.vulnerability,
description: !_.isEmpty(issue.description) ? issue.description :
sprintf(s__('ciReport|%{namespace} is affected by %{vulnerability}.'), {
namespace: issue.namespace,
vulnerability: issue.vulnerability,
}),
path: issue.namespace,
// external link to provide better description
nameLink: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${issue.vulnerability}`,
identifiers: [{
type: 'CVE',
name: issue.vulnerability,
value: issue.vulnerability,
url: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${issue.vulnerability}`,
}],
};
// Generate solution
if (!_.isEmpty(issue.fixedby) &&
!_.isEmpty(issue.featurename) &&
!_.isEmpty(issue.featureversion)
) {
Object.assign(parsed, {
solution: sprintf(s__('ciReport|Upgrade %{name} from %{version} to %{fixed}.'), {
name: issue.featurename,
version: issue.featureversion,
fixed: issue.fixedby,
}),
});
}
return {
...parsed,
...enrichVulnerabilityWithfeedbacks(parsed, feedbacks),
...enrichVulnerabilityWithfeedback(parsed, feedback),
};
});
export const parseDastIssues = (issues = [], feedbacks = []) =>
/**
* Parses DAST into a common format to allow to use the same Vue component.
* DAST report is currently the straigh output from the underlying tool (ZAProxy)
* hence the formatting happenning here.
*
* @param {Array} issues
* @param {Array} feedback
* @returns {Array}
*/
export const parseDastIssues = (issues = [], feedback = []) =>
issues.map(issue => {
const parsed = {
...issue,
category: 'dast',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(issue.pluginid),
parsedDescription: stripHtml(issue.desc, ' '),
priority: issue.riskdesc,
solution: stripHtml(issue.solution, ' '),
title: issue.name,
description: stripHtml(issue.desc, ' '),
solution: stripHtml(issue.solution, ' '),
};
if (issue.cweid && issue.cweid !== '') {
if (!_.isEmpty(issue.cweid)) {
Object.assign(parsed, {
identifier: `CWE-${issue.cweid}`,
identifiers: [{
type: 'CWE',
name: `CWE-${issue.cweid}`,
value: issue.cweid,
url: `https://cwe.mitre.org/data/definitions/${issue.cweid}.html`,
}],
});
}
if (issue.riskdesc && issue.riskdesc !== '') {
// Split 'severity (confidence)'
// Split riskdesc into severity and confidence.
// Riskdesc format is: "severity (confidence)"
const [, severity, confidence] = issue.riskdesc.match(/(.*) \((.*)\)/);
Object.assign(parsed, {
severity,
......@@ -148,7 +234,7 @@ export const parseDastIssues = (issues = [], feedbacks = []) =>
return {
...parsed,
...enrichVulnerabilityWithfeedbacks(parsed, feedbacks),
...enrichVulnerabilityWithfeedback(parsed, feedback),
};
});
......
......@@ -109,9 +109,22 @@ class Projects::VulnerabilityFeedbackController < Projects::ApplicationControlle
method
uri
],
location: %i[
file
start_line
end_line
class
method
],
identifiers: %i[
type
name
value
url
],
links: %i[
name
url
]
]
end
......
......@@ -2,14 +2,14 @@ module Issues
class CreateFromVulnerabilityDataService < ::BaseService
def execute
vulnerability = case @params[:category]
when 'sast', 'dependency_scanning'
when 'sast', 'dependency_scanning', 'dast'
Gitlab::Vulnerabilities::StandardVulnerability.new(params)
when 'container_scanning'
Gitlab::Vulnerabilities::ContainerScanningVulnerability.new(params)
when 'dast'
Gitlab::Vulnerabilities::DastVulnerability.new(params)
end
return error('Invalid vulnerability category') unless vulnerability
issue_params = {
title: "Investigate vulnerability: #{vulnerability.title}",
description: render_description(vulnerability)
......
......@@ -19,11 +19,22 @@
### Identifiers:
<% vulnerability.identifiers.each do |identifier| %>
<% if identifier[:link].present? %>
* [<%= identifier[:value] %>](<%= identifier[:link] %>)
<% if identifier[:url].present? %>
* [<%= identifier[:name] %>](<%= identifier[:url] %>)
<% else %>
* <%= identifier[:value] %>
* <%= identifier[:name] %>
<% end %>
<% end %>
<% end %>
<% if vulnerability.links.present? %>
### Links:
<% vulnerability.links.each do |link| %>
<% if link[:name].present? %>
* [<%= link[:name] %>](<%= link[:url] %>)
<% else %>
* <%= link[:url] %>
<% end %>
<% end %>
<% end %>
---
title: Enrich Security Reports with more data
merge_request: 5878
author:
type: changed
......@@ -15,23 +15,12 @@ module Gitlab
confidence
solution
identifiers
links
].each do |method_name|
define_method(method_name) do
raise NotImplementedError
end
end
protected
# cve_id must be 'CVE-YYYY-XXXX' (prefix + year + digits)
def cve_link(cve_id)
"https://cve.mitre.org/cgi-bin/cvename.cgi?name=#{cve_id}"
end
# cve_id must be a number only (no 'CWE-' prefix)
def cwe_link(cwe_id)
"https://cwe.mitre.org/data/definitions/#{cwe_id}.html"
end
end
end
end
......@@ -2,24 +2,24 @@ module Gitlab
module Vulnerabilities
class ContainerScanningVulnerability < BaseVulnerability
def title
"#{@data[:name]} in #{@data[:namespace]}"
"#{@data[:vulnerability]} in #{@data[:namespace]}"
end
# Passthrough properties
%i[
confidence
severity
identifiers
links
].each do |method_name|
define_method(method_name) do
@data[method_name]
end
end
def confidence
end
def description
@data[:description].presence ||
"**#{@data[:namespace]}** is affected by #{@data[:name]}"
"**#{@data[:namespace]}** is affected by #{@data[:vulnerability]}"
end
def solution
......@@ -30,13 +30,6 @@ module Gitlab
"Upgrade **#{@data[:featurename]}** from `#{@data[:featureversion]}` to `#{@data[:fixedby]}`"
end
end
def identifiers
[{
value: @data[:name],
link: cve_link(@data[:name])
}]
end
end
end
end
module Gitlab
module Vulnerabilities
class DastVulnerability < BaseVulnerability
def title
@data[:name]
end
# Passthrough properties
%i[
severity
confidence
solution
].each do |method_name|
define_method(method_name) do
@data[method_name]
end
end
def description
@data[:desc]
end
def identifiers
ids = []
if @data[:cweid].present?
ids << {
value: "CWE-#{@data[:cweid]}",
link: cwe_link(@data[:cweid])
}
end
if @data[:wascid].present?
ids << {
value: "WASC-#{@data[:wascid]}"
}
end
ids
end
end
end
end
module Gitlab
module Vulnerabilities
class StandardVulnerability < BaseVulnerability
def title
@data[:name]
end
# Passthrough properties
%i[
title
severity
confidence
solution
identifiers
links
].each do |method_name|
define_method(method_name) do
@data[method_name]
......@@ -17,30 +16,7 @@ module Gitlab
end
def description
@data[:description].presence || @data[:name]
end
def identifiers
return [] unless @data[:identifiers].present?
ids = []
@data[:identifiers].each do |identifier|
# Only show known identifiers
case identifier[:name]
when 'CVE'
ids << {
value: identifier[:value],
link: cve_link(identifier[:value])
}
when 'CWE'
ids << {
value: "CWE-#{identifier[:value]}",
link: cwe_link(identifier[:value])
}
end
end
ids
@data[:description].presence || @data[:title]
end
end
end
......
......@@ -86,18 +86,37 @@ describe Projects::VulnerabilityFeedbackController do
feedback_type: 'dismissal', pipeline_id: pipeline.id, category: 'sast',
project_fingerprint: '418291a26024a1445b23fe64de9380cdcdfd1fa8',
vulnerability_data: {
priority: 'Low', line: '41',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
category: 'sast',
severity: 'Low',
confidence: 'Medium',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator',
title: 'Predictable pseudorandom number generator',
description: 'Description of Predictable pseudorandom number generator',
tool: 'find_sec_bugs'
tool: 'find_sec_bugs',
location: {
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
start_line: '41'
},
identifiers: [{
type: 'CVE',
name: 'CVE-2018-1234',
value: 'CVE-2018-1234',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1234'
}],
links: [{
name: 'Awesome-security blog post',
url: 'https;//example.com/blog-post'
}]
}
}
end
context 'with valid params' do
it 'returns the created list' do
it 'returns the created feedback' do
allow(VulnerabilityFeedbackModule::CreateService)
.to receive(:new).with(project, user, create_params)
.and_call_original
create_feedback user: user, project: project, params: create_params
expect(response).to match_response_schema('vulnerability_feedback', dir: 'ee')
......@@ -142,7 +161,7 @@ describe Projects::VulnerabilityFeedbackController do
def create_feedback(user:, project:, params:)
sign_in(user)
post :create, namespace_id: project.namespace.to_param, project_id: project, vulnerability_feedback: params
post :create, namespace_id: project.namespace.to_param, project_id: project, vulnerability_feedback: params, format: :json
end
end
......
......@@ -29,19 +29,34 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
let(:params) do
{
category: 'sast',
priority: 'Low', line: '41',
severity: 'Low', confidence: 'High',
solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator',
title: 'Predictable pseudorandom number generator',
description: 'Description of Predictable pseudorandom number generator',
tool: 'find_sec_bugs',
identifiers: [
{ name: 'CVE', value: 'CVE-2017-15650' },
{ name: 'CWE', value: '16' },
{ name: 'GAS_RULE_ID', value: 'G105' }
]
identifiers: [{
type: 'CVE',
name: 'CVE-2017-15650',
value: 'CVE-2017-15650',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650'
}, {
type: 'CWE',
name: 'CWE-16',
value: '16',
url: 'https://cwe.mitre.org/data/definitions/16.html'
}, {
type: 'GAS_RULE_ID',
name: 'GAS Rule ID G105',
value: 'G105'
}],
links: [{
name: 'Awesome-security blog post',
url: 'https;//example.com/blog-post'
}, {
url: 'https://example.com/another-link'
}]
}
end
......@@ -63,6 +78,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
* [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650)
* [CWE-16](https://cwe.mitre.org/data/definitions/16.html)
* GAS Rule ID G105
### Links:
* [Awesome-security blog post](https;//example.com/blog-post)
* https://example.com/another-link
DESC
end
......@@ -73,16 +94,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
let(:params) do
{
category: 'sast',
priority: 'Low', line: '41',
severity: 'Low', confidence: 'High',
solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator',
tool: 'find_sec_bugs',
identifiers: [
{ name: 'CVE', value: 'CVE-2017-15650' }
]
title: 'Predictable pseudorandom number generator',
tool: 'find_sec_bugs'
}
end
let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' }
......@@ -98,10 +115,6 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
### Solution:
Please do something!
### Identifiers:
* [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650)
DESC
end
......@@ -114,19 +127,34 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
let(:params) do
{
category: 'dependency_scanning',
priority: 'Low', line: '41',
severity: 'Low', confidence: 'High',
solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator',
title: 'Predictable pseudorandom number generator',
description: 'Description of Predictable pseudorandom number generator',
tool: 'find_sec_bugs',
identifiers: [
{ name: 'CVE', value: 'CVE-2017-15650' },
{ name: 'CWE', value: '16' },
{ name: 'GAS_RULE_ID', value: 'G105' }
]
identifiers: [{
type: 'CVE',
name: 'CVE-2017-15650',
value: 'CVE-2017-15650',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650'
}, {
type: 'CWE',
name: 'CWE-16',
value: '16',
url: 'https://cwe.mitre.org/data/definitions/16.html'
}, {
type: 'GAS_RULE_ID',
name: 'GAS Rule ID G105',
value: 'G105'
}],
links: [{
name: 'Awesome-security blog post',
url: 'https;//example.com/blog-post'
}, {
url: 'https://example.com/another-link'
}]
}
end
let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' }
......@@ -147,6 +175,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
* [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650)
* [CWE-16](https://cwe.mitre.org/data/definitions/16.html)
* GAS Rule ID G105
### Links:
* [Awesome-security blog post](https;//example.com/blog-post)
* https://example.com/another-link
DESC
end
......@@ -162,11 +196,8 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator',
tool: 'find_sec_bugs',
identifiers: [
{ name: 'CVE', value: 'CVE-2017-15650' }
]
title: 'Predictable pseudorandom number generator',
tool: 'find_sec_bugs'
}
end
let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' }
......@@ -182,10 +213,6 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
### Solution:
Please do something!
### Identifiers:
* [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650)
DESC
end
......@@ -204,10 +231,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
featurename: 'musl',
featureversion: '1.1.14-r15',
fixedby: '1.1.14-r16',
name: 'CVE-2017-15650',
title: 'CVE-2017-15650',
vulnerability: 'CVE-2017-15650',
description: 'This is a description for CVE-2017-15650.',
tool: 'find_sec_bugs'
tool: 'find_sec_bugs',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-15650',
value: 'CVE-2017-15650',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650'
}]
}
end
let(:expected_title) { 'Investigate vulnerability: CVE-2017-15650 in alpine:v3.4' }
......@@ -241,10 +274,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
featurename: 'musl',
featureversion: '1.1.14-r15',
fixedby: '1.1.14-r16',
name: 'CVE-2017-15650',
title: 'CVE-2017-15650',
vulnerability: 'CVE-2017-15650',
description: '',
tool: 'find_sec_bugs'
tool: 'find_sec_bugs',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-15650',
value: 'CVE-2017-15650',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650'
}]
}
end
let(:expected_title) { 'Investigate vulnerability: CVE-2017-15650 in alpine:v3.4' }
......@@ -276,11 +315,22 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
category: 'dast',
priority: 'Low',
severity: 'Low',
name: 'X-Content-Type-Options Header Missing',
desc: 'The Anti-MIME-Sniffing header X-Content-Type-Options was not set to nosniff.',
title: 'X-Content-Type-Options Header Missing',
desc: '<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to nosniff.</p>',
description: 'The Anti-MIME-Sniffing header X-Content-Type-Options was not set to nosniff.',
cweid: '123',
wascid: '456',
solution: 'Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to nosniff for all web pages.'
solution: 'Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to nosniff for all web pages.',
identifiers: [{
type: 'CWE',
name: 'CWE-123',
value: '123',
url: 'https://cwe.mitre.org/data/definitions/123.html'
}, {
type: 'WASC',
name: 'WASC-456',
value: '456'
}]
}
end
let(:expected_title) { 'Investigate vulnerability: X-Content-Type-Options Header Missing' }
......@@ -306,4 +356,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
it_behaves_like 'a created issue'
end
end
context 'when params are invalid' do
context 'when category is unknown' do
let(:params) { { category: 'foo' } }
let(:result) { described_class.new(project, user, params).execute }
it 'return expected error' do
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('Invalid vulnerability category')
end
end
end
end
......@@ -5922,6 +5922,9 @@ msgstr ""
msgid "ciReport|%{linkStartTag}Learn more about SAST image %{linkEndTag}"
msgstr ""
msgid "ciReport|%{namespace} is affected by %{vulnerability}."
msgstr ""
msgid "ciReport|%{reportName} is loading"
msgstr ""
......@@ -5937,10 +5940,13 @@ msgstr ""
msgid "ciReport|%{type} detected no vulnerabilities"
msgstr ""
msgid "ciReport|Class"
msgstr ""
msgid "ciReport|Code quality"
msgstr ""
msgid "ciReport|Confidence Level"
msgid "ciReport|Confidence"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
......@@ -5991,7 +5997,7 @@ msgstr ""
msgid "ciReport|Fixed:"
msgstr ""
msgid "ciReport|Identifier"
msgid "ciReport|Identifiers"
msgstr ""
msgid "ciReport|Instances"
......@@ -6000,9 +6006,15 @@ msgstr ""
msgid "ciReport|Learn more about whitelisting"
msgstr ""
msgid "ciReport|Links"
msgstr ""
msgid "ciReport|Loading %{reportName} report"
msgstr ""
msgid "ciReport|Method"
msgstr ""
msgid "ciReport|Namespace"
msgstr ""
......@@ -6045,9 +6057,6 @@ msgstr ""
msgid "ciReport|Solution"
msgstr ""
msgid "ciReport|Source"
msgstr ""
msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
msgstr ""
......@@ -6069,6 +6078,9 @@ msgstr ""
msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved."
msgstr ""
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
msgid "ciReport|no vulnerabilities"
msgstr ""
......
......@@ -8,15 +8,13 @@ describe('dast issue body', () => {
const Component = Vue.extend(component);
const dastIssue = {
alert: 'X-Content-Type-Options Header Missing',
confidence: '2',
severity: 'Low',
confidence: 'Medium',
count: '17',
cweid: '16',
desc:
'<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". </p>',
name: 'X-Content-Type-Options Header Missing',
parsedDescription:
' The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". ',
priority: 'Low (Medium)',
title: 'X-Content-Type-Options Header Missing',
reference:
'<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>',
riskcode: '1',
......@@ -27,34 +25,19 @@ describe('dast issue body', () => {
vm.$destroy();
});
describe('with priority', () => {
it('renders priority key', () => {
describe('severity and confidence ', () => {
it('renders severity and confidence', () => {
vm = mountComponent(Component, {
issue: dastIssue,
issueIndex: 1,
modalTargetId: '#modal-mrwidget-issue',
});
expect(vm.$el.textContent.trim()).toContain(dastIssue.priority);
expect(vm.$el.textContent.trim()).toContain(`${dastIssue.severity} (${dastIssue.confidence})`);
});
});
describe('without priority', () => {
it('does not rendere priority key', () => {
const issueCopy = Object.assign({}, dastIssue);
delete issueCopy.priority;
vm = mountComponent(Component, {
issue: issueCopy,
issueIndex: 1,
modalTargetId: '#modal-mrwidget-issue',
});
expect(vm.$el.textContent.trim()).not.toContain(dastIssue.priority);
});
});
describe('issue name', () => {
describe('issue title', () => {
beforeEach(() => {
vm = mountComponent(Component, {
issue: dastIssue,
......@@ -63,8 +46,8 @@ describe('dast issue body', () => {
});
});
it('renders button with issue name', () => {
expect(vm.$el.textContent.trim()).toContain(dastIssue.name);
it('renders button with issue title', () => {
expect(vm.$el.textContent.trim()).toContain(dastIssue.title);
});
});
});
......@@ -27,7 +27,7 @@ describe('Security Reports modal', () => {
cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
isDismissed: true,
......@@ -83,7 +83,7 @@ describe('Security Reports modal', () => {
cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
});
......@@ -112,12 +112,10 @@ describe('Security Reports modal', () => {
describe('with instances', () => {
beforeEach(() => {
store.dispatch('setModalData', {
name: 'Absence of Anti-CSRF Tokens',
title: 'Absence of Anti-CSRF Tokens',
riskcode: '1',
riskdesc: 'Low (Medium)',
priority: 'Low (Medium)',
desc: '<p>No Anti-CSRF tokens were found in a HTML submission form.</p>',
parsedDescription: ' No Anti-CSRF tokens were found in a HTML submission form. ',
pluginid: '123',
instances: [
{
......@@ -159,13 +157,17 @@ describe('Security Reports modal', () => {
store.dispatch('setModalData', {
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
});
vm = mountComponentWithStore(Component, {
......
......@@ -95,7 +95,7 @@ describe('Report issues', () => {
it('should not render location', () => {
vm = mountComponent(ReportIssues, {
issues: [{
name: 'foo',
title: 'foo',
}],
type: 'SAST',
status: 'failed',
......@@ -106,7 +106,7 @@ describe('Report issues', () => {
});
});
describe('for docker issues', () => {
describe('for container scanning issues', () => {
beforeEach(() => {
vm = mountComponent(ReportIssues, {
issues: dockerReportParsed.unapproved,
......@@ -115,16 +115,16 @@ describe('Report issues', () => {
});
});
it('renders priority', () => {
it('renders severity', () => {
expect(
vm.$el.querySelector('.report-block-list li').textContent.trim(),
).toContain(dockerReportParsed.unapproved[0].priority);
).toContain(dockerReportParsed.unapproved[0].severity);
});
it('renders CVE name', () => {
expect(
vm.$el.querySelector('.report-block-list button').textContent.trim(),
).toEqual(dockerReportParsed.unapproved[0].name);
).toEqual(dockerReportParsed.unapproved[0].title);
});
it('renders namespace', () => {
......@@ -148,9 +148,9 @@ describe('Report issues', () => {
});
});
it('renders priority and name', () => {
expect(vm.$el.textContent).toContain(parsedDast[0].name);
expect(vm.$el.textContent).toContain(parsedDast[0].priority);
it('renders severity (confidence) and title', () => {
expect(vm.$el.textContent).toContain(parsedDast[0].title);
expect(vm.$el.textContent).toContain(`${parsedDast[0].severity} (${parsedDast[0].confidence})`);
});
});
});
......@@ -116,7 +116,7 @@ describe('Report section', () => {
cve: 'CVE-2016-9999',
file: 'Gemfile.lock',
message: 'Test Information Leak Vulnerability in Action View',
name: 'Test Information Leak Vulnerability in Action View',
title: 'Test Information Leak Vulnerability in Action View',
path: 'Gemfile.lock',
solution: 'upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1',
tool: 'bundler_audit',
......@@ -127,7 +127,7 @@ describe('Report section', () => {
cve: 'CVE-2014-7829',
file: 'Gemfile.lock',
message: 'Arbitrary file existence disclosure in Action Pack',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
tool: 'bundler_audit',
......@@ -138,7 +138,7 @@ describe('Report section', () => {
cve: 'CVE-2016-0752',
file: 'Gemfile.lock',
message: 'Possible Information Leak Vulnerability in Action View',
name: 'Possible Information Leak Vulnerability in Action View',
title: 'Possible Information Leak Vulnerability in Action View',
path: 'Gemfile.lock',
solution: 'upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1',
tool: 'bundler_audit',
......
......@@ -8,11 +8,9 @@ describe('sast container issue body', () => {
const Component = Vue.extend(component);
const sastContainerIssue = {
name: 'CVE-2017-11671',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11671',
title: 'CVE-2017-11671',
namespace: 'debian:8',
path: 'debian:8',
priority: 'Low',
severity: 'Low',
vulnerability: 'CVE-2017-11671',
};
......@@ -21,26 +19,26 @@ describe('sast container issue body', () => {
vm.$destroy();
});
describe('with priority', () => {
it('renders priority key', () => {
describe('with severity', () => {
it('renders severity key', () => {
vm = mountComponent(Component, {
issue: sastContainerIssue,
});
expect(vm.$el.textContent.trim()).toContain(sastContainerIssue.priority);
expect(vm.$el.textContent.trim()).toContain(sastContainerIssue.severity);
});
});
describe('without priority', () => {
it('does not rendere priority key', () => {
describe('without severity', () => {
it('does not render severity key', () => {
const issueCopy = Object.assign({}, sastContainerIssue);
delete issueCopy.priority;
delete issueCopy.severity;
vm = mountComponent(Component, {
issue: issueCopy,
});
expect(vm.$el.textContent.trim()).not.toContain(sastContainerIssue.priority);
expect(vm.$el.textContent.trim()).not.toContain(sastContainerIssue.severity);
});
});
......@@ -49,7 +47,7 @@ describe('sast container issue body', () => {
issue: sastContainerIssue,
});
expect(vm.$el.querySelector('button').textContent.trim()).toEqual(sastContainerIssue.name);
expect(vm.$el.querySelector('button').textContent.trim()).toEqual(sastContainerIssue.title);
});
describe('path', () => {
......
......@@ -11,7 +11,7 @@ describe('sast issue body', () => {
cve: 'CVE-2016-9999',
file: 'Gemfile.lock',
message: 'Test Information Leak Vulnerability in Action View',
name: 'Test Information Leak Vulnerability in Action View',
title: 'Test Information Leak Vulnerability in Action View',
path: 'Gemfile.lock',
solution:
'upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1',
......@@ -19,27 +19,57 @@ describe('sast issue body', () => {
url:
'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
urlPath: '/Gemfile.lock',
priority: 'Low',
severity: 'Medium',
confidence: 'Low',
};
afterEach(() => {
vm.$destroy();
});
describe('with priority', () => {
it('renders priority key', () => {
describe('with severity and confidence (new json format)', () => {
it('renders severity and confidence', () => {
vm = mountComponent(Component, {
issue: sastIssue,
});
expect(vm.$el.textContent.trim()).toContain(sastIssue.priority);
expect(vm.$el.textContent.trim()).toContain(`${sastIssue.severity} (${sastIssue.confidence})`);
});
});
describe('without severity', () => {
it('does not render severity nor confidence', () => {
const issueCopy = Object.assign({}, sastIssue);
delete issueCopy.severity;
vm = mountComponent(Component, {
issue: issueCopy,
});
expect(vm.$el.textContent.trim()).not.toContain(sastIssue.severity);
expect(vm.$el.textContent.trim()).not.toContain(sastIssue.confidence);
});
});
describe('with priority (old json format)', () => {
it('renders priority key', () => {
const issueCopy = Object.assign({}, sastIssue);
delete issueCopy.severity;
delete issueCopy.confidence;
issueCopy.priority = 'Low';
vm = mountComponent(Component, {
issue: issueCopy,
});
expect(vm.$el.textContent.trim()).toContain(issueCopy.priority);
});
});
describe('without priority', () => {
it('does not rendere priority key', () => {
it('does not render priority key', () => {
const issueCopy = Object.assign({}, sastIssue);
delete issueCopy.priority;
delete issueCopy.severity;
delete issueCopy.confidence;
vm = mountComponent(Component, {
issue: issueCopy,
......@@ -51,20 +81,20 @@ describe('sast issue body', () => {
});
});
describe('name', () => {
it('renders name', () => {
describe('title', () => {
it('renders title', () => {
vm = mountComponent(Component, {
issue: sastIssue,
});
expect(vm.$el.textContent.trim()).toContain(
sastIssue.name,
sastIssue.title,
);
});
});
describe('path', () => {
it('renders name', () => {
it('renders path', () => {
vm = mountComponent(Component, {
issue: sastIssue,
});
......
......@@ -316,21 +316,50 @@ describe('security reports mutations', () => {
const issue = {
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
cve: 'CVE-2014-7829',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
namespace: 'debian:8',
location: {
file: 'Gemfile.lock',
class: 'User',
method: 'do_something',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2014-9999',
value: 'CVE-2014-9999',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-9999',
}],
instances: [{
param: 'X-Content-Type-Options',
method: 'GET',
uri: 'http://example.com/some-path',
}],
isDismissed: true,
};
mutations[types.SET_ISSUE_MODAL_DATA](stateCopy, issue);
expect(stateCopy.modal.title).toEqual(issue.name);
expect(stateCopy.modal.data.file.value).toEqual(issue.file);
expect(stateCopy.modal.title).toEqual(issue.title);
expect(stateCopy.modal.data.description.value).toEqual(issue.description);
expect(stateCopy.modal.data.file.value).toEqual(issue.location.file);
expect(stateCopy.modal.data.file.url).toEqual(issue.urlPath);
expect(stateCopy.modal.data.className.value).toEqual(issue.location.class);
expect(stateCopy.modal.data.methodName.value).toEqual(issue.location.method);
expect(stateCopy.modal.data.namespace.value).toEqual(issue.namespace);
expect(stateCopy.modal.data.identifiers.value).toEqual(issue.identifiers);
expect(stateCopy.modal.data.severity.value).toEqual(issue.severity);
expect(stateCopy.modal.data.confidence.value).toEqual(issue.confidence);
expect(stateCopy.modal.data.solution.value).toEqual(issue.solution);
expect(stateCopy.modal.data.links.value).toEqual(issue.links);
expect(stateCopy.modal.data.instances.value).toEqual(issue.instances);
expect(stateCopy.modal.vulnerability).toEqual(issue);
});
});
......
......@@ -11,6 +11,7 @@ import {
statusIcon,
} from 'ee/vue_shared/security_reports/store/utils';
import {
oldSastIssues,
sastIssues,
sastFeedbacks,
dependencyScanningIssues,
......@@ -52,10 +53,17 @@ describe('security reports utils', () => {
});
describe('parseSastIssues', () => {
it('should parse the received issues', () => {
it('should parse the received issues with old JSON format', () => {
const parsed = parseSastIssues(oldSastIssues, [], 'path')[0];
expect(parsed.title).toEqual(sastIssues[0].message);
expect(parsed.path).toEqual(sastIssues[0].location.file);
expect(parsed.project_fingerprint).toEqual(sha1(sastIssues[0].cve));
});
it('should parse the received issues with new JSON format', () => {
const parsed = parseSastIssues(sastIssues, [], 'path')[0];
expect(parsed.name).toEqual(sastIssues[0].message);
expect(parsed.path).toEqual(sastIssues[0].file);
expect(parsed.title).toEqual(sastIssues[0].message);
expect(parsed.path).toEqual(sastIssues[0].location.file);
expect(parsed.project_fingerprint).toEqual(sha1(sastIssues[0].cve));
});
......@@ -75,7 +83,7 @@ describe('security reports utils', () => {
describe('parseDependencyScanningIssues', () => {
it('should parse the received issues', () => {
const parsed = parseDependencyScanningIssues(dependencyScanningIssues, [], 'path')[0];
expect(parsed.name).toEqual(dependencyScanningIssues[0].message);
expect(parsed.title).toEqual(dependencyScanningIssues[0].message);
expect(parsed.path).toEqual(dependencyScanningIssues[0].file);
expect(parsed.project_fingerprint).toEqual(sha1(dependencyScanningIssues[0].cve));
});
......@@ -107,14 +115,14 @@ describe('security reports utils', () => {
const parsed = parseSastContainer(dockerReport.vulnerabilities)[0];
const issue = dockerReport.vulnerabilities[0];
expect(parsed.name).toEqual(dockerReport.vulnerabilities[0].vulnerability);
expect(parsed.priority).toEqual(dockerReport.vulnerabilities[0].severity);
expect(parsed.path).toEqual(dockerReport.vulnerabilities[0].namespace);
expect(parsed.nameLink).toEqual(
`https://cve.mitre.org/cgi-bin/cvename.cgi?name=${
dockerReport.vulnerabilities[0].vulnerability
}`,
);
expect(parsed.title).toEqual(issue.vulnerability);
expect(parsed.path).toEqual(issue.namespace);
expect(parsed.identifiers).toEqual([{
type: 'CVE',
name: issue.vulnerability,
value: issue.vulnerability,
url: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${issue.vulnerability}`,
}]);
expect(parsed.project_fingerprint).toEqual(
sha1(`${issue.namespace}:${issue.vulnerability}:${issue.featurename}:${issue.featureversion}`));
});
......
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