Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feat/#197 mandatory tests csaf2 1 6.1.7 #208

Open
wants to merge 11 commits into
base: 196-csaf-2.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion csaf_2_1/mandatoryTests.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export { default as mandatoryTest_6_1_7 } from './mandatoryTests/mandatoryTest_6_1_7.js'
export {
mandatoryTest_6_1_1,
mandatoryTest_6_1_2,
mandatoryTest_6_1_3,
mandatoryTest_6_1_4,
mandatoryTest_6_1_5,
mandatoryTest_6_1_6,
mandatoryTest_6_1_7,
mandatoryTest_6_1_8,
mandatoryTest_6_1_9,
mandatoryTest_6_1_10,
Expand Down
110 changes: 110 additions & 0 deletions csaf_2_1/mandatoryTests/mandatoryTest_6_1_7.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import Ajv from 'ajv/dist/jtd.js'

const jtdAjv = new Ajv()

const inputSchema = /** @type {const} */ ({
additionalProperties: true,
properties: {
vulnerabilities: {
elements: {
additionalProperties: true,
properties: {
metrics: {
elements: {
additionalProperties: true,
optionalProperties: {
cvss_v2: {
additionalProperties: true,
properties: {
version: { type: 'string' },
},
},
cvss_v3: {
additionalProperties: true,
properties: {
version: { type: 'string' },
},
},
cvss_v4: {
additionalProperties: true,
properties: {
version: { type: 'string' },
},
},
},
},
},
},
},
},
},
})

const validate = jtdAjv.compile(inputSchema)

/**
*
* @param {unknown} doc
*/
export default function mandatoryTest_6_1_7(doc) {
const ctx = {
errors:
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
isValid: true,
}

/** @type {Array<{ message: string; instancePath: string }>} */
const errors = []
let isValid = true

if (!validate(doc)) {
return ctx
}

// 6.1.7 Multiple Scores with same Version per Product
/** @type {Array<any>} */
const vulnerabilities = doc.vulnerabilities
vulnerabilities.forEach((vulnerability, vulnerabilityIndex) => {
/** @type {Map<string, Set<string>>} */
const cvssVersionsByProductName = new Map()

/** @type {Array<any>} */
const metrics = vulnerability.metrics
metrics?.forEach((metric, scoreIndex) => {
/** @type {Array<any>} */
const products = metric.products
products?.forEach((product, productIndex) => {
const versionSet = cvssVersionsByProductName.get(product) ?? new Set()
cvssVersionsByProductName.set(product, versionSet)

if (
(metric.content?.cvss_v2?.version !== undefined &&
versionSet.has(metric.content?.cvss_v2.version)) ||
(metric.content?.cvss_v3?.version !== undefined &&
versionSet.has(metric.content?.cvss_v3.version)) ||
(metric.content?.cvss_v4?.version !== undefined &&
versionSet.has(metric.content?.cvss_v4.version))
) {
isValid = false
errors.push({
message: `product is already included in these cvss-versions: ${Array.from(
versionSet.keys()
).join(', ')}`,
instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${scoreIndex}/products/${productIndex}`,
})
}
if (metric.content?.cvss_v2?.version !== undefined) {
versionSet.add(metric.content?.cvss_v2.version)
}
if (metric.content?.cvss_v3?.version !== undefined) {
versionSet.add(metric.content?.cvss_v3.version)
}
if (metric.content?.cvss_v4?.version !== undefined) {
versionSet.add(metric.content?.cvss_v4.version)
}
})
})
})

return { errors, isValid }
}
158 changes: 158 additions & 0 deletions tests/csaf_2_1/mandatoryTest_6_1_7.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { expect } from 'chai'
import mandatoryTest_6_1_7 from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_7.js'
import minimalDoc from './shared/minimalDoc.js'
import csaf_2_1 from '../../csaf_2_1/schemaTests/csaf_2_1.js'

const emptyMandatoryTest6_1_7 = {
$schema: minimalDoc.$schema,
document: {
...minimalDoc.document,
},
product_tree: {
full_product_names: [
{
product_id: 'CSAFPID-9080700',
name: 'Product A',
},
],
},
vulnerabilities: [
{
metrics: [
{
content: {
cvss_v3: {},
},
products: ['CSAFPID-9080700'],
},
],
},
{
metrics: [
{
content: {},
products: [],
},
],
},
],
}

const invalidMandatoryTest6_1_7 = {
$schema: minimalDoc.$schema,
document: {
...minimalDoc.document,
},
product_tree: {
full_product_names: [
{
product_id: 'CSAFPID-9080700',
name: 'Product A',
},
],
},
vulnerabilities: [
{
metrics: [
{
content: {
cvss_v4: {
version: '4.0',
vectorString:
'CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H',
baseScore: 10,
baseSeverity: 'CRITICAL',
},
},
products: ['CSAFPID-9080700'],
},
{
content: {
cvss_v4: {
version: '4.0',
vectorString:
'CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:H/SI:N/SA:N',
baseScore: 4.9,
baseSeverity: 'MEDIUM',
},
},
products: ['CSAFPID-9080700'],
},
],
},
],
}

const validMandatoryTest6_1_7 = {
$schema: minimalDoc.$schema,
document: {
...minimalDoc.document,
},
product_tree: {
full_product_names: [
{
product_id: 'CSAFPID-9080700',
name: 'Product A',
},
],
},
vulnerabilities: [
{
metrics: [
{
content: {
cvss_v3: {
version: '3.1',
vectorString: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H',
baseScore: 10,
baseSeverity: 'CRITICAL',
},
},
products: ['CSAFPID-9080700'],
},
],
},
{
metrics: [
{
content: {
cvss_v3: {
version: '3.1',
vectorString: 'CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H',
baseScore: 6.5,
baseSeverity: 'MEDIUM',
},
},
products: ['CSAFPID-9080700'],
},
],
},
],
}

describe('mandatory test 6.1.7', function () {
describe('failing examples', function () {
it('test duplicate product id in cvss_v3 ', async function () {
expect(csaf_2_1(invalidMandatoryTest6_1_7).isValid).to.be.true
const result = await mandatoryTest_6_1_7(invalidMandatoryTest6_1_7)
expect(result.errors).to.have.length.greaterThan(0)
})
})

describe('valid examples', function () {
it('test duplicate product id different vulnerabilities', async function () {
expect(csaf_2_1(validMandatoryTest6_1_7).isValid).to.be.true
const result = await mandatoryTest_6_1_7(validMandatoryTest6_1_7)
expect(result.errors.length).to.eq(0)
})
it('test empty vulnerabilities', async function () {
const result = await mandatoryTest_6_1_7(emptyMandatoryTest6_1_7)
expect(result.errors.length).to.eq(0)
})
it('test minimal doc', async function () {
expect(csaf_2_1(minimalDoc).isValid).to.be.true
const result = await mandatoryTest_6_1_7(minimalDoc)
expect(result.errors.length).to.eq(0)
})
})
})
1 change: 0 additions & 1 deletion tests/csaf_2_1/oasis.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import * as mandatory from '../../csaf_2_1/mandatoryTests.js'
Once all tests are implemented for CSAF 2.1 this should be deleted.
*/
const excluded = [
'6.1.7',
'6.1.8',
'6.1.9',
'6.1.10',
Expand Down
40 changes: 40 additions & 0 deletions tests/csaf_2_1/shared/minimalDoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export default {
$schema: 'https://docs.oasis-open.org/csaf/csaf/v2.1/csaf_json_schema.json',
document: {
category: 'Test Report',
csaf_version: '2.1',
title: 'Minimal valid',
lang: 'en',
distribution: {
tlp: {
label: 'AMBER',
},
},
publisher: {
category: 'other',
name: 'Secvisogram Automated Tester',
namespace: 'https://github.com/secvisogram/secvisogram',
},
references: [
{
category: 'self',
summary: 'A non-canonical URL.',
url: 'https://example.com/security/data/csaf/2021/my-thing-_10.json',
},
],
tracking: {
current_release_date: '2021-01-14T00:00:00.000Z',
id: 'My-Thing-.10',
initial_release_date: '2021-01-14T00:00:00.000Z',
revision_history: [
{
number: '1',
date: '2021-01-14T00:00:00.000Z',
summary: 'Summary',
},
],
status: 'draft',
version: '1',
},
},
}