Last active
September 12, 2025 23:02
-
-
Save cgoldberg/caf2a7bf5ce859022bd42cfe9bd462f2 to your computer and use it in GitHub Desktop.
Python - Generate language specific SBOMs from a multi-language monorepo on GitHub
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
#!/usr/bin/env python3 | |
# | |
# Copyright (c) 2025 Corey Goldberg (https://github.com/cgoldberg) | |
# License: MIT | |
# | |
# Generate language specific SBOMs from a multi-language monorepo on GitHub | |
# - fetches repo-wide SBOM from GitHub | |
# - generates individual SBOMs for each language specified | |
# | |
# requires: | |
# - lib4sbom | |
# - requests | |
import re | |
from lib4sbom.parser import SBOMParser | |
from lib4sbom.output import SBOMOutput | |
import requests | |
ORGANIZATION = "SeleniumHQ" | |
REPO = "selenium" | |
PACKAGE_MANAGERS_LANGUAGES = { | |
"gem": "Ruby", | |
"cargo": "Rust", | |
"npm": "JavaScript", | |
"nuget": "DotNET", | |
"pypi": "Python", | |
} | |
def get_package_managers(packages): | |
package_managers = [] | |
for package in packages: | |
external_reference = package["externalreference"] | |
package_manager = "UNKNOWN" | |
if external_reference[0][0] == "PACKAGE-MANAGER": | |
match = re.search(r"^pkg:([^/]+)", external_reference[0][2]) | |
if match: | |
package_manager = match.group(1) | |
if package_manager != "githubactions": | |
package_managers.append(package_manager) | |
return list(set(package_managers)) | |
def filter_packages(packages, package_manager): | |
filtered_packages = [] | |
for package in packages: | |
match = re.search(r"^pkg:([^/]+)", package["externalreference"][0][2]) | |
if match.group(1) == package_manager: | |
filtered_packages.append(package) | |
return filtered_packages | |
def filter_relationships(packages, relationships): | |
filtered_relationships = [] | |
for relationship in relationships: | |
for package in packages: | |
if package["id"] == relationship["target_id"]: | |
filtered_relationships.append(relationship) | |
break | |
return filtered_relationships | |
def get_repo_sbom(organization, repo): | |
headers = { | |
"Accept": "application/vnd.github+json", | |
"X-GitHub-Api-Version": "2022-11-28", | |
} | |
url = f"https://api.github.com/repos/{organization}/{repo}/dependency-graph/sbom" | |
response = requests.get(url, headers=headers) | |
if response.status_code != 200: | |
raise Exception(f"Failed to retrieve SBOM") | |
return response.text | |
if __name__ == "__main__": | |
print(f"\nRetrieving SBOM from GitHub for {ORGANIZATION}/{REPO} ...\n") | |
sbom = get_repo_sbom(ORGANIZATION, REPO) | |
parser = SBOMParser() | |
parser.parse_string(sbom) | |
print(f"SBOM type: {parser.get_type()}") | |
main_sbom = parser.get_sbom() | |
all_packages = [p for p in parser.get_packages() if REPO not in p["name"].lower()] | |
print(f"Found {len(all_packages)} packages\n") | |
all_relationships = parser.get_relationships() | |
package_managers = get_package_managers(all_packages) | |
unknown_package_managers = [ | |
pm for pm in package_managers if pm not in PACKAGE_MANAGERS_LANGUAGES.keys() | |
] | |
if unknown_package_managers: | |
raise Exception( | |
f"Unknown package manager found in SBOM: {unknown_package_managers}" | |
) | |
for package_manager, language in PACKAGE_MANAGERS_LANGUAGES.items(): | |
language_sbom = main_sbom.copy() | |
packages = filter_packages(all_packages, package_manager) | |
relationships = filter_relationships(packages, all_relationships) | |
language_sbom["packages"] = packages | |
language_sbom["relationships"] = relationships | |
file_name = f"{ORGANIZATION}_{REPO}_{language}.json" | |
sbom_output = SBOMOutput(filename=file_name, output_format="json") | |
sbom_output.generate_output(language_sbom) | |
print(f"{language}:\n - Found {len(packages)} packages") | |
print(f" - Generated language-specific SBOM:\n - {file_name}\n") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment