Skip to content
Draft
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,6 @@ The following tests are not yet implemented and therefore missing:
- Recommended Test 6.2.50.1
- Recommended Test 6.2.50.2
- Recommended Test 6.2.50.3
- Recommended Test 6.2.51
- Recommended Test 6.2.54.1
- Recommended Test 6.2.54.2
- Recommended Test 6.2.54.4
Expand Down Expand Up @@ -535,6 +534,7 @@ export const recommendedTest_6_2_45: DocumentTest
export const recommendedTest_6_2_47: DocumentTest
export const recommendedTest_6_2_48: DocumentTest
export const recommendedTest_6_2_49: DocumentTest
export const recommendedTest_6_2_51: DocumentTest
export const recommendedTest_6_2_52: DocumentTest
export const recommendedTest_6_2_53: DocumentTest
export const recommendedTest_6_2_54_3: DocumentTest
Expand Down
1 change: 1 addition & 0 deletions csaf_2_1/recommendedTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export { recommendedTest_6_2_45 } from './recommendedTests/recommendedTest_6_2_4
export { recommendedTest_6_2_47 } from './recommendedTests/recommendedTest_6_2_47.js'
export { recommendedTest_6_2_48 } from './recommendedTests/recommendedTest_6_2_48.js'
export { recommendedTest_6_2_49 } from './recommendedTests/recommendedTest_6_2_49.js'
export { recommendedTest_6_2_51 } from './recommendedTests/recommendedTest_6_2_51.js'
export { recommendedTest_6_2_52 } from './recommendedTests/recommendedTest_6_2_52.js'
export { recommendedTest_6_2_53 } from './recommendedTests/recommendedTest_6_2_53.js'
export { recommendedTest_6_2_54_3 } from './recommendedTests/recommendedTest_6_2_54_3.js'
Expand Down
107 changes: 107 additions & 0 deletions csaf_2_1/recommendedTests/recommendedTest_6_2_51.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Ajv } from 'ajv/dist/jtd.js'
import { walkPath } from '../../lib/walkPaths.js'
import {
initVers,
isSupportedVersScheme,
} from '../shared/productVersionRangeHelper/vers.js'

// Regex to extracts the versioning type with the format `vers:<type>/<constraints>`.
const VERS_PREFIX = new RegExp('^vers:([a-z\\.\\-\\+][a-z0-9\\.\\-\\+]*)/.+')

const ajv = new Ajv()

const branchSchema = /** @type {const} */ ({
additionalProperties: true,
optionalProperties: {
category: { type: 'string' },
name: { type: 'string' },
},
})

const validateBranch = ajv.compile(branchSchema)

const inputSchema = /** @type {const} */ ({
additionalProperties: true,
properties: {
product_tree: {
additionalProperties: true,
optionalProperties: {
branches: { elements: branchSchema },
},
},
},
})

const validateInput = ajv.compile(inputSchema)

// VERS types registered in the vers specification (docs/types/vers-types.md)
const REGISTERED_VERS_TYPES = new Set([
'alpm',
'apk',
'cargo',
'composer',
'cpan',
'dart',
'deb',
'gem',
'golang',
'hackage',
'maven',
'nuget',
'npm',
'pypi',
'rpm',
'all',
'datetime',
'generic',
'intdot',
'lexicographic',
'none',
'semver',
])

/**
* This implements the recommended test 6.2.51 of the CSAF 2.1 standard.
*
* @param {unknown} doc
*/
export async function recommendedTest_6_2_51(doc) {
const ctx = {
warnings:
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
}

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

await initVers()

await walkPath(
doc,
'/product_tree/branches[*]',
async (instancePath, branch) => {
if (!validateBranch(branch)) return
if (branch.category !== 'product_version_range') return
if (typeof branch.name !== 'string') return

const match = branch.name.trim().match(VERS_PREFIX)
const type = match?.[1]?.toLowerCase()
if (!type) return

if (!REGISTERED_VERS_TYPES.has(type)) {
ctx.warnings.push({
instancePath: `${instancePath}/name`,
message: `usage of unknown version type \`${type}\` (not registered, not supported)`,
})
} else if (!isSupportedVersScheme(branch.name)) {
ctx.warnings.push({
instancePath: `${instancePath}/name`,
message: `usage of unknown version type \`${type}\` (registered, not supported)`,
})
}
}
)

return ctx
}
33 changes: 33 additions & 0 deletions csaf_2_1/shared/productVersionRangeHelper/vers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/** @type {Promise<typeof import('@csaf-rs/vers-rs')> | undefined} */
let loading

/** @type {typeof import('@csaf-rs/vers-rs') | undefined} */
let versRs

/**
* Loads the WebAssembly module of vers-rs. Must be awaited once before
* `isSupportedVersScheme` is used.
*
* @returns {Promise<void>}
*/
export async function initVers() {
loading ??= import('@csaf-rs/vers-rs')
versRs = await loading
}

/**
* Checks whether the versioning scheme of a product version range in the
* VERS syntax is supported by vers-rs. Syntax errors in the constraints do not
* make the scheme unsupported.
*
* @param {string} range
* @returns {boolean}
*/
export function isSupportedVersScheme(range) {
try {
versRs?.parse(range.trim())
return true
} catch (e) {
return !String(e).includes('Unsupported versioning scheme')
}
}
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"access": "public"
},
"dependencies": {
"@csaf-rs/vers-rs": "^0.1.2",
"@secvisogram/is-leap-second": "^1.0.0",
"@secvisogram/license-deprecation-list": "^1.0.1",
"ae-cvss-calculator": "^1.0.13",
Expand Down Expand Up @@ -157,6 +158,7 @@
"playwright": "^1.62.1",
"prettier": "^2.8.1",
"typescript": "^6.0.3",
"vite-plugin-wasm": "^3.6.0",
"vitest": "^4.1.10",
"xml2js": "^0.5.0"
},
Expand Down
1 change: 0 additions & 1 deletion tests/csaf_2_1/oasis.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
'6.2.50.1',
'6.2.50.2',
'6.2.50.3',
'6.2.51',
'6.2.54.1',
'6.2.54.2',
'6.2.54.4',
Expand Down Expand Up @@ -224,7 +223,7 @@
type === TYPE_FAILURES
? 'should have warnings'
: `should not have warnings, but had ${primaryExecutionResult.warnings?.length}`
).to.equal(type === TYPE_FAILURES)

Check failure on line 226 in tests/csaf_2_1/oasis.js

View workflow job for this annotation

GitHub Actions / test-browser

[browser (chromium)] tests/csaf_2_1/oasis.js > recommended > 6.2.51 > valid > recommended/oasis_csaf_tc-csaf_2_1-2024-6-2-51-12.json

AssertionError: should not have warnings, but had 1: expected true to equal false - Expected + Received - false + true ❯ tests/csaf_2_1/oasis.js:226:25

Check failure on line 226 in tests/csaf_2_1/oasis.js

View workflow job for this annotation

GitHub Actions / test-browser

[browser (chromium)] tests/csaf_2_1/oasis.js > recommended > 6.2.51 > valid > recommended/oasis_csaf_tc-csaf_2_1-2024-6-2-51-11.json

AssertionError: should not have warnings, but had 1: expected true to equal false - Expected + Received - false + true ❯ tests/csaf_2_1/oasis.js:226:25
} else if (group === 'informative') {
expect(
Boolean(primaryExecutionResult.infos?.length),
Expand Down
54 changes: 54 additions & 0 deletions tests/csaf_2_1/recommendedTest_6_2_51.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { recommendedTest_6_2_51 } from '../../csaf_2_1/recommendedTests/recommendedTest_6_2_51.js'

/** @param {string} name */
const docWith = (name) => ({
product_tree: {
branches: [
{
category: 'product_version_range',
name,
},
],
},
})

describe('recommendedTest_6_2_51', function () {
it('skips invalid child branches that do not pass schema validation', async function () {
const result = await recommendedTest_6_2_51({
product_tree: { branches: [42, null] },
})
expect(result.warnings.length).to.equal(0)
})

it('does not warn for a supported VERS type', async function () {
const result = await recommendedTest_6_2_51(
docWith('vers:npm/>1.2.0|<3.91.1')
)
expect(result.warnings.length).to.equal(0)
})

it('does not warn for a supported VERS type with invalid constraints', async function () {
const result = await recommendedTest_6_2_51(docWith('vers:npm/>1.2'))
expect(result.warnings.length).to.equal(0)
})

it('warns about a VERS type that is not registered', async function () {
const result = await recommendedTest_6_2_51(docWith('vers:alpm/<4.2.0'))
expect(result.warnings).to.eql([
{
instancePath: '/product_tree/branches/0/name',
message:
'usage of unknown version type `alpm` (registered, not supported)',
},
])
})

it('ignores branches that are not product version ranges', async function () {
const result = await recommendedTest_6_2_51({
product_tree: {
branches: [{ category: 'product_version', name: 'vers:unknown/1' }],
},
})
expect(result.warnings.length).to.equal(0)
})
})
8 changes: 8 additions & 0 deletions vitest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'
import { playwright } from '@vitest/browser-playwright'
import wasm from 'vite-plugin-wasm'

// The OASIS fixture-driven tests (tests/oasis.js, tests/csaf_2_1/oasis.js) run
// informativeTest_6_3_8 without a hunspell mock, so they exercise the real
Expand All @@ -26,6 +27,13 @@ export default defineConfig({
},
},
{
// vers-rs is a WebAssembly ES module, which vite only handles with a plugin
plugins: [
/** @type {() => import('vite').Plugin} */ (
/** @type {unknown} */ (wasm)
)(),
],
optimizeDeps: { exclude: ['@csaf-rs/vers-rs'] },
test: {
name: 'browser',
globals: true,
Expand Down
Loading