Skip to content

Latest commit

 

History

History
845 lines (701 loc) · 26.5 KB

File metadata and controls

845 lines (701 loc) · 26.5 KB

Custom validation

In addition to validating the the BOM input file using the standard CycloneDX schema, you now can provide a custom JSON file that will apply a built-in set of validation functions to selected parts of the BOM document that can validate JSON elements, property keys and values.

Check functions

The current set of functions that can achieve this includes:

  • isUnique - checks uniqueness of array items given a property name as key
  • hasProperties - can verify that a named property exists on a selected JSON element and can also enforce the corresponding property has the expected value using regex.

Note: More functions are planned for future releases if use cases are found.

Usage

The minimum set of required command flags to invoke custom validation using the utility's validate command would be:

./sbom-utility validate -i <input-bom.json> --custom <custom-validation-config.json>

Examples by function

Examples are provided for each custom validation function or "check":

  • isUnique examples - used to validate array item uniqueness.
  • hasProperties examples - used to validate that a selected JSON object has specified properties.
  • Combined examples - shows how to combine the isUnique function with the hasProperties function on the same selected JSON array.

Examples by use case


isUnique examples

The isUnique function can be used to validate that an item in a JSON array is "unique" using a specified primaryKey property. The primaryKey property specifies the JSON map key used as the primary key for for items in the array and its value tested for uniqueness.

Example: Valid: property is unique in the BOM's properties array

The custom validation configuration file test/custom/config-cdx-bom-properties-unique.json with contents::

{
  "validation": {
    "actions": [
      {
        "id": "custom-bom-properties-unique",
        "description": "Validate BOM properties unique",
        "selector": {
          "path": "properties",
          "primaryKey": {
            "key": "name",
            "value": "yyz"
          }
        },
        "functions": [
          "isUnique"
        ]
      }
    ]
  }
}

provides a path value as part of the selector object which is used to locate the BOM's top-level properties array which contains the property items that will be validated for uniqueness.

As each item of the selected JSON array is itself a JSON map object, the primaryKey of the selector is used to identify the map key (i.e., name in this case) and specified value (i.e., yyz) for to test for uniqueness within the array.

When this example's custom validation configuration is applied to the test CycloneDX BOM file: test/custom/cdx-1-6-test-bom-properties.json with contents:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "metadata": {
    "timestamp": "2025-08-09T07:20:00.000Z",
    "component": {
      "name": "sample app",
      "type": "application"
    }
  },
  "properties": [
    {
      "name": "yyz",
      "value": "rush"
    },
    {
      "name": "foo",
      "value": "bar1"
    },
    {
      "name": "foo",
      "value": "bar2"
    }
  ]
}

by running it from the command line:

./sbom-utility validate -i test/custom/cdx-1-6-test-bom-properties.json --custom test/custom/config-cdx-bom-properties-unique.json

produces the following result:

[INFO] Validating 'test/custom/cdx-1-6-test-bom-properties.json'...
[INFO] BOM valid against JSON schema: 'true'
[INFO] Loading custom validation config file: 'test/custom/config-cdx-bom-properties-unique.json'...
[INFO] Validating custom action (id: `custom-bom-properties-unique`, selector: `{"path": "properties", "primaryKey": {"key": "name", "value": "yyz"}}`)...
[INFO] >> Checking isUnique: selector: `{"path": "properties", "primaryKey": {"key": "name", "value": "yyz"}}`...
[INFO] BOM valid against custom JSON configuration: 'test/custom/config-cdx-bom-properties-unique.json': 'true'

As you can see, the standard schema validation is first applied and returns "BOM valid against JSON schema: 'true'" then the custom checks are applied which also returns "BOM valid against custom JSON configuration: true" with the details of each check provided.

The validate command factors in the custom validation along with the normal schema validation when setting the exit code (i.e., 0, zero in this valid case). This preserves the ability to test exit code from the command line and within test scripts:

$ echo $?
0

Example: Invalid: property item is not unique in BOM properties array

Using the custom validation configuration file test/custom/config-cdx-bom-properties-not-unique.json with contents::

