Commit 086cb496 authored by Mikołaj Wawrzyniak's avatar Mikołaj Wawrzyniak

Merge branch '321918_validate_report_artifacts' into 'master'

Validate security report artifacts

See merge request gitlab-org/gitlab!58667
parents 9eeaf517 4836e251
......@@ -412,6 +412,46 @@ You can do it quickly by following the hyperlink given to run a new pipeline.
![Run a new pipeline](img/outdated_report_pipeline_v12_9.png)
## Security report validation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321918) in GitLab 13.11.
As of GitLab 13.11, we've introduced the **optional** validation of the security report artifacts based on the
[report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/tree/master/dist).
If you enable validation, GitLab validates the report artifacts before ingesting the vulnerabilities.
This prevents ingesting broken vulnerability data into the database.
### Enable security report validation
To enable report artifacts validation, set the `VALIDATE_SCHEMA` environment variable to `"true"` for the jobs in the `.gitlab-ci.yml` file.
For example, the configuration below enables validation for only the `sast` job:
```yaml
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
stages:
- security-scan
dependency_scanning:
stage: security-scan
license_scanning:
stage: security-scan
sast:
stage: security-scan
variables:
VALIDATE_SCHEMA: "true"
.secret-analyzer:
stage: security-scan
```
## Troubleshooting
### Getting error message `sast job: stage parameter should be [some stage name here]`
......
......@@ -25,7 +25,7 @@ module Security
def execute
findings = requested_reports.each_with_object([]) do |(type, report), findings|
raise ParseError, 'JSON parsing failed' if report.error.is_a?(Gitlab::Ci::Parsers::Security::Common::SecurityReportParserError)
raise ParseError, 'JSON parsing failed' if report.errored?
normalized_findings = normalize_report_findings(
report.findings,
......
......@@ -10,6 +10,7 @@ module EE
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
VALIDATE_SCHEMA_VARIABLE_NAME = 'VALIDATE_SCHEMA'
LICENSED_PARSER_FEATURES = {
sast: :sast,
secret_detection: :secret_detection,
......@@ -82,8 +83,8 @@ module EE
next unless project.feature_available?(LICENSED_PARSER_FEATURES.fetch(file_type))
parse_security_artifact_blob(security_report, blob)
rescue => e
security_report.error = e
rescue
security_report.add_error('ParsingError')
end
end
end
......@@ -161,6 +162,10 @@ module EE
variables_hash.fetch(key, default)
end
def validate_schema?
variables[VALIDATE_SCHEMA_VARIABLE_NAME]&.value&.casecmp?('true')
end
private
def variables_hash
......
......@@ -56,6 +56,8 @@ module EE
scope :api_fuzzing_reports, -> do
with_file_types(API_FUZZING_REPORT_TYPES)
end
delegate :validate_schema?, to: :job
end
class_methods do
......@@ -78,14 +80,16 @@ module EE
# parsed report regardless of the `file_type` but this will
# require more effort so we can have this security reports
# specific method here for now.
def security_report
def security_report(validate: false)
strong_memoize(:security_report) do
next unless file_type.in?(SECURITY_REPORT_FILE_TYPES)
report = ::Gitlab::Ci::Reports::Security::Report.new(file_type, job.pipeline, nil).tap do |report|
each_blob do |blob|
::Gitlab::Ci::Parsers.fabricate!(file_type, blob, report).parse!
::Gitlab::Ci::Parsers.fabricate!(file_type, blob, report, validate: (validate && validate_schema?)).parse!
end
rescue
report.add_error('ParsingError')
end
# This will remove the duplicated findings within the artifact itself
......
......@@ -40,5 +40,9 @@ module Security
scope :latest_successful_by_build, -> { joins(:build).where(ci_builds: { status: 'success', retried: [nil, false] }) }
delegate :project, :name, to: :build
def has_errors?
info&.fetch('errors', []).present?
end
end
end
......@@ -22,7 +22,7 @@ module Security
@source_reports.first.type,
@source_reports.first.pipeline,
@source_reports.first.created_at
)
).tap { |report| report.errors = source_reports.flat_map(&:errors) }
@findings = []
end
......
......@@ -50,7 +50,7 @@ module Security
# and `INFINITY` for all the other scan types. There is no problem with
# calling this method for all the scan types to get rid of branching.
def scanner_order_for(artifact)
MergeReportsService::ANALYZER_ORDER.fetch(artifact.security_report.primary_scanner&.external_id, Float::INFINITY)
MergeReportsService::ANALYZER_ORDER.fetch(artifact.security_report(validate: true).primary_scanner&.external_id, Float::INFINITY)
end
def store_scan_for(artifact, deduplicate)
......
......@@ -19,6 +19,8 @@ module Security
end
def execute
return deduplicate if security_scan.has_errors?
StoreFindingsMetadataService.execute(security_scan, security_report)
deduplicate_findings? ? update_deduplicated_findings : register_finding_keys
......@@ -31,7 +33,9 @@ module Security
delegate :security_report, :project, to: :artifact, private: true
def security_scan
@security_scan ||= Security::Scan.safe_find_or_create_by!(build: artifact.job, scan_type: artifact.file_type)
@security_scan ||= Security::Scan.safe_find_or_create_by!(build: artifact.job, scan_type: artifact.file_type) do |scan|
scan.info['errors'] = security_report.errors.map(&:stringify_keys) if security_report.errored?
end
end
def deduplicate_findings?
......
---
title: Introduce security report schema validation
merge_request: 58667
author:
type: added
......@@ -7,17 +7,20 @@ module Gitlab
class Common
SecurityReportParserError = Class.new(Gitlab::Ci::Parsers::ParserError)
def self.parse!(json_data, report, vulnerability_finding_signatures_enabled = false)
new(json_data, report, vulnerability_finding_signatures_enabled).parse!
def self.parse!(json_data, report, vulnerability_finding_signatures_enabled = false, validate: false)
new(json_data, report, vulnerability_finding_signatures_enabled, validate: validate).parse!
end
def initialize(json_data, report, vulnerability_finding_signatures_enabled = false)
def initialize(json_data, report, vulnerability_finding_signatures_enabled = false, validate: false)
@json_data = json_data
@report = report
@validate = validate
@vulnerability_finding_signatures_enabled = vulnerability_finding_signatures_enabled
end
def parse!
return report_data unless valid?
raise SecurityReportParserError, "Invalid report format" unless report_data.is_a?(Hash)
create_scanner
......@@ -34,7 +37,19 @@ module Gitlab
private
attr_reader :json_data, :report
attr_reader :json_data, :report, :validate
def valid?
return true if !validate || schema_validator.valid?
schema_validator.errors.each { |error| report.add_error('Schema', error) }
false
end
def schema_validator
@schema_validator ||= ::Gitlab::Ci::Parsers::Security::Validators::SchemaValidator.new(report.type, report_data)
end
def report_data
@report_data ||= Gitlab::Json.parse!(json_data)
......
# frozen_string_literal: true
module Gitlab
module Ci
module Parsers
module Security
module Validators
class SchemaValidator
class Schema
ROOT_PATH = File.join(__dir__, 'schemas')
def initialize(report_type)
@report_type = report_type
end
delegate :validate, to: :schemer
private
attr_reader :report_type
def schemer
JSONSchemer.schema(pathname)
end
def pathname
Pathname.new(schema_path)
end
def schema_path
File.join(ROOT_PATH, file_name)
end
def file_name
"#{report_type}.json"
end
end
def initialize(report_type, report_data)
@report_type = report_type
@report_data = report_data
end
def valid?
errors.empty?
end
def errors
@errors ||= schema.validate(report_data).map { |error| JSONSchemer::Errors.pretty(error) }
end
private
attr_reader :report_type, :report_data
def schema
Schema.new(report_type)
end
end
end
end
end
end
end
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Report format for GitLab Container Scanning",
"description": "This schema provides the the report format for Container Scanning (https://docs.gitlab.com/ee/user/application_security/container_scanning).",
"definitions": {
"detail_type": {
"oneOf": [
{
"$ref": "#/definitions/named_list"
},
{
"$ref": "#/definitions/list"
},
{
"$ref": "#/definitions/table"
},
{
"$ref": "#/definitions/text"
},
{
"$ref": "#/definitions/url"
},
{
"$ref": "#/definitions/code"
},
{
"$ref": "#/definitions/value"
},
{
"$ref": "#/definitions/diff"
},
{
"$ref": "#/definitions/markdown"
},
{
"$ref": "#/definitions/commit"
},
{
"$ref": "#/definitions/file_location"
},
{
"$ref": "#/definitions/module_location"
}
]
},
"text_value": {
"type": "string"
},
"named_field": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"$ref": "#/definitions/text_value",
"minLength": 1
},
"description": {
"$ref": "#/definitions/text_value"
}
}
},
"named_list": {
"type": "object",
"description": "An object with named and typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "named-list"
},
"items": {
"type": "object",
"patternProperties": {
"^.*$": {
"allOf": [
{
"$ref": "#/definitions/named_field"
},
{
"$ref": "#/definitions/detail_type"
}
]
}
}
}
}
},
"list": {
"type": "object",
"description": "A list of typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "list"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
},
"table": {
"type": "object",
"description": "A table of typed fields",
"required": [
"type",
"rows"
],
"properties": {
"type": {
"const": "table"
},
"header": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
},
"rows": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
}
},
"text": {
"type": "object",
"description": "Raw text",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "text"
},
"value": {
"$ref": "#/definitions/text_value"
}
}
},
"url": {
"type": "object",
"description": "A single URL",
"required": [
"type",
"href"
],
"properties": {
"type": {
"const": "url"
},
"text": {
"$ref": "#/definitions/text_value"
},
"href": {
"type": "string",
"minLength": 1,
"examples": [
"http://mysite.com"
]
}
}
},
"code": {
"type": "object",
"description": "A codeblock",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "code"
},
"value": {
"type": "string"
},
"lang": {
"type": "string",
"description": "A programming language"
}
}
},
"value": {
"type": "object",
"description": "A field that can store a range of types of value",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "value"
},
"value": {
"type": [
"number",
"string",
"boolean"
]
}
}
},
"diff": {
"type": "object",
"description": "A diff",
"required": [
"type",
"before",
"after"
],
"properties": {
"type": {
"const": "diff"
},
"before": {
"type": "string"
},
"after": {
"type": "string"
}
}
},
"markdown": {
"type": "object",
"description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "markdown"
},
"value": {
"$ref": "#/definitions/text_value",
"examples": [
"Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)"
]
}
}
},
"commit": {
"type": "object",
"description": "A commit/tag/branch within the GitLab project",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "commit"
},
"value": {
"type": "string",
"description": "The commit SHA",
"minLength": 1
}
}
},
"file_location": {
"type": "object",
"description": "A location within a file in the project",
"required": [
"type",
"file_name",
"line_start"
],
"properties": {
"type": {
"const": "file-location"
},
"file_name": {
"type": "string",
"minLength": 1
},
"line_start": {
"type": "integer"
},
"line_end": {
"type": "integer"
}
}
},
"module_location": {
"type": "object",
"description": "A location within a binary module of the form module+relative_offset",
"required": [
"type",
"module_name",
"offset"
],
"properties": {
"type": {
"const": "module-location"
},
"module_name": {
"type": "string",
"minLength": 1,
"examples": [
"compiled_binary"
]
},
"offset": {
"type": "integer",
"examples": [
100
]
}
}
}
},
"self": {
"version": "14.0.0"
},
"required": [
"version",
"vulnerabilities"
],
"additionalProperties": true,
"properties": {
"scan": {
"type": "object",
"required": [
"end_time",
"scanner",
"start_time",
"status",
"type"
],
"properties": {
"end_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-01-28T03:26:02"
]
},
"messages": {
"type": "array",
"items": {
"type": "object",
"description": "Communication intended for the initiator of a scan.",
"required": [
"level",
"value"
],
"properties": {
"level": {
"type": "string",
"description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.",
"enum": [
"info",
"warn",
"fatal"
],
"examples": [
"info"
]
},
"value": {
"type": "string",
"description": "The message to communicate.",
"minLength": 1,
"examples": [
"Permission denied, scanning aborted"
]
}
}
}
},
"scanner": {
"type": "object",
"description": "Object defining the scanner used to perform the scan.",
"required": [
"id",
"name",
"version",
"vendor"
],
"properties": {
"id": {
"type": "string",
"description": "Unique id that identifies the scanner.",
"minLength": 1,
"examples": [
"my-sast-scanner"
]
},
"name": {
"type": "string",
"description": "A human readable value that identifies the scanner, not required to be unique.",
"minLength": 1,
"examples": [
"My SAST Scanner"
]
},
"url": {
"type": "string",
"description": "A link to more information about the scanner.",
"examples": [
"https://scanner.url"
]
},
"version": {
"type": "string",
"description": "The version of the scanner.",
"minLength": 1,
"examples": [
"1.0.2"
]
},
"vendor": {
"type": "object",
"description": "The vendor/maintainer of the scanner.",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the vendor.",
"minLength": 1,
"examples": [
"GitLab"
]
}
}
}
}
},
"start_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-02-14T16:01:59"
]
},
"status": {
"type": "string",
"description": "Result of the scan.",
"enum": [
"success",
"failure"
]
},
"type": {
"type": "string",
"description": "Type of the scan.",
"enum": [
"container_scanning"
]
}
}
},
"schema": {
"type": "string",
"description": "URI pointing to the validating security report schema.",
"format": "uri"
},
"version": {
"type": "string",
"description": "The version of the schema to which the JSON report conforms.",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
},
"vulnerabilities": {
"type": "array",
"description": "Array of vulnerability objects.",
"items": {
"type": "object",
"description": "Describes the vulnerability.",
"required": [
"category",
"cve",
"identifiers",
"location",
"scanner"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier of the vulnerability. This is recommended to be a UUID.",
"examples": [
"642735a5-1425-428d-8d4e-3c854885a3c9"
]
},
"category": {
"type": "string",
"minLength": 1,
"description": "Describes where this vulnerability belongs (for example, SAST, Dependency Scanning, and so on)."
},
"name": {
"type": "string",
"description": "The name of the vulnerability. This must not include the finding's specific information."
},
"message": {
"type": "string",
"description": "A short text section that describes the vulnerability. This may include the finding's specific information."
},
"description": {
"type": "string",
"description": "A long text section describing the vulnerability more fully."
},
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
},
"severity": {
"type": "string",
"description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.",
"enum": [
"Info",
"Unknown",
"Low",
"Medium",
"High",
"Critical"
]
},
"confidence": {
"type": "string",
"description": "How reliable the vulnerability's assessment is. Possible values are Ignore, Unknown, Experimental, Low, Medium, High, and Confirmed. Note that some analyzers may not report all these possible values.",
"enum": [
"Ignore",
"Unknown",
"Experimental",
"Low",
"Medium",
"High",
"Confirmed"
]
},
"solution": {
"type": "string",
"description": "Explanation of how to fix the vulnerability."
},
"scanner": {
"description": "Describes the scanner used to find this vulnerability.",
"type": "object",
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "The scanner's ID, as a snake_case string."
},
"name": {
"type": "string",
"minLength": 1,
"description": "Human-readable name of the scanner."
}
}
},
"identifiers": {
"type": "array",
"minItems": 1,
"description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.",
"items": {
"type": "object",
"required": [
"type",
"name",
"value"
],
"properties": {
"type": {
"type": "string",
"description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).",
"minLength": 1
},
"name": {
"type": "string",
"description": "Human-readable name of the identifier.",
"minLength": 1
},
"url": {
"type": "string",
"description": "URL of the identifier's documentation.",
"format": "uri"
},
"value": {
"type": "string",
"description": "Value of the identifier, for matching purpose.",
"minLength": 1
}
}
}
},
"links": {
"type": "array",
"description": "An array of references to external documentation or articles that describe the vulnerability.",
"items": {
"type": "object",
"required": [
"url"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the vulnerability details link."
},
"url": {
"type": "string",
"description": "URL of the vulnerability details document.",
"format": "uri"
}
}
}
},
"details": {
"$ref": "#/definitions/named_list/properties/items"
},
"location": {
"type": "object",
"description": "Identifies the vulnerability's location.",
"required": [
"dependency",
"operating_system",
"image"
],
"properties": {
"dependency": {
"type": "object",
"description": "Describes the dependency of a project where the vulnerability is located.",
"properties": {
"package": {
"type": "object",
"description": "Provides information on the package where the vulnerability is located.",
"properties": {
"name": {
"type": "string",
"description": "Name of the package where the vulnerability is located."
}
}
},
"version": {
"type": "string",
"description": "Version of the vulnerable package."
},
"iid": {
"description": "ID that identifies the dependency in the scope of a dependency file.",
"type": "number"
},
"direct": {
"type": "boolean",
"description": "Tells whether this is a direct, top-level dependency of the scanned project."
},
"dependency_path": {
"type": "array",
"description": "Ancestors of the dependency, starting from a direct project dependency, and ending with an immediate parent of the dependency. The dependency itself is excluded from the path. Direct dependencies have no path.",
"items": {
"type": "object",
"required": [
"iid"
],
"properties": {
"iid": {
"type": "number",
"description": "ID that is unique in the scope of a parent object, and specific to the resource type."
}
}
}
}
}
},
"operating_system": {
"type": "string",
"minLength": 1,
"description": "The operating system that contains the vulnerable package."
},
"image": {
"type": "string",
"minLength": 1,
"description": "The analyzed Docker image."
}
}
}
}
}
},
"remediations": {
"type": "array",
"description": "An array of objects containing information on available remediations, along with patch diffs to apply.",
"items": {
"type": "object",
"required": [
"fixes",
"summary",
"diff"
],
"properties": {
"fixes": {
"type": "array",
"description": "An array of strings that represent references to vulnerabilities fixed by this remediation.",
"items": {
"type": "object",
"required": [
"cve"
],
"properties": {
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
}
}
}
},
"summary": {
"type": "string",
"minLength": 1,
"description": "An overview of how the vulnerabilities were fixed."
},
"diff": {
"type": "string",
"minLength": 1,
"description": "A base64-encoded remediation code diff, compatible with git apply."
}
}
}
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Report format for GitLab Fuzz Testing",
"description": "This schema provides the report format for Coverage Guided Fuzz Testing (https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing).",
"definitions": {
"detail_type": {
"oneOf": [
{
"$ref": "#/definitions/named_list"
},
{
"$ref": "#/definitions/list"
},
{
"$ref": "#/definitions/table"
},
{
"$ref": "#/definitions/text"
},
{
"$ref": "#/definitions/url"
},
{
"$ref": "#/definitions/code"
},
{
"$ref": "#/definitions/value"
},
{
"$ref": "#/definitions/diff"
},
{
"$ref": "#/definitions/markdown"
},
{
"$ref": "#/definitions/commit"
},
{
"$ref": "#/definitions/file_location"
},
{
"$ref": "#/definitions/module_location"
}
]
},
"text_value": {
"type": "string"
},
"named_field": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"$ref": "#/definitions/text_value",
"minLength": 1
},
"description": {
"$ref": "#/definitions/text_value"
}
}
},
"named_list": {
"type": "object",
"description": "An object with named and typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "named-list"
},
"items": {
"type": "object",
"patternProperties": {
"^.*$": {
"allOf": [
{
"$ref": "#/definitions/named_field"
},
{
"$ref": "#/definitions/detail_type"
}
]
}
}
}
}
},
"list": {
"type": "object",
"description": "A list of typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "list"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
},
"table": {
"type": "object",
"description": "A table of typed fields",
"required": [
"type",
"rows"
],
"properties": {
"type": {
"const": "table"
},
"header": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
},
"rows": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
}
},
"text": {
"type": "object",
"description": "Raw text",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "text"
},
"value": {
"$ref": "#/definitions/text_value"
}
}
},
"url": {
"type": "object",
"description": "A single URL",
"required": [
"type",
"href"
],
"properties": {
"type": {
"const": "url"
},
"text": {
"$ref": "#/definitions/text_value"
},
"href": {
"type": "string",
"minLength": 1,
"examples": [
"http://mysite.com"
]
}
}
},
"code": {
"type": "object",
"description": "A codeblock",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "code"
},
"value": {
"type": "string"
},
"lang": {
"type": "string",
"description": "A programming language"
}
}
},
"value": {
"type": "object",
"description": "A field that can store a range of types of value",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "value"
},
"value": {
"type": [
"number",
"string",
"boolean"
]
}
}
},
"diff": {
"type": "object",
"description": "A diff",
"required": [
"type",
"before",
"after"
],
"properties": {
"type": {
"const": "diff"
},
"before": {
"type": "string"
},
"after": {
"type": "string"
}
}
},
"markdown": {
"type": "object",
"description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "markdown"
},
"value": {
"$ref": "#/definitions/text_value",
"examples": [
"Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)"
]
}
}
},
"commit": {
"type": "object",
"description": "A commit/tag/branch within the GitLab project",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "commit"
},
"value": {
"type": "string",
"description": "The commit SHA",
"minLength": 1
}
}
},
"file_location": {
"type": "object",
"description": "A location within a file in the project",
"required": [
"type",
"file_name",
"line_start"
],
"properties": {
"type": {
"const": "file-location"
},
"file_name": {
"type": "string",
"minLength": 1
},
"line_start": {
"type": "integer"
},
"line_end": {
"type": "integer"
}
}
},
"module_location": {
"type": "object",
"description": "A location within a binary module of the form module+relative_offset",
"required": [
"type",
"module_name",
"offset"
],
"properties": {
"type": {
"const": "module-location"
},
"module_name": {
"type": "string",
"minLength": 1,
"examples": [
"compiled_binary"
]
},
"offset": {
"type": "integer",
"examples": [
100
]
}
}
}
},
"self": {
"version": "14.0.0"
},
"required": [
"version",
"vulnerabilities"
],
"additionalProperties": true,
"properties": {
"scan": {
"type": "object",
"required": [
"end_time",
"scanner",
"start_time",
"status",
"type"
],
"properties": {
"end_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-01-28T03:26:02"
]
},
"messages": {
"type": "array",
"items": {
"type": "object",
"description": "Communication intended for the initiator of a scan.",
"required": [
"level",
"value"
],
"properties": {
"level": {
"type": "string",
"description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.",
"enum": [
"info",
"warn",
"fatal"
],
"examples": [
"info"
]
},
"value": {
"type": "string",
"description": "The message to communicate.",
"minLength": 1,
"examples": [
"Permission denied, scanning aborted"
]
}
}
}
},
"scanner": {
"type": "object",
"description": "Object defining the scanner used to perform the scan.",
"required": [
"id",
"name",
"version",
"vendor"
],
"properties": {
"id": {
"type": "string",
"description": "Unique id that identifies the scanner.",
"minLength": 1,
"examples": [
"my-sast-scanner"
]
},
"name": {
"type": "string",
"description": "A human readable value that identifies the scanner, not required to be unique.",
"minLength": 1,
"examples": [
"My SAST Scanner"
]
},
"url": {
"type": "string",
"description": "A link to more information about the scanner.",
"examples": [
"https://scanner.url"
]
},
"version": {
"type": "string",
"description": "The version of the scanner.",
"minLength": 1,
"examples": [
"1.0.2"
]
},
"vendor": {
"type": "object",
"description": "The vendor/maintainer of the scanner.",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the vendor.",
"minLength": 1,
"examples": [
"GitLab"
]
}
}
}
}
},
"start_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-02-14T16:01:59"
]
},
"status": {
"type": "string",
"description": "Result of the scan.",
"enum": [
"success",
"failure"
]
},
"type": {
"type": "string",
"description": "Type of the scan.",
"enum": [
"coverage_fuzzing"
]
}
}
},
"schema": {
"type": "string",
"description": "URI pointing to the validating security report schema.",
"format": "uri"
},
"version": {
"type": "string",
"description": "The version of the schema to which the JSON report conforms.",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
},
"vulnerabilities": {
"type": "array",
"description": "Array of vulnerability objects.",
"items": {
"type": "object",
"description": "Describes the vulnerability.",
"required": [
"category",
"cve",
"identifiers",
"location",
"scanner"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier of the vulnerability. This is recommended to be a UUID.",
"examples": [
"642735a5-1425-428d-8d4e-3c854885a3c9"
]
},
"category": {
"type": "string",
"minLength": 1,
"description": "Describes where this vulnerability belongs (for example, SAST, Dependency Scanning, and so on)."
},
"name": {
"type": "string",
"description": "The name of the vulnerability. This must not include the finding's specific information."
},
"message": {
"type": "string",
"description": "A short text section that describes the vulnerability. This may include the finding's specific information."
},
"description": {
"type": "string",
"description": "A long text section describing the vulnerability more fully."
},
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
},
"severity": {
"type": "string",
"description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.",
"enum": [
"Info",
"Unknown",
"Low",
"Medium",
"High",
"Critical"
]
},
"confidence": {
"type": "string",
"description": "How reliable the vulnerability's assessment is. Possible values are Ignore, Unknown, Experimental, Low, Medium, High, and Confirmed. Note that some analyzers may not report all these possible values.",
"enum": [
"Ignore",
"Unknown",
"Experimental",
"Low",
"Medium",
"High",
"Confirmed"
]
},
"solution": {
"type": "string",
"description": "Explanation of how to fix the vulnerability."
},
"scanner": {
"description": "Describes the scanner used to find this vulnerability.",
"type": "object",
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "The scanner's ID, as a snake_case string."
},
"name": {
"type": "string",
"minLength": 1,
"description": "Human-readable name of the scanner."
}
}
},
"identifiers": {
"type": "array",
"minItems": 1,
"description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.",
"items": {
"type": "object",
"required": [
"type",
"name",
"value"
],
"properties": {
"type": {
"type": "string",
"description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).",
"minLength": 1
},
"name": {
"type": "string",
"description": "Human-readable name of the identifier.",
"minLength": 1
},
"url": {
"type": "string",
"description": "URL of the identifier's documentation.",
"format": "uri"
},
"value": {
"type": "string",
"description": "Value of the identifier, for matching purpose.",
"minLength": 1
}
}
}
},
"links": {
"type": "array",
"description": "An array of references to external documentation or articles that describe the vulnerability.",
"items": {
"type": "object",
"required": [
"url"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the vulnerability details link."
},
"url": {
"type": "string",
"description": "URL of the vulnerability details document.",
"format": "uri"
}
}
}
},
"details": {
"$ref": "#/definitions/named_list/properties/items"
},
"location": {
"description": "The location of the error",
"type": "object",
"properties": {
"crash_address": {
"type": "string",
"description": "The relative address in memory were the crash occurred.",
"examples": [
"0xabababab"
]
},
"stacktrace_snippet": {
"type": "string",
"description": "The stack trace recorded during fuzzing resulting the crash.",
"examples": [
"func_a+0xabcd\nfunc_b+0xabcc"
]
},
"crash_state": {
"type": "string",
"description": "Minimised and normalized crash stack-trace (called crash_state).",
"examples": [
"func_a+0xa\nfunc_b+0xb\nfunc_c+0xc"
]
},
"crash_type": {
"type": "string",
"description": "Type of the crash.",
"examples": [
"Heap-Buffer-overflow",
"Division-by-zero"
]
}
}
}
}
}
},
"remediations": {
"type": "array",
"description": "An array of objects containing information on available remediations, along with patch diffs to apply.",
"items": {
"type": "object",
"required": [
"fixes",
"summary",
"diff"
],
"properties": {
"fixes": {
"type": "array",
"description": "An array of strings that represent references to vulnerabilities fixed by this remediation.",
"items": {
"type": "object",
"required": [
"cve"
],
"properties": {
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
}
}
}
},
"summary": {
"type": "string",
"minLength": 1,
"description": "An overview of how the vulnerabilities were fixed."
},
"diff": {
"type": "string",
"minLength": 1,
"description": "A base64-encoded remediation code diff, compatible with git apply."
}
}
}
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Report format for GitLab DAST",
"description": "This schema provides the the report format for Dynamic Application Security Testing (https://docs.gitlab.com/ee/user/application_security/dast).",
"definitions": {
"detail_type": {
"oneOf": [
{
"$ref": "#/definitions/named_list"
},
{
"$ref": "#/definitions/list"
},
{
"$ref": "#/definitions/table"
},
{
"$ref": "#/definitions/text"
},
{
"$ref": "#/definitions/url"
},
{
"$ref": "#/definitions/code"
},
{
"$ref": "#/definitions/value"
},
{
"$ref": "#/definitions/diff"
},
{
"$ref": "#/definitions/markdown"
},
{
"$ref": "#/definitions/commit"
},
{
"$ref": "#/definitions/file_location"
},
{
"$ref": "#/definitions/module_location"
}
]
},
"text_value": {
"type": "string"
},
"named_field": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"$ref": "#/definitions/text_value",
"minLength": 1
},
"description": {
"$ref": "#/definitions/text_value"
}
}
},
"named_list": {
"type": "object",
"description": "An object with named and typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "named-list"
},
"items": {
"type": "object",
"patternProperties": {
"^.*$": {
"allOf": [
{
"$ref": "#/definitions/named_field"
},
{
"$ref": "#/definitions/detail_type"
}
]
}
}
}
}
},
"list": {
"type": "object",
"description": "A list of typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "list"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
},
"table": {
"type": "object",
"description": "A table of typed fields",
"required": [
"type",
"rows"
],
"properties": {
"type": {
"const": "table"
},
"header": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
},
"rows": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
}
},
"text": {
"type": "object",
"description": "Raw text",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "text"
},
"value": {
"$ref": "#/definitions/text_value"
}
}
},
"url": {
"type": "object",
"description": "A single URL",
"required": [
"type",
"href"
],
"properties": {
"type": {
"const": "url"
},
"text": {
"$ref": "#/definitions/text_value"
},
"href": {
"type": "string",
"minLength": 1,
"examples": [
"http://mysite.com"
]
}
}
},
"code": {
"type": "object",
"description": "A codeblock",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "code"
},
"value": {
"type": "string"
},
"lang": {
"type": "string",
"description": "A programming language"
}
}
},
"value": {
"type": "object",
"description": "A field that can store a range of types of value",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "value"
},
"value": {
"type": [
"number",
"string",
"boolean"
]
}
}
},
"diff": {
"type": "object",
"description": "A diff",
"required": [
"type",
"before",
"after"
],
"properties": {
"type": {
"const": "diff"
},
"before": {
"type": "string"
},
"after": {
"type": "string"
}
}
},
"markdown": {
"type": "object",
"description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "markdown"
},
"value": {
"$ref": "#/definitions/text_value",
"examples": [
"Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)"
]
}
}
},
"commit": {
"type": "object",
"description": "A commit/tag/branch within the GitLab project",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "commit"
},
"value": {
"type": "string",
"description": "The commit SHA",
"minLength": 1
}
}
},
"file_location": {
"type": "object",
"description": "A location within a file in the project",
"required": [
"type",
"file_name",
"line_start"
],
"properties": {
"type": {
"const": "file-location"
},
"file_name": {
"type": "string",
"minLength": 1
},
"line_start": {
"type": "integer"
},
"line_end": {
"type": "integer"
}
}
},
"module_location": {
"type": "object",
"description": "A location within a binary module of the form module+relative_offset",
"required": [
"type",
"module_name",
"offset"
],
"properties": {
"type": {
"const": "module-location"
},
"module_name": {
"type": "string",
"minLength": 1,
"examples": [
"compiled_binary"
]
},
"offset": {
"type": "integer",
"examples": [
100
]
}
}
}
},
"self": {
"version": "14.0.0"
},
"required": [
"version",
"vulnerabilities"
],
"additionalProperties": true,
"properties": {
"scan": {
"type": "object",
"required": [
"end_time",
"scanned_resources",
"scanner",
"start_time",
"status",
"type"
],
"properties": {
"end_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-01-28T03:26:02"
]
},
"messages": {
"type": "array",
"items": {
"type": "object",
"description": "Communication intended for the initiator of a scan.",
"required": [
"level",
"value"
],
"properties": {
"level": {
"type": "string",
"description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.",
"enum": [
"info",
"warn",
"fatal"
],
"examples": [
"info"
]
},
"value": {
"type": "string",
"description": "The message to communicate.",
"minLength": 1,
"examples": [
"Permission denied, scanning aborted"
]
}
}
}
},
"scanner": {
"type": "object",
"description": "Object defining the scanner used to perform the scan.",
"required": [
"id",
"name",
"version",
"vendor"
],
"properties": {
"id": {
"type": "string",
"description": "Unique id that identifies the scanner.",
"minLength": 1,
"examples": [
"my-sast-scanner"
]
},
"name": {
"type": "string",
"description": "A human readable value that identifies the scanner, not required to be unique.",
"minLength": 1,
"examples": [
"My SAST Scanner"
]
},
"url": {
"type": "string",
"description": "A link to more information about the scanner.",
"examples": [
"https://scanner.url"
]
},
"version": {
"type": "string",
"description": "The version of the scanner.",
"minLength": 1,
"examples": [
"1.0.2"
]
},
"vendor": {
"type": "object",
"description": "The vendor/maintainer of the scanner.",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the vendor.",
"minLength": 1,
"examples": [
"GitLab"
]
}
}
}
}
},
"start_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-02-14T16:01:59"
]
},
"status": {
"type": "string",
"description": "Result of the scan.",
"enum": [
"success",
"failure"
]
},
"type": {
"type": "string",
"description": "Type of the scan.",
"enum": [
"dast",
"api_fuzzing"
]
},
"scanned_resources": {
"type": "array",
"description": "The attack surface scanned by DAST.",
"items": {
"type": "object",
"required": [
"method",
"url",
"type"
],
"properties": {
"method": {
"type": "string",
"minLength": 1,
"description": "HTTP method of the scanned resource.",
"examples": [
"GET",
"POST",
"HEAD"
]
},
"url": {
"type": "string",
"minLength": 1,
"description": "URL of the scanned resource.",
"examples": [
"http://my.site.com/a-page"
]
},
"type": {
"type": "string",
"minLength": 1,
"description": "Type of the scanned resource, for DAST, this must be 'url'.",
"examples": [
"url"
]
}
}
}
}
}
},
"schema": {
"type": "string",
"description": "URI pointing to the validating security report schema.",
"format": "uri"
},
"version": {
"type": "string",
"description": "The version of the schema to which the JSON report conforms.",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
},
"vulnerabilities": {
"type": "array",
"description": "Array of vulnerability objects.",
"items": {
"type": "object",
"description": "Describes the vulnerability.",
"required": [
"category",
"cve",
"identifiers",
"location",
"scanner"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier of the vulnerability. This is recommended to be a UUID.",
"examples": [
"642735a5-1425-428d-8d4e-3c854885a3c9"
]
},
"category": {
"type": "string",
"minLength": 1,
"description": "Describes where this vulnerability belongs (for example, SAST, Dependency Scanning, and so on)."
},
"name": {
"type": "string",
"description": "The name of the vulnerability. This must not include the finding's specific information."
},
"message": {
"type": "string",
"description": "A short text section that describes the vulnerability. This may include the finding's specific information."
},
"description": {
"type": "string",
"description": "A long text section describing the vulnerability more fully."
},
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
},
"severity": {
"type": "string",
"description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.",
"enum": [
"Info",
"Unknown",
"Low",
"Medium",
"High",
"Critical"
]
},
"confidence": {
"type": "string",
"description": "How reliable the vulnerability's assessment is. Possible values are Ignore, Unknown, Experimental, Low, Medium, High, and Confirmed. Note that some analyzers may not report all these possible values.",
"enum": [
"Ignore",
"Unknown",
"Experimental",
"Low",
"Medium",
"High",
"Confirmed"
]
},
"solution": {
"type": "string",
"description": "Explanation of how to fix the vulnerability."
},
"scanner": {
"description": "Describes the scanner used to find this vulnerability.",
"type": "object",
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "The scanner's ID, as a snake_case string."
},
"name": {
"type": "string",
"minLength": 1,
"description": "Human-readable name of the scanner."
}
}
},
"identifiers": {
"type": "array",
"minItems": 1,
"description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.",
"items": {
"type": "object",
"required": [
"type",
"name",
"value"
],
"properties": {
"type": {
"type": "string",
"description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).",
"minLength": 1
},
"name": {
"type": "string",
"description": "Human-readable name of the identifier.",
"minLength": 1
},
"url": {
"type": "string",
"description": "URL of the identifier's documentation.",
"format": "uri"
},
"value": {
"type": "string",
"description": "Value of the identifier, for matching purpose.",
"minLength": 1
}
}
}
},
"links": {
"type": "array",
"description": "An array of references to external documentation or articles that describe the vulnerability.",
"items": {
"type": "object",
"required": [
"url"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the vulnerability details link."
},
"url": {
"type": "string",
"description": "URL of the vulnerability details document.",
"format": "uri"
}
}
}
},
"details": {
"$ref": "#/definitions/named_list/properties/items"
},
"evidence": {
"type": "object",
"properties": {
"source": {
"type": "object",
"description": "Source of evidence",
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "Unique source identifier",
"examples": [
"assert:LogAnalysis",
"assert:StatusCode"
]
},
"name": {
"type": "string",
"minLength": 1,
"description": "Source display name",
"examples": [
"Log Analysis",
"Status Code"
]
},
"url": {
"type": "string",
"description": "Link to additional information",
"examples": [
"https://docs.gitlab.com/ee/development/integrations/secure.html"
]
}
}
},
"summary": {
"type": "string",
"description": "Human readable string containing evidence of the vulnerability.",
"examples": [
"Credit card 4111111111111111 found",
"Server leaked information nginx/1.17.6"
]
},
"request": {
"type": "object",
"description": "An HTTP request.",
"required": [
"headers",
"method",
"url"
],
"properties": {
"headers": {
"type": "array",
"description": "HTTP headers present on the request.",
"items": {
"type": "object",
"required": [
"name",
"value"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Name of the HTTP header.",
"examples": [
"Accept",
"Content-Length",
"Content-Type"
]
},
"value": {
"type": "string",
"minLength": 1,
"description": "Value of the HTTP header.",
"examples": [
"*/*",
"560",
"application/json; charset=utf-8"
]
}
}
}
},
"method": {
"type": "string",
"minLength": 1,
"description": "HTTP method used in the request.",
"examples": [
"GET",
"POST"
]
},
"url": {
"type": "string",
"minLength": 1,
"description": "URL of the request.",
"examples": [
"http://my.site.com/vulnerable-endpoint?show-credit-card"
]
},
"body": {
"type": "string",
"description": "Body of the request for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.",
"examples": [
"user=jsmith&first=%27&last=smith"
]
}
}
},
"response": {
"type": "object",
"description": "An HTTP response.",
"required": [
"headers",
"reason_phrase",
"status_code"
],
"properties": {
"headers": {
"type": "array",
"description": "HTTP headers present on the request.",
"items": {
"type": "object",
"required": [
"name",
"value"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Name of the HTTP header.",
"examples": [
"Accept",
"Content-Length",
"Content-Type"
]
},
"value": {
"type": "string",
"minLength": 1,
"description": "Value of the HTTP header.",
"examples": [
"*/*",
"560",
"application/json; charset=utf-8"
]
}
}
}
},
"reason_phrase": {
"type": "string",
"description": "HTTP reason phrase of the response.",
"examples": [
"OK",
"Internal Server Error"
]
},
"status_code": {
"type": "integer",
"description": "HTTP status code of the response.",
"examples": [
200,
500
]
},
"body": {
"type": "string",
"description": "Body of the response for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.",
"examples": [
"{\"user_id\": 2}"
]
}
}
},
"supporting_messages": {
"type": "array",
"description": "Array of supporting http messages.",
"items": {
"type": "object",
"description": "A supporting http message.",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Message display name.",
"examples": [
"Unmodified",
"Recorded"
]
},
"request": {
"type": "object",
"description": "An HTTP request.",
"required": [
"headers",
"method",
"url"
],
"properties": {
"headers": {
"type": "array",
"description": "HTTP headers present on the request.",
"items": {
"type": "object",
"required": [
"name",
"value"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Name of the HTTP header.",
"examples": [
"Accept",
"Content-Length",
"Content-Type"
]
},
"value": {
"type": "string",
"minLength": 1,
"description": "Value of the HTTP header.",
"examples": [
"*/*",
"560",
"application/json; charset=utf-8"
]
}
}
}
},
"method": {
"type": "string",
"minLength": 1,
"description": "HTTP method used in the request.",
"examples": [
"GET",
"POST"
]
},
"url": {
"type": "string",
"minLength": 1,
"description": "URL of the request.",
"examples": [
"http://my.site.com/vulnerable-endpoint?show-credit-card"
]
},
"body": {
"type": "string",
"description": "Body of the request for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.",
"examples": [
"user=jsmith&first=%27&last=smith"
]
}
}
},
"response": {
"type": "object",
"description": "An HTTP response.",
"required": [
"headers",
"reason_phrase",
"status_code"
],
"properties": {
"headers": {
"type": "array",
"description": "HTTP headers present on the request.",
"items": {
"type": "object",
"required": [
"name",
"value"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Name of the HTTP header.",
"examples": [
"Accept",
"Content-Length",
"Content-Type"
]
},
"value": {
"type": "string",
"minLength": 1,
"description": "Value of the HTTP header.",
"examples": [
"*/*",
"560",
"application/json; charset=utf-8"
]
}
}
}
},
"reason_phrase": {
"type": "string",
"description": "HTTP reason phrase of the response.",
"examples": [
"OK",
"Internal Server Error"
]
},
"status_code": {
"type": "integer",
"description": "HTTP status code of the response.",
"examples": [
200,
500
]
},
"body": {
"type": "string",
"description": "Body of the response for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.",
"examples": [
"{\"user_id\": 2}"
]
}
}
}
}
}
}
}
},
"location": {
"type": "object",
"description": "Identifies the vulnerability's location.",
"properties": {
"hostname": {
"type": "string",
"description": "The protocol, domain, and port of the application where the vulnerability was found."
},
"method": {
"type": "string",
"description": "The HTTP method that was used to request the URL where the vulnerability was found."
},
"param": {
"type": "string",
"description": "A value provided by a vulnerability rule related to the found vulnerability. Examples include a header value, or a parameter used in a HTTP POST."
},
"path": {
"type": "string",
"description": "The path of the URL where the vulnerability was found. Typically, this would start with a forward slash."
}
}
},
"assets": {
"type": "array",
"description": "Array of build assets associated with vulnerability.",
"items": {
"type": "object",
"description": "Describes an asset associated with vulnerability.",
"required": [
"type",
"name",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "The type of asset",
"enum": [
"http_session",
"postman"
]
},
"name": {
"type": "string",
"minLength": 1,
"description": "Display name for asset",
"examples": [
"HTTP Messages",
"Postman Collection"
]
},
"url": {
"type": "string",
"minLength": 1,
"description": "Link to asset in build artifacts",
"examples": [
"https://gitlab.com/gitlab-org/security-products/dast/-/jobs/626397001/artifacts/file//output/zap_session.data"
]
}
}
}
},
"discovered_at": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss.sss, representing when the vulnerability was discovered",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}\\.\\d{3}$",
"examples": [
"2020-01-28T03:26:02.956"
]
}
}
}
},
"remediations": {
"type": "array",
"description": "An array of objects containing information on available remediations, along with patch diffs to apply.",
"items": {
"type": "object",
"required": [
"fixes",
"summary",
"diff"
],
"properties": {
"fixes": {
"type": "array",
"description": "An array of strings that represent references to vulnerabilities fixed by this remediation.",
"items": {
"type": "object",
"required": [
"cve"
],
"properties": {
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
}
}
}
},
"summary": {
"type": "string",
"minLength": 1,
"description": "An overview of how the vulnerabilities were fixed."
},
"diff": {
"type": "string",
"minLength": 1,
"description": "A base64-encoded remediation code diff, compatible with git apply."
}
}
}
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Report format for GitLab Dependency Scanning",
"description": "This schema provides the the report format for Dependency Scanning analyzers (https://docs.gitlab.com/ee/user/application_security/dependency_scanning).",
"definitions": {
"detail_type": {
"oneOf": [
{
"$ref": "#/definitions/named_list"
},
{
"$ref": "#/definitions/list"
},
{
"$ref": "#/definitions/table"
},
{
"$ref": "#/definitions/text"
},
{
"$ref": "#/definitions/url"
},
{
"$ref": "#/definitions/code"
},
{
"$ref": "#/definitions/value"
},
{
"$ref": "#/definitions/diff"
},
{
"$ref": "#/definitions/markdown"
},
{
"$ref": "#/definitions/commit"
},
{
"$ref": "#/definitions/file_location"
},
{
"$ref": "#/definitions/module_location"
}
]
},
"text_value": {
"type": "string"
},
"named_field": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"$ref": "#/definitions/text_value",
"minLength": 1
},
"description": {
"$ref": "#/definitions/text_value"
}
}
},
"named_list": {
"type": "object",
"description": "An object with named and typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "named-list"
},
"items": {
"type": "object",
"patternProperties": {
"^.*$": {
"allOf": [
{
"$ref": "#/definitions/named_field"
},
{
"$ref": "#/definitions/detail_type"
}
]
}
}
}
}
},
"list": {
"type": "object",
"description": "A list of typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "list"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
},
"table": {
"type": "object",
"description": "A table of typed fields",
"required": [
"type",
"rows"
],
"properties": {
"type": {
"const": "table"
},
"header": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
},
"rows": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
}
},
"text": {
"type": "object",
"description": "Raw text",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "text"
},
"value": {
"$ref": "#/definitions/text_value"
}
}
},
"url": {
"type": "object",
"description": "A single URL",
"required": [
"type",
"href"
],
"properties": {
"type": {
"const": "url"
},
"text": {
"$ref": "#/definitions/text_value"
},
"href": {
"type": "string",
"minLength": 1,
"examples": [
"http://mysite.com"
]
}
}
},
"code": {
"type": "object",
"description": "A codeblock",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "code"
},
"value": {
"type": "string"
},
"lang": {
"type": "string",
"description": "A programming language"
}
}
},
"value": {
"type": "object",
"description": "A field that can store a range of types of value",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "value"
},
"value": {
"type": [
"number",
"string",
"boolean"
]
}
}
},
"diff": {
"type": "object",
"description": "A diff",
"required": [
"type",
"before",
"after"
],
"properties": {
"type": {
"const": "diff"
},
"before": {
"type": "string"
},
"after": {
"type": "string"
}
}
},
"markdown": {
"type": "object",
"description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "markdown"
},
"value": {
"$ref": "#/definitions/text_value",
"examples": [
"Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)"
]
}
}
},
"commit": {
"type": "object",
"description": "A commit/tag/branch within the GitLab project",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "commit"
},
"value": {
"type": "string",
"description": "The commit SHA",
"minLength": 1
}
}
},
"file_location": {
"type": "object",
"description": "A location within a file in the project",
"required": [
"type",
"file_name",
"line_start"
],
"properties": {
"type": {
"const": "file-location"
},
"file_name": {
"type": "string",
"minLength": 1
},
"line_start": {
"type": "integer"
},
"line_end": {
"type": "integer"
}
}
},
"module_location": {
"type": "object",
"description": "A location within a binary module of the form module+relative_offset",
"required": [
"type",
"module_name",
"offset"
],
"properties": {
"type": {
"const": "module-location"
},
"module_name": {
"type": "string",
"minLength": 1,
"examples": [
"compiled_binary"
]
},
"offset": {
"type": "integer",
"examples": [
100
]
}
}
}
},
"self": {
"version": "14.0.0"
},
"required": [
"dependency_files",
"version",
"vulnerabilities"
],
"additionalProperties": true,
"properties": {
"scan": {
"type": "object",
"required": [
"end_time",
"scanner",
"start_time",
"status",
"type"
],
"properties": {
"end_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-01-28T03:26:02"
]
},
"messages": {
"type": "array",
"items": {
"type": "object",
"description": "Communication intended for the initiator of a scan.",
"required": [
"level",
"value"
],
"properties": {
"level": {
"type": "string",
"description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.",
"enum": [
"info",
"warn",
"fatal"
],
"examples": [
"info"
]
},
"value": {
"type": "string",
"description": "The message to communicate.",
"minLength": 1,
"examples": [
"Permission denied, scanning aborted"
]
}
}
}
},
"scanner": {
"type": "object",
"description": "Object defining the scanner used to perform the scan.",
"required": [
"id",
"name",
"version",
"vendor"
],
"properties": {
"id": {
"type": "string",
"description": "Unique id that identifies the scanner.",
"minLength": 1,
"examples": [
"my-sast-scanner"
]
},
"name": {
"type": "string",
"description": "A human readable value that identifies the scanner, not required to be unique.",
"minLength": 1,
"examples": [
"My SAST Scanner"
]
},
"url": {
"type": "string",
"description": "A link to more information about the scanner.",
"examples": [
"https://scanner.url"
]
},
"version": {
"type": "string",
"description": "The version of the scanner.",
"minLength": 1,
"examples": [
"1.0.2"
]
},
"vendor": {
"type": "object",
"description": "The vendor/maintainer of the scanner.",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the vendor.",
"minLength": 1,
"examples": [
"GitLab"
]
}
}
}
}
},
"start_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-02-14T16:01:59"
]
},
"status": {
"type": "string",
"description": "Result of the scan.",
"enum": [
"success",
"failure"
]
},
"type": {
"type": "string",
"description": "Type of the scan.",
"enum": [
"dependency_scanning"
]
}
}
},
"schema": {
"type": "string",
"description": "URI pointing to the validating security report schema.",
"format": "uri"
},
"version": {
"type": "string",
"description": "The version of the schema to which the JSON report conforms.",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
},
"vulnerabilities": {
"type": "array",
"description": "Array of vulnerability objects.",
"items": {
"type": "object",
"description": "Describes the vulnerability.",
"required": [
"category",
"cve",
"identifiers",
"location",
"scanner"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier of the vulnerability. This is recommended to be a UUID.",
"examples": [
"642735a5-1425-428d-8d4e-3c854885a3c9"
]
},
"category": {
"type": "string",
"minLength": 1,
"description": "Describes where this vulnerability belongs (for example, SAST, Dependency Scanning, and so on)."
},
"name": {
"type": "string",
"description": "The name of the vulnerability. This must not include the finding's specific information."
},
"message": {
"type": "string",
"description": "A short text section that describes the vulnerability. This may include the finding's specific information."
},
"description": {
"type": "string",
"description": "A long text section describing the vulnerability more fully."
},
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
},
"severity": {
"type": "string",
"description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.",
"enum": [
"Info",
"Unknown",
"Low",
"Medium",
"High",
"Critical"
]
},
"confidence": {
"type": "string",
"description": "How reliable the vulnerability's assessment is. Possible values are Ignore, Unknown, Experimental, Low, Medium, High, and Confirmed. Note that some analyzers may not report all these possible values.",
"enum": [
"Ignore",
"Unknown",
"Experimental",
"Low",
"Medium",
"High",
"Confirmed"
]
},
"solution": {
"type": "string",
"description": "Explanation of how to fix the vulnerability."
},
"scanner": {
"description": "Describes the scanner used to find this vulnerability.",
"type": "object",
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "The scanner's ID, as a snake_case string."
},
"name": {
"type": "string",
"minLength": 1,
"description": "Human-readable name of the scanner."
}
}
},
"identifiers": {
"type": "array",
"minItems": 1,
"description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.",
"items": {
"type": "object",
"required": [
"type",
"name",
"value"
],
"properties": {
"type": {
"type": "string",
"description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).",
"minLength": 1
},
"name": {
"type": "string",
"description": "Human-readable name of the identifier.",
"minLength": 1
},
"url": {
"type": "string",
"description": "URL of the identifier's documentation.",
"format": "uri"
},
"value": {
"type": "string",
"description": "Value of the identifier, for matching purpose.",
"minLength": 1
}
}
}
},
"links": {
"type": "array",
"description": "An array of references to external documentation or articles that describe the vulnerability.",
"items": {
"type": "object",
"required": [
"url"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the vulnerability details link."
},
"url": {
"type": "string",
"description": "URL of the vulnerability details document.",
"format": "uri"
}
}
}
},
"details": {
"$ref": "#/definitions/named_list/properties/items"
},
"location": {
"type": "object",
"description": "Identifies the vulnerability's location.",
"required": [
"file",
"dependency"
],
"properties": {
"file": {
"type": "string",
"minLength": 1,
"description": "Path to the manifest or lock file where the dependency is declared (such as yarn.lock)."
},
"dependency": {
"type": "object",
"description": "Describes the dependency of a project where the vulnerability is located.",
"properties": {
"package": {
"type": "object",
"description": "Provides information on the package where the vulnerability is located.",
"properties": {
"name": {
"type": "string",
"description": "Name of the package where the vulnerability is located."
}
}
},
"version": {
"type": "string",
"description": "Version of the vulnerable package."
},
"iid": {
"description": "ID that identifies the dependency in the scope of a dependency file.",
"type": "number"
},
"direct": {
"type": "boolean",
"description": "Tells whether this is a direct, top-level dependency of the scanned project."
},
"dependency_path": {
"type": "array",
"description": "Ancestors of the dependency, starting from a direct project dependency, and ending with an immediate parent of the dependency. The dependency itself is excluded from the path. Direct dependencies have no path.",
"items": {
"type": "object",
"required": [
"iid"
],
"properties": {
"iid": {
"type": "number",
"description": "ID that is unique in the scope of a parent object, and specific to the resource type."
}
}
}
}
}
}
}
}
}
}
},
"remediations": {
"type": "array",
"description": "An array of objects containing information on available remediations, along with patch diffs to apply.",
"items": {
"type": "object",
"required": [
"fixes",
"summary",
"diff"
],
"properties": {
"fixes": {
"type": "array",
"description": "An array of strings that represent references to vulnerabilities fixed by this remediation.",
"items": {
"type": "object",
"required": [
"cve"
],
"properties": {
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
}
}
}
},
"summary": {
"type": "string",
"minLength": 1,
"description": "An overview of how the vulnerabilities were fixed."
},
"diff": {
"type": "string",
"minLength": 1,
"description": "A base64-encoded remediation code diff, compatible with git apply."
}
}
}
},
"dependency_files": {
"type": "array",
"description": "List of dependency files identified in the project.",
"items": {
"type": "object",
"required": [
"path",
"package_manager",
"dependencies"
],
"properties": {
"path": {
"type": "string",
"minLength": 1
},
"package_manager": {
"type": "string",
"minLength": 1
},
"dependencies": {
"type": "array",
"items": {
"type": "object",
"description": "Describes the dependency of a project where the vulnerability is located.",
"properties": {
"package": {
"type": "object",
"description": "Provides information on the package where the vulnerability is located.",
"properties": {
"name": {
"type": "string",
"description": "Name of the package where the vulnerability is located."
}
}
},
"version": {
"type": "string",
"description": "Version of the vulnerable package."
},
"iid": {
"description": "ID that identifies the dependency in the scope of a dependency file.",
"type": "number"
},
"direct": {
"type": "boolean",
"description": "Tells whether this is a direct, top-level dependency of the scanned project."
},
"dependency_path": {
"type": "array",
"description": "Ancestors of the dependency, starting from a direct project dependency, and ending with an immediate parent of the dependency. The dependency itself is excluded from the path. Direct dependencies have no path.",
"items": {
"type": "object",
"required": [
"iid"
],
"properties": {
"iid": {
"type": "number",
"description": "ID that is unique in the scope of a parent object, and specific to the resource type."
}
}
}
}
}
}
}
}
}
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Report format for GitLab SAST",
"description": "This schema provides the report format for Static Application Security Testing analyzers (https://docs.gitlab.com/ee/user/application_security/sast).",
"definitions": {
"detail_type": {
"oneOf": [
{
"$ref": "#/definitions/named_list"
},
{
"$ref": "#/definitions/list"
},
{
"$ref": "#/definitions/table"
},
{
"$ref": "#/definitions/text"
},
{
"$ref": "#/definitions/url"
},
{
"$ref": "#/definitions/code"
},
{
"$ref": "#/definitions/value"
},
{
"$ref": "#/definitions/diff"
},
{
"$ref": "#/definitions/markdown"
},
{
"$ref": "#/definitions/commit"
},
{
"$ref": "#/definitions/file_location"
},
{
"$ref": "#/definitions/module_location"
}
]
},
"text_value": {
"type": "string"
},
"named_field": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"$ref": "#/definitions/text_value",
"minLength": 1
},
"description": {
"$ref": "#/definitions/text_value"
}
}
},
"named_list": {
"type": "object",
"description": "An object with named and typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "named-list"
},
"items": {
"type": "object",
"patternProperties": {
"^.*$": {
"allOf": [
{
"$ref": "#/definitions/named_field"
},
{
"$ref": "#/definitions/detail_type"
}
]
}
}
}
}
},
"list": {
"type": "object",
"description": "A list of typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "list"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
},
"table": {
"type": "object",
"description": "A table of typed fields",
"required": [
"type",
"rows"
],
"properties": {
"type": {
"const": "table"
},
"header": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
},
"rows": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
}
},
"text": {
"type": "object",
"description": "Raw text",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "text"
},
"value": {
"$ref": "#/definitions/text_value"
}
}
},
"url": {
"type": "object",
"description": "A single URL",
"required": [
"type",
"href"
],
"properties": {
"type": {
"const": "url"
},
"text": {
"$ref": "#/definitions/text_value"
},
"href": {
"type": "string",
"minLength": 1,
"examples": [
"http://mysite.com"
]
}
}
},
"code": {
"type": "object",
"description": "A codeblock",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "code"
},
"value": {
"type": "string"
},
"lang": {
"type": "string",
"description": "A programming language"
}
}
},
"value": {
"type": "object",
"description": "A field that can store a range of types of value",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "value"
},
"value": {
"type": [
"number",
"string",
"boolean"
]
}
}
},
"diff": {
"type": "object",
"description": "A diff",
"required": [
"type",
"before",
"after"
],
"properties": {
"type": {
"const": "diff"
},
"before": {
"type": "string"
},
"after": {
"type": "string"
}
}
},
"markdown": {
"type": "object",
"description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "markdown"
},
"value": {
"$ref": "#/definitions/text_value",
"examples": [
"Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)"
]
}
}
},
"commit": {
"type": "object",
"description": "A commit/tag/branch within the GitLab project",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "commit"
},
"value": {
"type": "string",
"description": "The commit SHA",
"minLength": 1
}
}
},
"file_location": {
"type": "object",
"description": "A location within a file in the project",
"required": [
"type",
"file_name",
"line_start"
],
"properties": {
"type": {
"const": "file-location"
},
"file_name": {
"type": "string",
"minLength": 1
},
"line_start": {
"type": "integer"
},
"line_end": {
"type": "integer"
}
}
},
"module_location": {
"type": "object",
"description": "A location within a binary module of the form module+relative_offset",
"required": [
"type",
"module_name",
"offset"
],
"properties": {
"type": {
"const": "module-location"
},
"module_name": {
"type": "string",
"minLength": 1,
"examples": [
"compiled_binary"
]
},
"offset": {
"type": "integer",
"examples": [
100
]
}
}
}
},
"self": {
"version": "14.0.0"
},
"required": [
"version",
"vulnerabilities"
],
"additionalProperties": true,
"properties": {
"scan": {
"type": "object",
"required": [
"end_time",
"scanner",
"start_time",
"status",
"type"
],
"properties": {
"end_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-01-28T03:26:02"
]
},
"messages": {
"type": "array",
"items": {
"type": "object",
"description": "Communication intended for the initiator of a scan.",
"required": [
"level",
"value"
],
"properties": {
"level": {
"type": "string",
"description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.",
"enum": [
"info",
"warn",
"fatal"
],
"examples": [
"info"
]
},
"value": {
"type": "string",
"description": "The message to communicate.",
"minLength": 1,
"examples": [
"Permission denied, scanning aborted"
]
}
}
}
},
"scanner": {
"type": "object",
"description": "Object defining the scanner used to perform the scan.",
"required": [
"id",
"name",
"version",
"vendor"
],
"properties": {
"id": {
"type": "string",
"description": "Unique id that identifies the scanner.",
"minLength": 1,
"examples": [
"my-sast-scanner"
]
},
"name": {
"type": "string",
"description": "A human readable value that identifies the scanner, not required to be unique.",
"minLength": 1,
"examples": [
"My SAST Scanner"
]
},
"url": {
"type": "string",
"description": "A link to more information about the scanner.",
"examples": [
"https://scanner.url"
]
},
"version": {
"type": "string",
"description": "The version of the scanner.",
"minLength": 1,
"examples": [
"1.0.2"
]
},
"vendor": {
"type": "object",
"description": "The vendor/maintainer of the scanner.",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the vendor.",
"minLength": 1,
"examples": [
"GitLab"
]
}
}
}
}
},
"start_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-02-14T16:01:59"
]
},
"status": {
"type": "string",
"description": "Result of the scan.",
"enum": [
"success",
"failure"
]
},
"type": {
"type": "string",
"description": "Type of the scan.",
"enum": [
"sast"
]
}
}
},
"schema": {
"type": "string",
"description": "URI pointing to the validating security report schema.",
"format": "uri"
},
"version": {
"type": "string",
"description": "The version of the schema to which the JSON report conforms.",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
},
"vulnerabilities": {
"type": "array",
"description": "Array of vulnerability objects.",
"items": {
"type": "object",
"description": "Describes the vulnerability.",
"required": [
"category",
"cve",
"identifiers",
"location",
"scanner"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier of the vulnerability. This is recommended to be a UUID.",
"examples": [
"642735a5-1425-428d-8d4e-3c854885a3c9"
]
},
"category": {
"type": "string",
"minLength": 1,
"description": "Describes where this vulnerability belongs (for example, SAST, Dependency Scanning, and so on)."
},
"name": {
"type": "string",
"description": "The name of the vulnerability. This must not include the finding's specific information."
},
"message": {
"type": "string",
"description": "A short text section that describes the vulnerability. This may include the finding's specific information."
},
"description": {
"type": "string",
"description": "A long text section describing the vulnerability more fully."
},
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
},
"severity": {
"type": "string",
"description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.",
"enum": [
"Info",
"Unknown",
"Low",
"Medium",
"High",
"Critical"
]
},
"confidence": {
"type": "string",
"description": "How reliable the vulnerability's assessment is. Possible values are Ignore, Unknown, Experimental, Low, Medium, High, and Confirmed. Note that some analyzers may not report all these possible values.",
"enum": [
"Ignore",
"Unknown",
"Experimental",
"Low",
"Medium",
"High",
"Confirmed"
]
},
"solution": {
"type": "string",
"description": "Explanation of how to fix the vulnerability."
},
"scanner": {
"description": "Describes the scanner used to find this vulnerability.",
"type": "object",
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "The scanner's ID, as a snake_case string."
},
"name": {
"type": "string",
"minLength": 1,
"description": "Human-readable name of the scanner."
}
}
},
"identifiers": {
"type": "array",
"minItems": 1,
"description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.",
"items": {
"type": "object",
"required": [
"type",
"name",
"value"
],
"properties": {
"type": {
"type": "string",
"description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).",
"minLength": 1
},
"name": {
"type": "string",
"description": "Human-readable name of the identifier.",
"minLength": 1
},
"url": {
"type": "string",
"description": "URL of the identifier's documentation.",
"format": "uri"
},
"value": {
"type": "string",
"description": "Value of the identifier, for matching purpose.",
"minLength": 1
}
}
}
},
"links": {
"type": "array",
"description": "An array of references to external documentation or articles that describe the vulnerability.",
"items": {
"type": "object",
"required": [
"url"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the vulnerability details link."
},
"url": {
"type": "string",
"description": "URL of the vulnerability details document.",
"format": "uri"
}
}
}
},
"details": {
"$ref": "#/definitions/named_list/properties/items"
},
"location": {
"type": "object",
"description": "Identifies the vulnerability's location.",
"properties": {
"file": {
"type": "string",
"description": "Path to the file where the vulnerability is located."
},
"start_line": {
"type": "number",
"description": "The first line of the code affected by the vulnerability."
},
"end_line": {
"type": "number",
"description": "The last line of the code affected by the vulnerability."
},
"class": {
"type": "string",
"description": "Provides the name of the class where the vulnerability is located."
},
"method": {
"type": "string",
"description": "Provides the name of the method where the vulnerability is located."
}
}
},
"raw_source_code_extract": {
"type": "string",
"description": "Provides an unsanitized excerpt of the affected source code."
}
}
}
},
"remediations": {
"type": "array",
"description": "An array of objects containing information on available remediations, along with patch diffs to apply.",
"items": {
"type": "object",
"required": [
"fixes",
"summary",
"diff"
],
"properties": {
"fixes": {
"type": "array",
"description": "An array of strings that represent references to vulnerabilities fixed by this remediation.",
"items": {
"type": "object",
"required": [
"cve"
],
"properties": {
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
}
}
}
},
"summary": {
"type": "string",
"minLength": 1,
"description": "An overview of how the vulnerabilities were fixed."
},
"diff": {
"type": "string",
"minLength": 1,
"description": "A base64-encoded remediation code diff, compatible with git apply."
}
}
}
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Report format for GitLab Secret Detection",
"description": "This schema provides the the report format for the Secret Detection analyzer (https://docs.gitlab.com/ee/user/application_security/secret_detection)",
"definitions": {
"detail_type": {
"oneOf": [
{
"$ref": "#/definitions/named_list"
},
{
"$ref": "#/definitions/list"
},
{
"$ref": "#/definitions/table"
},
{
"$ref": "#/definitions/text"
},
{
"$ref": "#/definitions/url"
},
{
"$ref": "#/definitions/code"
},
{
"$ref": "#/definitions/value"
},
{
"$ref": "#/definitions/diff"
},
{
"$ref": "#/definitions/markdown"
},
{
"$ref": "#/definitions/commit"
},
{
"$ref": "#/definitions/file_location"
},
{
"$ref": "#/definitions/module_location"
}
]
},
"text_value": {
"type": "string"
},
"named_field": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"$ref": "#/definitions/text_value",
"minLength": 1
},
"description": {
"$ref": "#/definitions/text_value"
}
}
},
"named_list": {
"type": "object",
"description": "An object with named and typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "named-list"
},
"items": {
"type": "object",
"patternProperties": {
"^.*$": {
"allOf": [
{
"$ref": "#/definitions/named_field"
},
{
"$ref": "#/definitions/detail_type"
}
]
}
}
}
}
},
"list": {
"type": "object",
"description": "A list of typed fields",
"required": [
"type",
"items"
],
"properties": {
"type": {
"const": "list"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
},
"table": {
"type": "object",
"description": "A table of typed fields",
"required": [
"type",
"rows"
],
"properties": {
"type": {
"const": "table"
},
"header": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
},
"rows": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
}
},
"text": {
"type": "object",
"description": "Raw text",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "text"
},
"value": {
"$ref": "#/definitions/text_value"
}
}
},
"url": {
"type": "object",
"description": "A single URL",
"required": [
"type",
"href"
],
"properties": {
"type": {
"const": "url"
},
"text": {
"$ref": "#/definitions/text_value"
},
"href": {
"type": "string",
"minLength": 1,
"examples": [
"http://mysite.com"
]
}
}
},
"code": {
"type": "object",
"description": "A codeblock",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "code"
},
"value": {
"type": "string"
},
"lang": {
"type": "string",
"description": "A programming language"
}
}
},
"value": {
"type": "object",
"description": "A field that can store a range of types of value",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "value"
},
"value": {
"type": [
"number",
"string",
"boolean"
]
}
}
},
"diff": {
"type": "object",
"description": "A diff",
"required": [
"type",
"before",
"after"
],
"properties": {
"type": {
"const": "diff"
},
"before": {
"type": "string"
},
"after": {
"type": "string"
}
}
},
"markdown": {
"type": "object",
"description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "markdown"
},
"value": {
"$ref": "#/definitions/text_value",
"examples": [
"Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)"
]
}
}
},
"commit": {
"type": "object",
"description": "A commit/tag/branch within the GitLab project",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "commit"
},
"value": {
"type": "string",
"description": "The commit SHA",
"minLength": 1
}
}
},
"file_location": {
"type": "object",
"description": "A location within a file in the project",
"required": [
"type",
"file_name",
"line_start"
],
"properties": {
"type": {
"const": "file-location"
},
"file_name": {
"type": "string",
"minLength": 1
},
"line_start": {
"type": "integer"
},
"line_end": {
"type": "integer"
}
}
},
"module_location": {
"type": "object",
"description": "A location within a binary module of the form module+relative_offset",
"required": [
"type",
"module_name",
"offset"
],
"properties": {
"type": {
"const": "module-location"
},
"module_name": {
"type": "string",
"minLength": 1,
"examples": [
"compiled_binary"
]
},
"offset": {
"type": "integer",
"examples": [
100
]
}
}
}
},
"self": {
"version": "14.0.0"
},
"required": [
"version",
"vulnerabilities"
],
"additionalProperties": true,
"properties": {
"scan": {
"type": "object",
"required": [
"end_time",
"scanner",
"start_time",
"status",
"type"
],
"properties": {
"end_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-01-28T03:26:02"
]
},
"messages": {
"type": "array",
"items": {
"type": "object",
"description": "Communication intended for the initiator of a scan.",
"required": [
"level",
"value"
],
"properties": {
"level": {
"type": "string",
"description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.",
"enum": [
"info",
"warn",
"fatal"
],
"examples": [
"info"
]
},
"value": {
"type": "string",
"description": "The message to communicate.",
"minLength": 1,
"examples": [
"Permission denied, scanning aborted"
]
}
}
}
},
"scanner": {
"type": "object",
"description": "Object defining the scanner used to perform the scan.",
"required": [
"id",
"name",
"version",
"vendor"
],
"properties": {
"id": {
"type": "string",
"description": "Unique id that identifies the scanner.",
"minLength": 1,
"examples": [
"my-sast-scanner"
]
},
"name": {
"type": "string",
"description": "A human readable value that identifies the scanner, not required to be unique.",
"minLength": 1,
"examples": [
"My SAST Scanner"
]
},
"url": {
"type": "string",
"description": "A link to more information about the scanner.",
"examples": [
"https://scanner.url"
]
},
"version": {
"type": "string",
"description": "The version of the scanner.",
"minLength": 1,
"examples": [
"1.0.2"
]
},
"vendor": {
"type": "object",
"description": "The vendor/maintainer of the scanner.",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the vendor.",
"minLength": 1,
"examples": [
"GitLab"
]
}
}
}
}
},
"start_time": {
"type": "string",
"description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}$",
"examples": [
"2020-02-14T16:01:59"
]
},
"status": {
"type": "string",
"description": "Result of the scan.",
"enum": [
"success",
"failure"
]
},
"type": {
"type": "string",
"description": "Type of the scan.",
"enum": [
"secret_detection"
]
}
}
},
"schema": {
"type": "string",
"description": "URI pointing to the validating security report schema.",
"format": "uri"
},
"version": {
"type": "string",
"description": "The version of the schema to which the JSON report conforms.",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
},
"vulnerabilities": {
"type": "array",
"description": "Array of vulnerability objects.",
"items": {
"type": "object",
"description": "Describes the vulnerability.",
"required": [
"category",
"cve",
"identifiers",
"location",
"scanner"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier of the vulnerability. This is recommended to be a UUID.",
"examples": [
"642735a5-1425-428d-8d4e-3c854885a3c9"
]
},
"category": {
"type": "string",
"minLength": 1,
"description": "Describes where this vulnerability belongs (for example, SAST, Dependency Scanning, and so on)."
},
"name": {
"type": "string",
"description": "The name of the vulnerability. This must not include the finding's specific information."
},
"message": {
"type": "string",
"description": "A short text section that describes the vulnerability. This may include the finding's specific information."
},
"description": {
"type": "string",
"description": "A long text section describing the vulnerability more fully."
},
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
},
"severity": {
"type": "string",
"description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.",
"enum": [
"Info",
"Unknown",
"Low",
"Medium",
"High",
"Critical"
]
},
"confidence": {
"type": "string",
"description": "How reliable the vulnerability's assessment is. Possible values are Ignore, Unknown, Experimental, Low, Medium, High, and Confirmed. Note that some analyzers may not report all these possible values.",
"enum": [
"Ignore",
"Unknown",
"Experimental",
"Low",
"Medium",
"High",
"Confirmed"
]
},
"solution": {
"type": "string",
"description": "Explanation of how to fix the vulnerability."
},
"scanner": {
"description": "Describes the scanner used to find this vulnerability.",
"type": "object",
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "The scanner's ID, as a snake_case string."
},
"name": {
"type": "string",
"minLength": 1,
"description": "Human-readable name of the scanner."
}
}
},
"identifiers": {
"type": "array",
"minItems": 1,
"description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.",
"items": {
"type": "object",
"required": [
"type",
"name",
"value"
],
"properties": {
"type": {
"type": "string",
"description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).",
"minLength": 1
},
"name": {
"type": "string",
"description": "Human-readable name of the identifier.",
"minLength": 1
},
"url": {
"type": "string",
"description": "URL of the identifier's documentation.",
"format": "uri"
},
"value": {
"type": "string",
"description": "Value of the identifier, for matching purpose.",
"minLength": 1
}
}
}
},
"links": {
"type": "array",
"description": "An array of references to external documentation or articles that describe the vulnerability.",
"items": {
"type": "object",
"required": [
"url"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the vulnerability details link."
},
"url": {
"type": "string",
"description": "URL of the vulnerability details document.",
"format": "uri"
}
}
}
},
"details": {
"$ref": "#/definitions/named_list/properties/items"
},
"location": {
"required": [
"commit"
],
"properties": {
"file": {
"type": "string",
"description": "Path to the file where the vulnerability is located"
},
"commit": {
"type": "object",
"description": "Represents the commit in which the vulnerability was detected",
"required": [
"sha"
],
"properties": {
"author": {
"type": "string"
},
"date": {
"type": "string"
},
"message": {
"type": "string"
},
"sha": {
"type": "string",
"minLength": 1
}
}
},
"start_line": {
"type": "number",
"description": "The first line of the code affected by the vulnerability"
},
"end_line": {
"type": "number",
"description": "The last line of the code affected by the vulnerability"
},
"class": {
"type": "string",
"description": "Provides the name of the class where the vulnerability is located"
},
"method": {
"type": "string",
"description": "Provides the name of the method where the vulnerability is located"
}
}
},
"raw_source_code_extract": {
"type": "string",
"description": "Provides an unsanitized excerpt of the affected source code."
}
}
}
},
"remediations": {
"type": "array",
"description": "An array of objects containing information on available remediations, along with patch diffs to apply.",
"items": {
"type": "object",
"required": [
"fixes",
"summary",
"diff"
],
"properties": {
"fixes": {
"type": "array",
"description": "An array of strings that represent references to vulnerabilities fixed by this remediation.",
"items": {
"type": "object",
"required": [
"cve"
],
"properties": {
"cve": {
"type": "string",
"description": "(Deprecated - use vulnerabilities[].id instead) A fingerprint string value that represents a concrete finding. This is used to determine whether two findings are same, which may not be 100% accurate. Note that this is NOT a CVE as described by https://cve.mitre.org/."
}
}
}
},
"summary": {
"type": "string",
"minLength": 1,
"description": "An overview of how the vulnerabilities were fixed."
},
"diff": {
"type": "string",
"minLength": 1,
"description": "A base64-encoded remediation code diff, compatible with git apply."
}
}
}
}
}
}
......@@ -6,7 +6,7 @@ module Gitlab
module Security
class Report
attr_reader :created_at, :type, :pipeline, :findings, :scanners, :identifiers
attr_accessor :scan, :scanned_resources, :error
attr_accessor :scan, :scanned_resources, :errors
delegate :project_id, to: :pipeline
......@@ -18,14 +18,19 @@ module Gitlab
@scanners = {}
@identifiers = {}
@scanned_resources = []
@errors = []
end
def commit_sha
pipeline.sha
end
def add_error(type, message = 'An unexpected error happened!')
errors << { type: type, message: message }
end
def errored?
error.present?
errors.present?
end
def add_scanner(scanner)
......
......@@ -22,6 +22,77 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
artifact.each_blob { |blob| described_class.parse!(blob, report, vulnerability_finding_signatures_enabled) }
end
describe 'schema validation' do
let(:validator_class) { Gitlab::Ci::Parsers::Security::Validators::SchemaValidator }
let(:parser) { described_class.new('{}', report, vulnerability_finding_signatures_enabled, validate: validate) }
subject(:parse_report) { parser.parse! }
before do
allow(validator_class).to receive(:new).and_call_original
end
context 'when the validate flag is set as `false`' do
let(:validate) { false }
it 'does not run the validation logic' do
parse_report
expect(validator_class).not_to have_received(:new)
end
end
context 'when the validate flag is set as `true`' do
let(:validate) { true }
let(:valid?) { false }
before do
allow_next_instance_of(validator_class) do |instance|
allow(instance).to receive(:valid?).and_return(valid?)
allow(instance).to receive(:errors).and_return(['foo'])
end
allow(parser).to receive_messages(create_scanner: true, create_scan: true, collate_remediations: [])
end
it 'instantiates the validator with correct params' do
parse_report
expect(validator_class).to have_received(:new).with(report.type, {})
end
context 'when the report data is not valid according to the schema' do
it 'adds errors to the report' do
expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }])
end
it 'does not try to create report entities' do
parse_report
expect(parser).not_to have_received(:create_scanner)
expect(parser).not_to have_received(:create_scan)
expect(parser).not_to have_received(:collate_remediations)
end
end
context 'when the report data is valid according to the schema' do
let(:valid?) { true }
it 'does not add errors to the report' do
expect { parse_report }.not_to change { report.errors }.from([])
end
it 'keeps the execution flow as normal' do
parse_report
expect(parser).to have_received(:create_scanner)
expect(parser).to have_received(:create_scan)
expect(parser).to have_received(:collate_remediations)
end
end
end
end
describe 'parsing finding.name' do
let(:artifact) { build(:ee_ci_job_artifact, :common_security_report_with_blank_names) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
using RSpec::Parameterized::TableSyntax
where(:report_type, :expected_errors, :valid_data) do
:container_scanning | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] }
:coverage_fuzzing | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] }
:dast | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] }
:dependency_scanning | ['root is missing required keys: dependency_files, vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [], 'dependency_files' => [] }
:sast | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] }
:secret_detection | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] }
end
with_them do
let(:validator) { described_class.new(report_type, report_data) }
describe '#valid?' do
subject { validator.valid? }
context 'when given data is invalid according to the schema' do
let(:report_data) { {} }
it { is_expected.to be_falsey }
end
context 'when given data is valid according to the schema' do
let(:report_data) { valid_data }
it { is_expected.to be_truthy }
end
end
describe '#errors' do
let(:report_data) { { 'version' => '10.0.0' } }
subject { validator.errors }
it { is_expected.to eq(expected_errors) }
end
end
end
......@@ -139,4 +139,38 @@ RSpec.describe Gitlab::Ci::Reports::Security::Report do
it { is_expected.to eq(scanner_1) }
end
describe '#add_error' do
context 'when the message is not given' do
it 'adds a new error to report with the generic error message' do
expect { report.add_error('foo') }.to change { report.errors }
.from([])
.to([{ type: 'foo', message: 'An unexpected error happened!' }])
end
end
context 'when the message is given' do
it 'adds a new error to report' do
expect { report.add_error('foo', 'bar') }.to change { report.errors }
.from([])
.to([{ type: 'foo', message: 'bar' }])
end
end
end
describe 'errored?' do
subject { report.errored? }
context 'when the report does not have any errors' do
it { is_expected.to be_falsey }
end
context 'when the report has errors' do
before do
report.add_error('foo', 'bar')
end
it { is_expected.to be_truthy }
end
end
end
......@@ -639,4 +639,34 @@ RSpec.describe Ci::Build do
end
end
end
describe '#validate_schema?' do
let(:ci_build) { build(:ci_build) }
subject { ci_build.validate_schema? }
before do
ci_build.yaml_variables = variables
end
context 'when the yaml variables does not have the configuration' do
let(:variables) { [] }
it { is_expected.to be_falsey }
end
context 'when the yaml variables has the configuration' do
context 'when the configuration is set as `false`' do
let(:variables) { [{ key: 'VALIDATE_SCHEMA', value: 'false' }] }
it { is_expected.to be_falsey }
end
context 'when the configuration is set as `true`' do
let(:variables) { [{ key: 'VALIDATE_SCHEMA', value: 'true' }] }
it { is_expected.to be_truthy }
end
end
end
end
......@@ -6,6 +6,8 @@ RSpec.describe Ci::JobArtifact do
using RSpec::Parameterized::TableSyntax
include EE::GeoHelpers
it { is_expected.to delegate_method(:validate_schema?).to(:job) }
describe '#destroy' do
let_it_be(:primary) { create(:geo_node, :primary) }
let_it_be(:secondary) { create(:geo_node) }
......@@ -223,7 +225,8 @@ RSpec.describe Ci::JobArtifact do
describe '#security_report' do
let(:job_artifact) { create(:ee_ci_job_artifact, :sast) }
let(:security_report) { job_artifact.security_report }
let(:validate) { false }
let(:security_report) { job_artifact.security_report(validate: validate) }
subject(:findings_count) { security_report.findings.length }
......@@ -248,6 +251,44 @@ RSpec.describe Ci::JobArtifact do
it { is_expected.to be(security_report?) }
end
end
context 'when the parsing fails' do
let(:job_artifact) { create(:ee_ci_job_artifact, :sast) }
let(:errors) { security_report.errors }
before do
allow(::Gitlab::Ci::Parsers).to receive(:fabricate!).and_raise(:foo)
end
it 'returns an errored report instance' do
expect(errors).to eql([{ type: 'ParsingError', message: 'An unexpected error happened!' }])
end
end
describe 'schema validation' do
where(:validate, :build_is_subject_to_validation?, :expected_validate_flag) do
false | false | false
false | true | false
true | false | false
true | true | true
end
with_them do
let(:mock_parser) { double(:parser, parse!: true) }
let(:expected_parser_args) { ['sast', instance_of(String), instance_of(::Gitlab::Ci::Reports::Security::Report), validate: expected_validate_flag] }
before do
allow(job_artifact.job).to receive(:validate_schema?).and_return(build_is_subject_to_validation?)
allow(::Gitlab::Ci::Parsers).to receive(:fabricate!).and_return(mock_parser)
end
it 'calls the parser with the correct arguments' do
security_report
expect(::Gitlab::Ci::Parsers).to have_received(:fabricate!).with(*expected_parser_args)
end
end
end
end
describe '#clear_security_report' do
......
......@@ -44,6 +44,34 @@ RSpec.describe Security::Scan do
it { is_expected.to delegate_method(:name).to(:build) }
end
describe '#has_errors?' do
let(:scan) { build(:security_scan, info: info) }
subject { scan.has_errors? }
context 'when the info attribute is nil' do
let(:info) { nil }
it { is_expected.to be_falsey }
end
context 'when the info attribute presents' do
let(:info) { { errors: errors } }
context 'when there is no error' do
let(:errors) { [] }
it { is_expected.to be_falsey }
end
context 'when there are errors' do
let(:errors) { [{ type: 'Foo', message: 'Bar' }] }
it { is_expected.to be_truthy }
end
end
end
describe '.by_scan_types' do
let!(:sast_scan) { create(:security_scan, scan_type: :sast) }
let!(:dast_scan) { create(:security_scan, scan_type: :dast) }
......
......@@ -136,6 +136,17 @@ RSpec.describe Security::MergeReportsService, '#execute' do
subject(:merged_report) { merge_service.execute }
describe 'errors on target report' do
subject { merged_report.errors }
before do
report_1.add_error('foo', 'bar')
report_2.add_error('zoo', 'baz')
end
it { is_expected.to eq([{ type: 'foo', message: 'bar' }, { type: 'zoo', message: 'baz' }]) }
end
it 'copies scanners into target report and eliminates duplicates' do
expect(merged_report.scanners.values).to contain_exactly(scanner_1, scanner_2, scanner_3)
end
......
......@@ -60,6 +60,25 @@ RSpec.describe Security::StoreGroupedScansService do
allow(Security::StoreScanService).to receive(:execute).and_return(true)
end
context 'schema validation' do
let(:mock_scanner) { instance_double(::Gitlab::Ci::Reports::Security::Scanner, external_id: 'unknown') }
let(:mock_report) { instance_double(::Gitlab::Ci::Reports::Security::Report, primary_scanner: mock_scanner) }
before do
allow(artifact_1).to receive(:security_report).and_return(mock_report)
allow(artifact_2).to receive(:security_report).and_return(mock_report)
allow(artifact_3).to receive(:security_report).and_return(mock_report)
end
it 'accesses the validated security reports' do
store_scan_group
expect(artifact_1).to have_received(:security_report).with(validate: true)
expect(artifact_2).to have_received(:security_report).with(validate: true)
expect(artifact_3).to have_received(:security_report).with(validate: true)
end
end
context 'when the artifacts are not dependency_scanning' do
it 'calls the Security::StoreScanService with ordered artifacts' do
store_scan_group
......
......@@ -57,6 +57,22 @@ RSpec.describe Security::StoreScanService do
known_keys.add(finding_key)
end
context 'when the report has some errors' do
before do
artifact.security_report.errors << { 'type' => 'foo', 'message' => 'bar' }
end
it 'does not call the `Security::StoreFindingsMetadataService` and returns false' do
expect(store_scan).to be(false)
expect(Security::StoreFindingsMetadataService).not_to have_received(:execute)
end
end
context 'when the report does not have any errors' do
before do
artifact.security_report.errors.clear
end
it 'calls the `Security::StoreFindingsMetadataService` to store findings' do
store_scan
......@@ -144,4 +160,5 @@ RSpec.describe Security::StoreScanService do
end
end
end
end
end
......@@ -15,8 +15,8 @@ module Gitlab
}
end
def self.fabricate!(file_type, *args)
parsers.fetch(file_type.to_sym).new(*args)
def self.fabricate!(file_type, *args, **kwargs)
parsers.fetch(file_type.to_sym).new(*args, **kwargs)
rescue KeyError
raise ParserNotFoundError, "Cannot find any parser matching file type '#{file_type}'"
end
......
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