-
-
Notifications
You must be signed in to change notification settings - Fork 58
docs: Add comprehensive SBOM validation guide #933
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
Open
saquibsaifee
wants to merge
9
commits into
CycloneDX:main
Choose a base branch
from
saquibsaifee:docs/add-validation-guide
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+214
−0
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
b55d436
docs: Add comprehensive SBOM validation guide
saquibsaifee c9081d4
Update docs/validation.rst
saquibsaifee e52401f
Update docs/validation.rst
saquibsaifee 1061440
Update docs/validation.rst
saquibsaifee 87749c2
docs: use literalinclude for validation examples and improve format d…
saquibsaifee 0295ccc
docs: remove reference to validation.rst in index.rst
saquibsaifee d3b3f54
Update examples/complex_validation.py
saquibsaifee 5a842e1
fix: correct validation errors and refactor for code quality
saquibsaifee a617abe
fix: add type ignore comment for lxml import
saquibsaifee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| # This file is part of CycloneDX Python Library | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # Copyright (c) OWASP Foundation. All Rights Reserved. | ||
|
|
||
| import json | ||
| import sys | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from lxml import etree # type: ignore[import-untyped] | ||
|
|
||
| from cyclonedx.exception import MissingOptionalDependencyException | ||
| from cyclonedx.schema import OutputFormat, SchemaVersion | ||
| from cyclonedx.validation import make_schemabased_validator | ||
|
|
||
| if TYPE_CHECKING: | ||
| from cyclonedx.validation.json import JsonValidator | ||
| from cyclonedx.validation.xml import XmlValidator | ||
|
|
||
| """ | ||
| This example demonstrates how to validate CycloneDX documents (both JSON and XML). | ||
| Make sure to have the needed dependencies installed - install the library's extra 'validation' for that. | ||
| """ | ||
|
|
||
| # region Sample SBOMs | ||
|
|
||
| JSON_SBOM = """ | ||
| { | ||
| "bomFormat": "CycloneDX", | ||
| "specVersion": "1.5", | ||
| "version": 1, | ||
| "metadata": { | ||
| "component": { | ||
| "type": "application", | ||
| "name": "my-app", | ||
| "version": "1.0.0" | ||
| } | ||
| }, | ||
| "components": [] | ||
| } | ||
| """ | ||
|
|
||
| XML_SBOM = """<?xml version="1.0" encoding="UTF-8"?> | ||
| <bom xmlns="http://cyclonedx.org/schema/bom/1.5" version="1"> | ||
| <metadata> | ||
| <component type="application"> | ||
| <name>my-app</name> | ||
| <version>1.0.0</version> | ||
| </component> | ||
| </metadata> | ||
| </bom> | ||
| """ | ||
|
|
||
| INVALID_JSON_SBOM = """ | ||
| { | ||
| "bomFormat": "CycloneDX", | ||
| "specVersion": "1.5", | ||
| "metadata": { | ||
| "component": { | ||
| "type": "invalid-type", | ||
| "name": "my-app" | ||
| } | ||
| } | ||
| } | ||
| """ | ||
| # endregion Sample SBOMs | ||
|
|
||
|
|
||
| # region JSON Validation | ||
|
|
||
| print('--- JSON Validation ---') | ||
|
|
||
| # Create a JSON validator for a specific schema version | ||
| json_validator: 'JsonValidator' = make_schemabased_validator(OutputFormat.JSON, SchemaVersion.V1_5) | ||
|
|
||
| try: | ||
| # 1. Validate valid SBOM | ||
| validation_errors = json_validator.validate_str(JSON_SBOM) | ||
| if validation_errors: | ||
| print('JSON SBOM is unexpectedly invalid!', file=sys.stderr) | ||
| else: | ||
| print('JSON SBOM is valid') | ||
|
|
||
| # 2. Validate invalid SBOM and inspect details | ||
| print('\nChecking invalid JSON SBOM...') | ||
| validation_errors = json_validator.validate_str(INVALID_JSON_SBOM) | ||
| if validation_errors: | ||
| print('Validation failed as expected.') | ||
| print(f'Error Message: {validation_errors.data.message}') | ||
| print(f'JSON Path: {validation_errors.data.json_path}') | ||
| print(f'Invalid Data: {validation_errors.data.instance}') | ||
| except MissingOptionalDependencyException as error: | ||
| print('JSON validation was skipped:', error) | ||
|
|
||
| # endregion JSON Validation | ||
|
|
||
|
|
||
| print('\n' + '=' * 30 + '\n') | ||
|
|
||
|
|
||
| # region XML Validation | ||
|
|
||
| print('--- XML Validation ---') | ||
|
|
||
| xml_validator: 'XmlValidator' = make_schemabased_validator(OutputFormat.XML, SchemaVersion.V1_5) | ||
|
|
||
| try: | ||
| xml_validation_errors = xml_validator.validate_str(XML_SBOM) | ||
| if xml_validation_errors: | ||
| print('XML SBOM is invalid!', file=sys.stderr) | ||
| else: | ||
| print('XML SBOM is valid') | ||
| except MissingOptionalDependencyException as error: | ||
| print('XML validation was skipped:', error) | ||
|
|
||
| # endregion XML Validation | ||
|
|
||
|
|
||
| print('\n' + '=' * 30 + '\n') | ||
|
|
||
|
|
||
| # region Dynamic version detection | ||
|
|
||
| print('--- Dynamic Validation ---') | ||
|
|
||
|
|
||
| def _detect_json_format(raw_data: str) -> tuple[OutputFormat, SchemaVersion] | None: | ||
| """Detect JSON format and extract schema version.""" | ||
| try: | ||
| data = json.loads(raw_data) | ||
| spec_version_str = data.get('specVersion') | ||
| if not spec_version_str: | ||
| print('Error: Missing specVersion in JSON SBOM', file=sys.stderr) | ||
| return None | ||
| schema_version = SchemaVersion.from_version(spec_version_str) | ||
| return (OutputFormat.JSON, schema_version) | ||
| except (json.JSONDecodeError, ValueError): | ||
| return None | ||
|
|
||
|
|
||
| def _detect_xml_format(raw_data: str) -> tuple[OutputFormat, SchemaVersion] | None: | ||
| """Detect XML format and extract schema version.""" | ||
| try: | ||
| xml_tree = etree.fromstring(raw_data.encode('utf-8')) | ||
| # Extract version from CycloneDX namespace | ||
| schema_version = SchemaVersion.V1_5 # Default | ||
| for ns in xml_tree.nsmap.values(): | ||
| if ns and ns.startswith('http://cyclonedx.org/schema/bom/'): | ||
| try: | ||
| schema_version = SchemaVersion.from_version(ns.split('/')[-1]) | ||
| break | ||
| except ValueError: | ||
| pass | ||
| return (OutputFormat.XML, schema_version) | ||
| except etree.XMLSyntaxError: | ||
| print('Error: Unknown or malformed SBOM format', file=sys.stderr) | ||
| return None | ||
| except Exception as e: | ||
| print(f'Error: Format detection failed: {e}', file=sys.stderr) | ||
| return None | ||
|
|
||
|
|
||
| def validate_sbom(raw_data: str) -> bool: | ||
| """Validate an SBOM by detecting its format and version.""" | ||
| # Detect format and version | ||
| format_info = _detect_json_format(raw_data) or _detect_xml_format(raw_data) | ||
| if not format_info: | ||
| return False | ||
|
|
||
| input_format, schema_version = format_info | ||
|
|
||
| # Perform validation | ||
| try: | ||
| validator = make_schemabased_validator(input_format, schema_version) | ||
| errors = validator.validate_str(raw_data) | ||
|
|
||
| if errors: | ||
| print(f'Validation failed for {input_format.name} version {schema_version.to_version()}', file=sys.stderr) | ||
| print(f'Reason: {errors}', file=sys.stderr) | ||
| return False | ||
|
|
||
| print(f'Successfully validated {input_format.name} SBOM (Version {schema_version.to_version()})') | ||
| return True | ||
|
|
||
| except MissingOptionalDependencyException as error: | ||
| print(f'Validation skipped due to missing dependencies: {error}') | ||
| return False | ||
|
|
||
|
|
||
| # Execute dynamic validation | ||
| validate_sbom(JSON_SBOM) | ||
| validate_sbom(XML_SBOM) | ||
|
|
||
| # endregion Dynamic version detection | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.