{
  "validation": {
    "actions": [
      {
        "id": "custom-bom-properties-not-unique",
        "description": "Validate BOM properties not unique",
        "selector": {
          "path": "properties",
          "primaryKey": {
            "key": "name",
            "value": "foo"
          }
        },
        "functions": [
          "isUnique"
        ]
      }
    ]
  }
}

and applying it to the test CycloneDX BOM file: test/custom/cdx-1-6-test-bom-properties.json:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "metadata": {
    "timestamp": "2025-08-09T07:20:00.000Z",
    "component": {
      "name": "sample app",
      "type": "application"
    }
  },
  "properties": [
    {
      "name": "yyz",
      "value": "rush"
    },
    {
      "name": "foo",
      "value": "bar1"
    },
    {
      "name": "foo",
      "value": "bar2"
    }
  ]
}

by running it from the command line:

./sbom-utility validate -i test/custom/cdx-1-6-test-bom-properties.json --custom test/custom/config-cdx-bom-properties-not-unique.json

produces the following result:

[INFO] Validating 'test/custom/cdx-1-6-test-bom-properties.json'...
[INFO] BOM valid against JSON schema: 'true'
[INFO] Loading custom validation config file: 'test/custom/config-cdx-bom-properties-not-unique.json'...
[INFO] Validating custom action (id: `custom-bom-properties-not-unique`, selector: `{"path": "properties", "primaryKey": {"key": "name", "value": "foo"}}`)...
[INFO] >> Checking isUnique: selector: `{"path": "properties", "primaryKey": {"key": "name", "value": "foo"}}`...
[INFO] BOM valid against custom JSON configuration: 'test/custom/config-cdx-bom-properties-not-unique.json': 'false'
[ERROR] invalid SBOM: custom validation failed: Function: 'isUnique', selector: {"path": "properties", "primaryKey": {"key": "name", "value": "foo"}}, matches found: 2, bom: (test/custom/cdx-1-6-test-bom-properties.json)
[INFO] document 'test/custom/cdx-1-6-test-bom-properties.json': valid=[false]

which indicates the property designated as the "primary key" (i.e., the name key with value foo) resulted in multiple (i.e., two (2)) items and therefore not unique.

In other words, there were two CycloneDX property items in the array that have the foo value in the name key which was designated as the primaryKey.

In this invalid example, the exit code will reflect the custom validation failure with a non-zero exit code:

echo $?
2

Example: Invalid: primaryKey not found

Using the custom validation configuration file test/custom/config-cdx-bom-properties-primary-key-missing.json with contents::

{
  "validation": {
    "actions": [
      {
        "id": "custom-bom-properties-primary-key-missing",
        "description": "Validate BOM properties primary key is missing",
        "selector": {
          "path": "properties",
          "primaryKey": {
            "key": "kwarg",
            "value": "baz"
          }
        },
        "functions": [
          "isUnique"
        ]
      }
    ]
  }
}

and applying it to the test CycloneDX BOM file: test/custom/cdx-1-6-test-bom-properties.json:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "metadata": {
    "timestamp": "2025-08-09T07:20:00.000Z",
    "component": {
      "name": "sample app",
      "type": "application"
    }
  },
  "properties": [
    {
      "name": "yyz",
      "value": "rush"
    },
    {
      "name": "foo",
      "value": "bar1"
    },
    {
      "name": "foo",
      "value": "bar2"
    }
  ]
}

by running it from the command line:

./sbom-utility validate -i test/custom/cdx-1-6-test-bom-properties.json --custom test/custom/config-cdx-bom-properties-primary-key-missing.json

produces the following result:

