Created
May 12, 2026 17:42
-
-
Save renepardon/49d5deacfd9b9dd7a87edf8dd2fd47f8 to your computer and use it in GitHub Desktop.
Read SBOM JSON and check osv.dev for known vulnerabilities
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
| import json | |
| import sys | |
| import requests | |
| class OSVClient: | |
| """Client for the OSV.dev API.""" | |
| BASE_URL = "https://api.osv.dev/v1/query" | |
| def check_vulnerability(self, package_name, version): | |
| """Queries OSV.dev for vulnerabilities for a specific package and version.""" | |
| payload = { | |
| "version": version, | |
| "package": { | |
| "name": package_name, | |
| "ecosystem": "PyPI" | |
| } | |
| } | |
| try: | |
| response = requests.post(self.BASE_URL, json=payload, timeout=10) | |
| response.raise_for_status() | |
| data = response.json() | |
| return data.get("vulns", []) | |
| except requests.exceptions.RequestException as e: | |
| # For simplicity in this exercise, we treat API errors as no vulnerabilities found | |
| # or we could raise it. The requirement says "Errors in file handling", | |
| # but doesn't specify API error handling details beyond "Query an external API". | |
| print(f"Warning: Failed to query OSV for {package_name}@{version}: {e}") | |
| return [] | |
| def display_results(vulnerable_components): | |
| """Displays the scan results to the user.""" | |
| if not vulnerable_components: | |
| print("\nNo vulnerabilities found.") | |
| return | |
| print("\nVulnerable components found:") | |
| for comp in vulnerable_components: | |
| print(f"- {comp['name']} ({comp['version']}): {len(comp['vulns'])} vulnerabilities") | |
| for vuln in comp['vulns']: | |
| vuln_id = vuln.get('id', 'N/A') | |
| summary = vuln.get('summary', 'No summary provided') | |
| print(f" * {vuln_id}: {summary}") | |
| # Exit with a clear error if vulnerabilities are found | |
| sys.exit(1) | |
| class SBOMScanner: | |
| """Scanner for SBOM files to detect vulnerabilities.""" | |
| def __init__(self, sbom_path): | |
| self.sbom_path = sbom_path | |
| self.osv_client = OSVClient() | |
| def load_sbom(self): | |
| """Loads and parses the SBOM JSON file.""" | |
| try: | |
| with open(self.sbom_path, 'r') as f: | |
| return json.load(f) | |
| except FileNotFoundError: | |
| print(f"Error: The file '{self.sbom_path}' was not found.") | |
| sys.exit(1) | |
| except json.JSONDecodeError: | |
| print(f"Error: Failed to decode JSON from '{self.sbom_path}'.") | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"Error reading file '{self.sbom_path}': {e}") | |
| sys.exit(1) | |
| def scan(self): | |
| """Scans all components in the SBOM for vulnerabilities.""" | |
| sbom_data = self.load_sbom() | |
| components = sbom_data.get("components", []) | |
| vulnerable_components = [] | |
| for component in components: | |
| name = component.get("name") | |
| version = component.get("version") | |
| if not name or not version: | |
| continue | |
| print(f"Checking {name}@{version}...") | |
| vulns = self.osv_client.check_vulnerability(name, version) | |
| if vulns: | |
| vulnerable_components.append({ | |
| "name": name, | |
| "version": version, | |
| "vulns": vulns | |
| }) | |
| return vulnerable_components | |
| def main(): | |
| if len(sys.argv) < 2: | |
| print("Usage: python3 sbom_scanner.py <path_to_sbom_json>") | |
| sys.exit(1) | |
| sbom_path = sys.argv[1] | |
| scanner = SBOMScanner(sbom_path) | |
| vulnerable_components = scanner.scan() | |
| display_results(vulnerable_components) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment