|
| 1 | +import Ajv from 'ajv/dist/jtd.js' |
| 2 | + |
| 3 | +const jtdAjv = new Ajv() |
| 4 | + |
| 5 | +/* |
| 6 | + This is the jtd schema that needs to match the input document so that the |
| 7 | + test is activated. If this schema doesn't match it normally means that the input |
| 8 | + document does not validate against the csaf json schema or optional fields that |
| 9 | + the test checks are not present. |
| 10 | + */ |
| 11 | +const inputSchema = /** @type {const} */ ({ |
| 12 | + additionalProperties: true, |
| 13 | + properties: { |
| 14 | + document: { |
| 15 | + additionalProperties: true, |
| 16 | + properties: { |
| 17 | + category: { |
| 18 | + type: 'string', |
| 19 | + }, |
| 20 | + }, |
| 21 | + }, |
| 22 | + }, |
| 23 | +}) |
| 24 | + |
| 25 | +const validateInput = jtdAjv.compile(inputSchema) |
| 26 | + |
| 27 | +const profileValues = [ |
| 28 | + 'csaf_base', |
| 29 | + 'csaf_security_incident_response', |
| 30 | + 'csaf_informational_advisory', |
| 31 | + 'csaf_security_advisory', |
| 32 | + 'csaf_vex', |
| 33 | + 'csaf_deprecated_security_advisory', |
| 34 | + 'csaf_withdrawn', |
| 35 | + 'csaf_superseded', |
| 36 | +] |
| 37 | +const otherProfileValues = [ |
| 38 | + 'securityincidentresponse', |
| 39 | + 'informationaladvisory', |
| 40 | + 'securityadvisory', |
| 41 | + 'vex', |
| 42 | + 'deprecatedsecurityadvisory', |
| 43 | + 'withdrawn', |
| 44 | + 'superseded', |
| 45 | +] |
| 46 | + |
| 47 | +/** |
| 48 | + * It MUST be tested that the document category is not equal to the (case-insensitive) name (without the prefix csaf_) |
| 49 | + * or value of any other profile than "CSAF Base". Any occurrences of dash, whitespace, and underscore characters are |
| 50 | + * removed from the values on both sides before the match. |
| 51 | + * Also, the value MUST NOT start with the reserved prefix csaf_ except if the value is csaf_base. |
| 52 | + * @param {unknown} doc |
| 53 | + */ |
| 54 | +export function mandatoryTest_6_1_26(doc) { |
| 55 | + const ctx = { |
| 56 | + errors: |
| 57 | + /** @type {Array<{ instancePath: string; message: string }>} */ ([]), |
| 58 | + isValid: true, |
| 59 | + } |
| 60 | + if (!validateInput(doc)) { |
| 61 | + return ctx |
| 62 | + } |
| 63 | + |
| 64 | + /** @type {string} */ |
| 65 | + const category = doc.document.category |
| 66 | + |
| 67 | + // Skip test if profile is not "CSAF Base" but one of the other profiles or matches exactly "csaf_base" |
| 68 | + if (profileValues.includes(category)) { |
| 69 | + return ctx |
| 70 | + } |
| 71 | + |
| 72 | + // Fail on reserved prefix |
| 73 | + if (category.toLowerCase().startsWith('csaf_')) { |
| 74 | + ctx.isValid = false |
| 75 | + ctx.errors.push({ |
| 76 | + instancePath: `/document/category`, |
| 77 | + message: `reserved prefix used`, |
| 78 | + }) |
| 79 | + |
| 80 | + return ctx |
| 81 | + } |
| 82 | + |
| 83 | + // Fail on name similarity |
| 84 | + if ( |
| 85 | + otherProfileValues.includes(category.replace(/[_-\s]+/g, '').toLowerCase()) |
| 86 | + ) { |
| 87 | + ctx.isValid = false |
| 88 | + ctx.errors.push({ |
| 89 | + instancePath: `/document/category`, |
| 90 | + message: `value prohibited`, |
| 91 | + }) |
| 92 | + } |
| 93 | + return ctx |
| 94 | +} |
0 commit comments