[INFO] Validating 'test/custom/cdx-1-6-test-bom-properties.json'...
[INFO] BOM valid against JSON schema: 'true'
[INFO] Loading custom validation config file: 'test/custom/config-cdx-bom-properties-primary-key-missing.json'...
[INFO] Validating custom action (id: `custom-bom-properties-primary-key-missing`, selector: `{"path": "properties", "primaryKey": {"key": "kwarg", "value": "baz"}}`)...
[ERROR] invalid query: key `kwarg` not found in object map: {
    "Key": "kwarg",
    "Operand": "=",
    "Value": "baz",
    "ValueRegEx": "baz"
}
[INFO] BOM valid against custom JSON configuration: 'test/custom/config-cdx-bom-properties-primary-key-missing.json': 'false'
[ERROR] invalid SBOM: invalid query: invalid selector into JSON document: {"path": "properties", "primaryKey": {"key": "kwarg", "value": "baz"}}, bom: (test/custom/cdx-1-6-test-bom-properties.json)
[INFO] document 'test/custom/cdx-1-6-test-bom-properties.json': valid=[false]

which indicates the primaryKey selector with key set to "kwarg" is not found in the BOM's metadata.properties array.


hasProperties examples

The hasProperties function can be used to validate that specific properties (i.e., key-value pairs) are present in a selected object within the BOM document.

Example: Valid: metadata has timestamp, supplier, component and licenses properties

Using the custom validation configuration file test/custom/config-cdx-metadata-elements-found.json with contents::

{
  "validation": {
    "actions": [
      {
        "id": "custom-test-metadata-has-elements",
        "description": "Test the 'metadata' element contains required child elements which are a mix of primitives and complex types.",
        "selector": {
          "path": "metadata"
        },
        "functions": [
          "hasProperties"
        ],
        "properties": [
          {
            "key": "timestamp"
          },
          {
            "key": "supplier"
          },
          {
            "key": "component"
          },
          {
            "key": "licenses"
          }
        ]
      }
    ]
  }
}

and applying it to the test CycloneDX BOM file: test/custom/cdx-1-6-test-bom-metadata.json:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "version": 1,
  "serialNumber": "urn:uuid:1a2b3c4d-1234-abcd-9876-a3b4c5d6e7e0",
  "metadata": {
    "timestamp": "2025-08-09T07:20:00.000Z",
    "component": {
      "name": "sample app",
      "type": "application"
    },
    "licenses": [
      {
        "license": {
          "id": "Apache-2.0"
        }
      }
    ],
    "supplier": {
      "name": "Example.com",
      "url": [
        "https://example.com"
      ],
      "contact": [
        {
          "email": "info@example.com"
        }
      ]
    }
  }
}

by running it from the command line:

./sbom-utility validate -i test/custom/cdx-1-6-test-bom-metadata.json --custom test/custom/config-cdx-metadata-elements-found.json

produces the following result:

[INFO] Validating 'test/custom/cdx-1-6-test-bom-metadata.json'...
[INFO] BOM valid against JSON schema: 'true'
[INFO] Loading custom validation config file: 'test/custom/config-cdx-metadata-elements-found.json'...
[INFO] Validating custom action (id: `custom-metadata-elements-found`, selector: `{"path": "metadata", "primaryKey": {"key": "", "value": ""}}`)...
[INFO] >> Checking hasProperties: selector: `{"path": "metadata", "primaryKey": {"key": "", "value": ""}}`, properties: '[{"key": "timestamp", "value": ""},{"key": "supplier", "value": ""},{"key": "component", "value": ""},{"key": "licenses", "value": ""},]'...
[INFO] BOM valid against custom JSON configuration: 'test/custom/config-cdx-metadata-elements-found.json': 'true'

This indicates all four properties exist in the BOM where they are expected in the BOM metadata object (i.e., timestamp, supplier, component and licenses).

and the exit code aligns with the logged output:

$ echo $?
0

Example: Invalid: metadata missing authors element

Using the custom validation configuration file test/custom/config-cdx-metadata-elements-not-found.json with contents::

{
  "validation": {
    "actions": [
      {
        "id": "custom-test-metadata-property-not-found",
        "description": "Test the error if metadata property is not found",
        "selector": {
          "path": "metadata"
        },
        "functions": [
          "hasProperties"
        ],
        "properties": [
          {
            "key": "authors"
          }
        ]
      }
    ]
  }
}

