yusufcalisir's picture
deploy: Hugging Face space upload
94cb318
Raw History Blame Contribute Delete
3.68 kB
"""
Dynamic SBOM (Software Bill of Materials) Generator for CFI Platform.
Generates a genuine CycloneDX 1.5 compliant SBOM from the active Python distribution
metadata and the frontend npm manifest.
Outputs:
storage/sbom_cyclonedx.json
"""
import datetime
import importlib.metadata
import json
import sys
import uuid
from pathlib import Path
def generate_sbom(output_path: Path = Path("storage/sbom_cyclonedx.json")) -> dict:
output_path.parent.mkdir(parents=True, exist_ok=True)
components = []
# 1. Discover all active Python packages from importlib.metadata
dists = list(importlib.metadata.distributions())
for dist in dists:
name = dist.metadata.get("Name")
version = dist.metadata.get("Version")
if not name or not version:
continue
license_name = dist.metadata.get("License") or "Unknown"
summary = dist.metadata.get("Summary") or ""
component = {
"type": "library",
"bom-ref": f"pkg:pypi/{name.lower()}@{version}",
"name": name,
"version": version,
"description": summary[:200] if summary else "",
"licenses": [{"license": {"id": license_name}} if license_name in ["MIT", "Apache-2.0", "BSD-3-Clause", "MPL-2.0"] else {"license": {"name": license_name[:50]}}],
"purl": f"pkg:pypi/{name.lower()}@{version}",
"properties": [
{"name": "ecosystem", "value": "pypi"}
]
}
components.append(component)
# 2. Discover frontend packages from frontend/package.json
frontend_pkg_path = Path("frontend/package.json")
if frontend_pkg_path.exists():
try:
with open(frontend_pkg_path, encoding="utf-8") as f:
pkg_data = json.load(f)
deps = pkg_data.get("dependencies", {})
dev_deps = pkg_data.get("devDependencies", {})
for dep_name, dep_ver in {**deps, **dev_deps}.items():
clean_ver = dep_ver.lstrip("^~")
components.append({
"type": "library",
"bom-ref": f"pkg:npm/{dep_name}@{clean_ver}",
"name": dep_name,
"version": clean_ver,
"purl": f"pkg:npm/{dep_name}@{clean_ver}",
"properties": [
{"name": "ecosystem", "value": "npm"}
]
})
except Exception as exc:
print(f"Warning reading frontend package.json: {exc}", file=sys.stderr)
# Sort components deterministically by name
components.sort(key=lambda c: c["name"].lower())
sbom = {
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": f"urn:uuid:{uuid.uuid4()}",
"version": 1,
"metadata": {
"timestamp": datetime.datetime.now(datetime.UTC).isoformat(),
"tools": [
{
"vendor": "CF-Intelligence",
"name": "cfi-sbom-generator",
"version": "1.0.0"
}
],
"component": {
"type": "application",
"name": "privacy-preserving-cross-bank-fraud-detection",
"version": "1.0.0",
"licenses": [{"license": {"id": "MIT"}}]
}
},
"components": components
}
with open(output_path, "w", encoding="utf-8") as f:
json.dump(sbom, f, indent=2)
print(f"Generated CycloneDX 1.5 SBOM with {len(components)} components -> {output_path}")
return sbom
if __name__ == "__main__":
generate_sbom()