Software Bill of Materials (SBOM)
Overview
An SBOM is a comprehensive inventory of all the components, dependencies, and attributes associated with a software product.
In software development, SBOMs are widely used to provide transparency and traceability in the software supply chain, for risk assessment or to identify and manage security vulnerabilities and dependencies. They are also used to provide proof of compliance to licensing requirements and other legal obligations associated with the use of third-party components.
A typical SBOM holds information like:
-
Component details including the software name, version and identifier.
-
List of dependencies between different software components.
-
List of each component's vulnerabilities.
-
License types.
-
Links to documentation and project URLs.
Jit SBOM plan item
Jit's SBOM plan item scans all the components in your project's libraries, their dependencies and sub-dependencies. It then generates an all-inclusive and continually updated report holding the Library name, License and Resource of each component.
-
SBOM scans are run daily.
-
All repositories are individually scanned.
-
A scan is initiated on any change in a repository when added or removed.
-
The SBOM report is updated after each scan.
Premium user privileges
Some features of the SBOM report require Premium user privileges. If you are not a Premium user, this information is not displayed in the report.
Generating an SBOM report
-
In the left menu, select SBOM.
-
Optional: If you have not activated SBOM, click Activate and wait for the process to run. This may take some time. Data is displayed in the SBOM interface throughout the Activation process. For more information about SBOM activation, see Security Plan Page.
-
Click an entry to display where the component appears in the project.
-
Click Contact Us for more information and to learn about the Premium Users package.
CSV export for SBOM
Currently, SBOM reports are available for the product standard Cyclone DX format.
For users requiring a CSV script the below python file can be run locally:
- Download the Python file on a machine that has python3 installed and name it cyclonedx-to-csv.py
- Download the JSON SBOM from the Jit UI
- Open a terminal and issue the OS command to change the directory to the location of the Python file
- Use the following command to convert the file:
- python3 cyclonedx-to-csv.py
#!/usr/bin/env python3
import json
import csv
import argparse
from typing import Dict, List, Any, Optional
from pathlib import Path
def safe_get_licenses(license_data: Optional[List[Any]]) -> str:
"""
Safely extract license information from various possible formats.
"""
if not license_data:
return ''
licenses = []
for license_info in license_data:
if isinstance(license_info, dict):
# Handle license dictionary format
if 'license' in license_info:
license_content = license_info['license']
if isinstance(license_content, dict):
licenses.append(license_content.get('id', ''))
else:
licenses.append(str(license_content))
# Handle expression format
elif 'expression' in license_info:
licenses.append(license_info['expression'])
else:
# Handle direct license string
licenses.append(str(license_info))
return '; '.join(filter(None, licenses))
def extract_component_data(component: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract relevant data from a component in the SBOM, handling missing fields gracefully.
"""
if not isinstance(component, dict):
return {
'name': '',
'version': '',
'purl': '',
'type': '',
'publisher': '',
'group': '',
'description': '',
'licenses': '',
'cpe': '',
'author': ''
}
# Extract publisher information
publisher = component.get('publisher', '')
if isinstance(publisher, dict):
publisher = publisher.get('name', '')
# Extract author information
author = component.get('author', '')
if isinstance(author, dict):
author = author.get('name', '')
return {
'name': str(component.get('name', '')),
'version': str(component.get('version', '')),
'purl': str(component.get('purl', '')),
'type': str(component.get('type', '')),
'publisher': str(publisher),
'group': str(component.get('group', '')),
'description': str(component.get('description', '')),
'licenses': safe_get_licenses(component.get('licenses', [])),
'cpe': str(component.get('cpe', '')),
'author': str(author)
}
def process_sbom(sbom_data: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
Process the SBOM data and extract component information, handling missing sections gracefully.
"""
components = []
# Process metadata components if they exist
metadata = sbom_data.get('metadata', {})
if isinstance(metadata, dict):
metadata_components = metadata.get('components', [])
if isinstance(metadata_components, list):
for component in metadata_components:
if component:
components.append(extract_component_data(component))
# Process main components list
main_components = sbom_data.get('components', [])
if isinstance(main_components, list):
for component in main_components:
if component:
components.append(extract_component_data(component))
return components
def write_csv(components: List[Dict[str, Any]], output_file: Path) -> None:
"""
Write the component data to a CSV file.
"""
if not components:
print("No components found to write to CSV.")
return
fieldnames = [
'name', 'version', 'purl', 'type', 'publisher',
'group', 'description', 'licenses', 'cpe', 'author'
]
with output_file.open('w', newline='', encoding='utf-8') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(components)
def main():
parser = argparse.ArgumentParser(description='Convert CycloneDX SBOM JSON to CSV')
parser.add_argument('input_file', type=str, help='Input CycloneDX JSON file path')
parser.add_argument('output_file', type=str, help='Output CSV file path')
args = parser.parse_args()
input_path = Path(args.input_file)
output_path = Path(args.output_file)
if not input_path.exists():
print(f"Error: Input file {input_path} does not exist.")
return
try:
with input_path.open('r', encoding='utf-8') as f:
sbom_data = json.load(f)
if not isinstance(sbom_data, dict):
print("Error: Invalid SBOM format - root element must be an object")
return
components = process_sbom(sbom_data)
write_csv(components, output_path)
print(f"Successfully converted {input_path} to {output_path}")
print(f"Processed {len(components)} components")
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON file - {e}")
except Exception as e:
print(f"Error: {str(e)}")
if __name__ == "__main__":
main
Premium users
The following SBOM interface is displayed for Premium users.
- Click an entry to display where the component appears in the project.
- Search for components, for example based on library names, versions or repositories and more.
- Click Export report to download the SBOM report in CycloneDX JSON format.
Supported Ecosystems
Jit will scan and include components from the following ecosystems in the SBOM report:
- Alpine (apk)
- C (conan)
- C++ (conan)
- Dart (pubs)
- Debian (dpkg)
- Dotnet (deps.json)
- Objective-C (cocoapods)
- Elixir (mix)
- Erlang (rebar3)
- Go (go.mod, Go binaries)
- Haskell (cabal, stack)
- Java (jar, ear, war, par, sar, nar, native-image)
- JavaScript (npm, yarn)
- Jenkins Plugins (jpi, hpi)
- Linux kernel archives (vmlinz)
- Linux kernel modules (ko)
- Nix (outputs in /nix/store)
- PHP (composer)
- Python (wheel, egg, poetry, requirements.txt)
- Red Hat (rpm)
- Ruby (gem)
- Rust (cargo.lock)
- Swift (cocoapods, swift-package-manager)
- Wordpress plugins
Licenses
Software licenses are included for every component for the following languages:
- NodeJS
- Go Modules
- Python
- PHP
*In some cases, licenses will be included for languages not specified above.
Updated about 1 month ago