and applying it to the test CycloneDX BOM file: test/custom/cdx-1-6-test-bom-metadata.json whose metadata contains many elements, but not authors:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "version": 1,
  "serialNumber": "urn:uuid:1a2b3c4d-1234-abcd-9876-a3b4c5d6e7e0",
  "metadata": {
    "timestamp": "2025-08-09T07:20:00.000Z",
    "component": {
      "name": "sample app",
      "type": "application"
    },
    "licenses": [
      {
        ...
      }
    ],
    "supplier": {
      ...
    }
  }
}

by running it from the command line:

./sbom-utility validate -i test/custom/cdx-1-6-test-bom-metadata.json --custom test/custom/config-cdx-metadata-elements-not-found.json

produces the following error result since no authors property was found:

[INFO] Validating 'test/custom/cdx-1-6-test-bom-metadata.json'...
[INFO] BOM valid against JSON schema: 'true'
[INFO] Loading custom validation config file: 'test/custom/config-cdx-metadata-elements-not-found.json'...
[INFO] Validating custom action (id: `custom-metadata-elements-not-found`, selector: `{"path": "metadata", "primaryKey": {"key": "", "value": ""}}`)...
[INFO] >> Checking hasProperties: selector: `{"path": "metadata", "primaryKey": {"key": "", "value": ""}}`, properties: '[{"key": "authors", "value": ""},]'...
[INFO] BOM valid against custom JSON configuration: 'test/custom/config-cdx-metadata-elements-not-found.json': 'false'
[ERROR] invalid SBOM: custom validation failed: Function: 'hasProperties' selector: {"path": "metadata", "primaryKey": {"key": "", "value": ""}}, property: {"key": "authors", "value": ""}, bom: (test/custom/cdx-1-6-test-bom-metadata.json)
[INFO] document 'test/custom/cdx-1-6-test-bom-metadata.json': valid=[false]

As expected, the exit code reflects the failed result:

$ echo $?
2

Example: metadata.component has a purl key with a valid OCI container prefix

Using the custom validation configuration file test/custom/config-cdx-metadata-component-purl-oci.json with contents::

{
  "validation": {
    "actions": [
      {
        "id": "custom-metadata-component-purl-oci",
        "description": "Test the 'metadata.component' element contains a Package URL (PURL) with an OCI container prefix.",
        "selector": {
          "path": "metadata.component"
        },
        "functions": [
          "hasProperties"
        ],
        "properties": [
          {
            "key": "purl",
            "value": "^pkg:oci"
          }
        ]
      }
    ]
  }
}

and applying it to the test CycloneDX BOM file: test/custom/cdx-1-6-test-bom-metadata.json:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  ...
  "metadata": {
    "timestamp": "2025-08-09T07:20:00.000Z",
    "component": {
      "name": "sample app",
      "type": "container",
      "purl": "pkg:oci/app/sample@2.0.0"
    },
    "licenses": [
      ...
    ],
    "supplier": {
      ...
    }
  }
}

by running it from the command line:

./sbom-utility validate -i test/custom/cdx-1-6-test-bom-metadata.json --custom test/custom/config-cdx-metadata-component-purl-oci.json

produces the following result:

[INFO] Validating 'test/custom/cdx-1-6-test-bom-metadata.json'...
[INFO] BOM valid against JSON schema: 'true'
[INFO] Loading custom validation config file: 'test/custom/config-cdx-metadata-component-purl-oci.json'...
[INFO] Validating custom action (id: `custom-metadata-component-purl-oci`, selector: `{"path": "metadata.component", "primaryKey": {"key": "", "value": ""}}`)...
[INFO] >> Checking hasProperties: selector: `{"path": "metadata.component", "primaryKey": {"key": "", "value": ""}}`, properties: '[{"key": "purl", "value": "^pkg:oci"},]'...
[INFO] BOM valid against custom JSON configuration: 'test/custom/config-cdx-metadata-component-purl-oci.json': 'true'

Combined examples

These examples perform both a isUnique validation and then further inspec the unique item to validate its other properties (i.e., key-value pairs) using the hasProperties function.

Example: Valid: BOM properties has a unique name property and associated value that matches regex

Using the custom validation configuration file test/custom/config-cdx-bom-properties-unique-match.json with contents::

{
  "validation": {
    "actions": [
      {
        "id": "custom-bom-properties-unique-match",
        "description": "Validate BOM named property is unique and has the specific value",
        "selector": {
          "path": "properties",
          "primaryKey": {
            "key": "name",
            "value": "yyz"
          }
        },
        "functions": [
          "isUnique", "hasProperties"
        ],
        "properties": [
          {
            "key": "value",
            "value": "rush"
          }
        ]
      }
    ]
  }
}

and applying it to the test CycloneDX BOM file: test/custom/cdx-1-6-test-bom-properties.json:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "metadata": {
    "timestamp": "2025-08-09T07:20:00.000Z",
    "component": {
      "name": "sample app",
      "type": "application"
    }
  },
  "properties": [
    {
      "name": "yyz",
      "value": "rush"
    },
    {
      "name": "foo",
      "value": "bar1"
    },
    {
      "name": "foo",
      "value": "bar2"
    }
  ]
}

by running it from the command line:

./sbom-utility validate -i test/custom/cdx-1-6-test-bom-properties.json --custom test/custom/config-cdx-bom-properties-unique-match.json

produces the following result:

[INFO] Validating 'test/custom/cdx-1-6-test-bom-properties.json'...
[INFO] BOM valid against JSON schema: 'true'
[INFO] Loading custom validation config file: 'test/custom/config-cdx-bom-properties-unique-match.json'...
[INFO] Validating custom action (id: `custom-bom-properties-unique-match`, selector: `{"path": "properties", "primaryKey": {"key": "name", "value": "yyz"}}`)...
[INFO] >> Checking isUnique: selector: `{"path": "properties", "primaryKey": {"key": "name", "value": "yyz"}}`...
[INFO] >> Checking hasProperties: selector: `{"path": "properties", "primaryKey": {"key": "name", "value": "yyz"}}`, properties: '[{"key": "value", "value": "rush"},]'...
[INFO] BOM valid against custom JSON configuration: 'test/custom/config-cdx-bom-properties-unique-match.json': 'true'

Use case examples

This section shows some additional use cases that are based on real-world BOM requirements.

BOM Disclaimer and Classification

The BOM metadata has a properties array that allows organizations to add their own custom, property key-value pairs for legal and or classification purposes and validate for them.

Example: Validate "disclaimer" property with name key is unique in the metadata.properties array

Using the custom validation configuration file test/custom/config-cdx-metadata-properties-disclaimer-unique.json with contents::

{
  "validation": {
    "actions": [
      {
        "id": "custom-metadata-properties-disclaimer-unique",
        "description": "Validate BOM metadata properties has a unique disclaimer value.",
        "selector": {
          "path": "metadata.properties",
          "primaryKey": {
            "key": "name",
            "value": "urn:example.com:disclaimer"
          }
        },
        "functions": [
          "isUnique"
        ]
      }
    ]
  }
}

The path value of the selector object is set to metadata.properties and will be used to locate the JSON array that holds the property items. As each item is a JSON map object, the primaryKey can be used to identify the map key (in this case the name map key) and its value (i.e., urn:example.com:disclaimer) used to identify the specific key value to validate as unique within the array.

When the custom validation configuration (above) is applied to the test CycloneDX BOM file: test/custom/cdx-1-6-test-metedata-properties-disclaimer.json with contents:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "metadata": {
    "timestamp": "2025-08-09T07:20:00.000Z",
    "component": {
      "type": "application",
      "name": "sample app"
    },
    "properties": [
      {
        "name": "urn:example.com:disclaimer",
        "value": "This SBOM is current as of date of the software component's release."
      },
      {
        "name": "urn:example.com:classification",
        "value": "This SBOM is Confidential Information. Do not distribute."
      }
    ]
  }
}

by running it from the command line:

./sbom-utility validate -i test/custom/cdx-1-6-test-metedata-properties-disclaimer.json --custom test/custom/config-cdx-metadata-properties-disclaimer-unique.json

it produces the following result:

[INFO] Validating 'test/custom/cdx-1-6-test-metedata-properties-disclaimer.json'...
[INFO] BOM valid against JSON schema: 'true'
[INFO] Loading custom validation config file: 'test/custom/config-cdx-metadata-properties-disclaimer-unique.json'...
[INFO] Validating custom action (id: `custom-metadata-properties-disclaimer-unique`, selector: `{"path": "metadata.properties", "primaryKey": {"key": "name", "value": "urn:example.com:disclaimer"}}`)...
[INFO] >> Checking isUnique: selector: `{"path": "metadata.properties", "primaryKey": {"key": "name", "value": "urn:example.com:disclaimer"}}`...
[INFO] BOM valid against custom JSON configuration: 'test/custom/config-cdx-metadata-properties-disclaimer-unique.json': 'true'

As you can see, the standard schema validation is first applied and returns "BOM valid against JSON schema: 'true'" then the custom checks are applied which also returns "BOM valid against custom JSON configuration" with the details of each check provided.

The validate command factors in the custom validation along with the normal schema validation when setting the exit code (i.e., 0, zero in this valid case). This preserves the ability to test exit code from the command line and within test scripts:

$ echo $?
0

Example: unique disclaimer and value property matches

This example builds upon the last example additionally validate that the unique property with name key equal to urn:example.com:disclaimer also has a value that matches a specific value.

Using the custom validation configuration file test/custom/config-cdx-metadata-properties-disclaimer-unique-match.json with contents::

{
  "validation": {
    "actions": [
      {
        "id": "custom-metadata-properties-disclaimer-unique-match",
        "description": "Validate BOM metadata properties has a unique disclaimer property and specified value.",
        "selector": {
          "path": "metadata.properties",
          "primaryKey": {
            "key": "name",
            "value": "urn:example.com:disclaimer"
          }
        },
        "functions": [
          "isUnique", "hasProperties"
        ],
        "properties": [
          {
            "key": "value",
            "value": "This SBOM is current as of the date it was generated and is subject to change."
          }
        ]
      }
    ]
  }
}

and applying it to the test CycloneDX BOM file: test/custom/cdx-1-6-test-metedata-properties-disclaimer.json:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "metadata": {
    "timestamp": "2025-08-09T07:20:00.000Z",
    "component": {
      "type": "application",
      "name": "sample app"
    },
    "properties": [
      {
        "name": "urn:example.com:disclaimer",
        "value": "This SBOM is current as of the date it was generated and is subject to change."
      },
      {
        "name": "urn:example.com:classification",
        "value": "This SBOM is Confidential Information. Do not distribute."
      }
    ]
  }
}

by running it from the command line:

./sbom-utility validate -i test/custom/cdx-1-6-test-metedata-properties-disclaimer.json --custom test/custom/config-cdx-metadata-properties-disclaimer-unique-match.json

produces the following result:

[INFO] Validating 'test/custom/cdx-1-6-test-metedata-properties-disclaimer.json'...
[INFO] BOM valid against JSON schema: 'true'
[INFO] Loading custom validation config file: 'test/custom/config-cdx-metadata-properties-disclaimer-unique-match.json'...
[INFO] Validating custom action (id: `custom-metadata-properties-disclaimer-unique-match`, selector: `{"path": "metadata.properties", "primaryKey": {"key": "name", "value": "urn:example.com:disclaimer"}}`)...
[INFO] >> Checking isUnique: selector: `{"path": "metadata.properties", "primaryKey": {"key": "name", "value": "urn:example.com:disclaimer"}}`...
[INFO] >> Checking hasProperties: selector: `{"path": "metadata.properties", "primaryKey": {"key": "name", "value": "urn:example.com:disclaimer"}}`, properties: '[{"key": "value", "value": "This SBOM is current as of the date it was generated and is subject to change."},]'...
[INFO] BOM valid against custom JSON configuration: 'test/custom/config-cdx-metadata-properties-disclaimer-unique-match.json': 'true'