sbom-generator / app.py
AYI-NEDJIMI's picture
Upload folder using huggingface_hub
e6a2523 verified
Raw
History Blame Contribute Delete
41.7 kB
"""
SBOM Generator & Validator - Interactive Gradio Space
Powered by AYI-NEDJIMI Consultants | https://ayinedjimi-consultants.fr
"""
import gradio as gr
import json
import hashlib
import uuid
import re
from datetime import datetime, timezone
# ---------------------------------------------------------------------------
# Translations
# ---------------------------------------------------------------------------
TR = {
"fr": {
"title": "Generateur & Validateur SBOM",
"subtitle": "Creez et validez vos Software Bill of Materials (SBOM) conformes CycloneDX & SPDX",
"lang_btn": "English",
"tab_gen": "Generateur SBOM",
"tab_val": "Validateur SBOM",
"tab_reg": "Exigences reglementaires",
"tab_cmp": "Comparaison de formats",
"tab_tools": "Ecosysteme outils",
"tab_res": "Ressources",
"input_label": "Contenu du fichier de dependances",
"input_placeholder": "Collez ici le contenu de requirements.txt, package.json, pom.xml ou go.mod ...",
"format_label": "Format de sortie",
"generate_btn": "Generer le SBOM",
"output_label": "Document SBOM genere",
"stats_label": "Statistiques",
"val_input_label": "Document SBOM a valider",
"val_input_placeholder": "Collez ici un document SBOM (CycloneDX JSON/XML ou SPDX JSON/Tag-Value) ...",
"validate_btn": "Valider le SBOM",
"val_result_label": "Resultat de la validation",
"val_details_label": "Details et recommandations",
"no_input": "Veuillez fournir un contenu de fichier de dependances.",
"no_sbom": "Veuillez fournir un document SBOM a valider.",
"parsed_ok": "composants detectes",
"gen_ok": "SBOM genere avec succes",
"components": "Composants",
"licenses": "Distribution des licences",
"format_version": "Version du format",
"score": "Score de conformite",
"result_pass": "SUCCES - Le SBOM est conforme",
"result_fail": "ECHEC - Le SBOM presente des problemes",
"missing": "Champs manquants",
"recommendations": "Recommandations",
"ntia_title": "Checklist NTIA Minimum Elements",
"reg_title": "Exigences SBOM par regulation",
"cmp_title": "Comparaison des formats SBOM",
"tools_title": "Ecosysteme des outils SBOM",
"res_title": "Ressources et articles",
"footer": "Propulse par",
},
"en": {
"title": "SBOM Generator & Validator",
"subtitle": "Create and validate your Software Bill of Materials (SBOM) compliant with CycloneDX & SPDX",
"lang_btn": "Francais",
"tab_gen": "SBOM Generator",
"tab_val": "SBOM Validator",
"tab_reg": "Regulatory Requirements",
"tab_cmp": "Format Comparison",
"tab_tools": "Tool Ecosystem",
"tab_res": "Resources",
"input_label": "Dependency file content",
"input_placeholder": "Paste requirements.txt, package.json, pom.xml or go.mod content here ...",
"format_label": "Output format",
"generate_btn": "Generate SBOM",
"output_label": "Generated SBOM document",
"stats_label": "Statistics",
"val_input_label": "SBOM document to validate",
"val_input_placeholder": "Paste a SBOM document (CycloneDX JSON/XML or SPDX JSON/Tag-Value) here ...",
"validate_btn": "Validate SBOM",
"val_result_label": "Validation result",
"val_details_label": "Details and recommendations",
"no_input": "Please provide dependency file content.",
"no_sbom": "Please provide an SBOM document to validate.",
"parsed_ok": "components detected",
"gen_ok": "SBOM generated successfully",
"components": "Components",
"licenses": "License distribution",
"format_version": "Format version",
"score": "Compliance score",
"result_pass": "PASS - SBOM is compliant",
"result_fail": "FAIL - SBOM has issues",
"missing": "Missing fields",
"recommendations": "Recommendations",
"ntia_title": "NTIA Minimum Elements Checklist",
"reg_title": "SBOM requirements by regulation",
"cmp_title": "SBOM Format Comparison",
"tools_title": "SBOM Tool Ecosystem",
"res_title": "Resources and articles",
"footer": "Powered by",
},
}
KNOWN_LICENSES = {
"MIT": "MIT", "Apache-2.0": "Apache-2.0", "BSD-3-Clause": "BSD-3-Clause",
"BSD-2-Clause": "BSD-2-Clause", "GPL-3.0-only": "GPL-3.0-only",
"GPL-2.0-only": "GPL-2.0-only", "LGPL-3.0-only": "LGPL-3.0-only",
"ISC": "ISC", "MPL-2.0": "MPL-2.0", "AGPL-3.0-only": "AGPL-3.0-only",
"Unlicense": "Unlicense", "0BSD": "0BSD",
}
DEFAULT_LICENSE_MAP = {
"flask": "BSD-3-Clause", "django": "BSD-3-Clause", "requests": "Apache-2.0",
"numpy": "BSD-3-Clause", "pandas": "BSD-3-Clause", "fastapi": "MIT",
"gradio": "Apache-2.0", "express": "MIT", "react": "MIT", "lodash": "MIT",
"axios": "MIT", "vue": "MIT", "typescript": "Apache-2.0", "webpack": "MIT",
"spring-boot-starter-web": "Apache-2.0", "junit-jupiter": "EPL-2.0",
"lombok": "MIT", "gin": "MIT", "cobra": "Apache-2.0", "logrus": "MIT",
"pytest": "MIT", "black": "MIT", "uvicorn": "BSD-3-Clause",
"sqlalchemy": "MIT", "pydantic": "MIT", "boto3": "Apache-2.0",
"tensorflow": "Apache-2.0", "torch": "BSD-3-Clause", "scipy": "BSD-3-Clause",
"scikit-learn": "BSD-3-Clause", "matplotlib": "PSF-2.0",
}
# ---------------------------------------------------------------------------
# Parsers
# ---------------------------------------------------------------------------
def _sim_hash(name: str, version: str) -> str:
return hashlib.sha256(f"{name}@{version}".encode()).hexdigest()
def _guess_license(name: str) -> str:
key = name.lower().replace("_", "-")
return DEFAULT_LICENSE_MAP.get(key, "MIT")
def parse_requirements(text: str):
components = []
for line in text.strip().splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
m = re.match(r"^([A-Za-z0-9_\-\.]+)\s*[=~<>!]=?\s*([0-9][^\s,;]*)?", line)
if m:
name = m.group(1)
version = m.group(2) or "0.0.0"
components.append({"name": name, "version": version,
"license": _guess_license(name), "type": "library",
"purl": f"pkg:pypi/{name}@{version}"})
return components
def parse_package_json(text: str):
components = []
try:
data = json.loads(text)
except json.JSONDecodeError:
return components
deps = {}
deps.update(data.get("dependencies", {}))
deps.update(data.get("devDependencies", {}))
for name, ver in deps.items():
version = re.sub(r"[^0-9\.]", "", ver) or "0.0.0"
components.append({"name": name, "version": version,
"license": _guess_license(name), "type": "library",
"purl": f"pkg:npm/{name}@{version}"})
return components
def parse_pom_xml(text: str):
components = []
pattern = r"<dependency>\s*<groupId>([^<]+)</groupId>\s*<artifactId>([^<]+)</artifactId>\s*<version>([^<]+)</version>"
for m in re.finditer(pattern, text, re.DOTALL):
group, artifact, version = m.group(1), m.group(2), m.group(3)
components.append({"name": f"{group}:{artifact}", "version": version,
"license": _guess_license(artifact), "type": "library",
"purl": f"pkg:maven/{group}/{artifact}@{version}"})
return components
def parse_go_mod(text: str):
components = []
for line in text.strip().splitlines():
line = line.strip()
m = re.match(r"^([\w\.\-/]+)\s+(v?[\d\.]+)", line)
if m and "/" in m.group(1):
mod = m.group(1)
version = m.group(2)
short = mod.split("/")[-1]
components.append({"name": mod, "version": version,
"license": _guess_license(short), "type": "library",
"purl": f"pkg:golang/{mod}@{version}"})
return components
def auto_parse(text: str):
text = text.strip()
if not text:
return []
if text.lstrip().startswith("{"):
comps = parse_package_json(text)
if comps:
return comps
if "<dependency>" in text:
comps = parse_pom_xml(text)
if comps:
return comps
if "module " in text or "require (" in text or "go " in text:
comps = parse_go_mod(text)
if comps:
return comps
return parse_requirements(text)
# ---------------------------------------------------------------------------
# SBOM generators
# ---------------------------------------------------------------------------
NOW_ISO = lambda: datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def gen_cyclonedx_json(components):
bom = {
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": f"urn:uuid:{uuid.uuid4()}",
"version": 1,
"metadata": {
"timestamp": NOW_ISO(),
"tools": [{"vendor": "AYI-NEDJIMI Consultants", "name": "sbom-generator", "version": "1.0.0"}],
"component": {"type": "application", "name": "my-application", "version": "1.0.0"},
},
"components": [],
"dependencies": [{"ref": "my-application", "dependsOn": []}],
}
for c in components:
comp = {
"type": c["type"],
"name": c["name"],
"version": c["version"],
"purl": c["purl"],
"licenses": [{"license": {"id": c["license"]}}],
"hashes": [{"alg": "SHA-256", "content": _sim_hash(c["name"], c["version"])}],
}
bom["components"].append(comp)
bom["dependencies"][0]["dependsOn"].append(c["purl"])
return json.dumps(bom, indent=2)
def gen_cyclonedx_xml(components):
ts = NOW_ISO()
serial = f"urn:uuid:{uuid.uuid4()}"
lines = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<bom xmlns="http://cyclonedx.org/schema/bom/1.5" serialNumber="' + serial + '" version="1">',
" <metadata>",
f" <timestamp>{ts}</timestamp>",
" <tools><tool><vendor>AYI-NEDJIMI Consultants</vendor><name>sbom-generator</name><version>1.0.0</version></tool></tools>",
' <component type="application"><name>my-application</name><version>1.0.0</version></component>',
" </metadata>",
" <components>",
]
refs = []
for c in components:
h = _sim_hash(c["name"], c["version"])
lines.append(f' <component type="{c["type"]}">')
lines.append(f" <name>{c['name']}</name>")
lines.append(f" <version>{c['version']}</version>")
lines.append(f" <purl>{c['purl']}</purl>")
lines.append(f' <licenses><license><id>{c["license"]}</id></license></licenses>')
lines.append(f' <hashes><hash alg="SHA-256">{h}</hash></hashes>')
lines.append(" </component>")
refs.append(c["purl"])
lines.append(" </components>")
lines.append(" <dependencies>")
lines.append(' <dependency ref="my-application">')
for r in refs:
lines.append(f' <dependency ref="{r}"/>')
lines.append(" </dependency>")
lines.append(" </dependencies>")
lines.append("</bom>")
return "\n".join(lines)
def gen_spdx_json(components):
doc_ns = f"https://spdx.org/spdxdocs/sbom-generator-{uuid.uuid4()}"
doc = {
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "my-application",
"documentNamespace": doc_ns,
"creationInfo": {
"created": NOW_ISO(),
"creators": ["Tool: sbom-generator-1.0.0", "Organization: AYI-NEDJIMI Consultants"],
},
"packages": [],
"relationships": [],
}
for i, c in enumerate(components):
spdx_id = f"SPDXRef-Package-{i+1}"
pkg = {
"SPDXID": spdx_id,
"name": c["name"],
"versionInfo": c["version"],
"downloadLocation": "https://example.com/download",
"supplier": "Organization: Open Source",
"licenseConcluded": c["license"],
"licenseDeclared": c["license"],
"copyrightText": "NOASSERTION",
"externalRefs": [
{"referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": c["purl"]}
],
"checksums": [{"algorithm": "SHA256", "checksumValue": _sim_hash(c["name"], c["version"])}],
}
doc["packages"].append(pkg)
doc["relationships"].append({
"spdxElementId": "SPDXRef-DOCUMENT",
"relatedSpdxElement": spdx_id,
"relationshipType": "DESCRIBES",
})
return json.dumps(doc, indent=2)
def gen_spdx_tv(components):
ts = NOW_ISO()
doc_ns = f"https://spdx.org/spdxdocs/sbom-generator-{uuid.uuid4()}"
lines = [
"SPDXVersion: SPDX-2.3",
"DataLicense: CC0-1.0",
"SPDXID: SPDXRef-DOCUMENT",
"DocumentName: my-application",
f"DocumentNamespace: {doc_ns}",
f"Creator: Tool: sbom-generator-1.0.0",
f"Creator: Organization: AYI-NEDJIMI Consultants",
f"Created: {ts}",
"",
]
for i, c in enumerate(components):
sid = f"SPDXRef-Package-{i+1}"
lines += [
f"PackageName: {c['name']}",
f"SPDXID: {sid}",
f"PackageVersion: {c['version']}",
"PackageDownloadLocation: https://example.com/download",
"PackageSupplier: Organization: Open Source",
f"PackageLicenseConcluded: {c['license']}",
f"PackageLicenseDeclared: {c['license']}",
"PackageCopyrightText: NOASSERTION",
f"PackageChecksum: SHA256: {_sim_hash(c['name'], c['version'])}",
f"ExternalRef: PACKAGE-MANAGER purl {c['purl']}",
f"Relationship: SPDXRef-DOCUMENT DESCRIBES {sid}",
"",
]
return "\n".join(lines)
FORMAT_FUNCS = {
"CycloneDX JSON": gen_cyclonedx_json,
"CycloneDX XML": gen_cyclonedx_xml,
"SPDX JSON": gen_spdx_json,
"SPDX Tag-Value": gen_spdx_tv,
}
FORMAT_VERSIONS = {
"CycloneDX JSON": "CycloneDX 1.5",
"CycloneDX XML": "CycloneDX 1.5",
"SPDX JSON": "SPDX 2.3",
"SPDX Tag-Value": "SPDX 2.3",
}
# ---------------------------------------------------------------------------
# Generate handler
# ---------------------------------------------------------------------------
def generate_sbom(text: str, fmt: str, lang: str):
t = TR[lang]
if not text or not text.strip():
return t["no_input"], "", None
components = auto_parse(text)
if not components:
return t["no_input"], "", None
sbom_text = FORMAT_FUNCS[fmt](components)
lic_dist = {}
for c in components:
lic_dist[c["license"]] = lic_dist.get(c["license"], 0) + 1
lic_lines = ", ".join(f"{k}: {v}" for k, v in sorted(lic_dist.items(), key=lambda x: -x[1]))
stats = (
f"{t['gen_ok']}\n\n"
f"**{t['components']}**: {len(components)} {t['parsed_ok']}\n\n"
f"**{t['licenses']}**: {lic_lines}\n\n"
f"**{t['format_version']}**: {FORMAT_VERSIONS[fmt]}"
)
ext = {"CycloneDX JSON": ".json", "CycloneDX XML": ".xml",
"SPDX JSON": ".json", "SPDX Tag-Value": ".spdx"}[fmt]
tmp_path = f"/tmp/sbom{ext}"
with open(tmp_path, "w") as f:
f.write(sbom_text)
return sbom_text, stats, tmp_path
# ---------------------------------------------------------------------------
# Validator
# ---------------------------------------------------------------------------
def validate_sbom(text: str, lang: str):
t = TR[lang]
if not text or not text.strip():
return t["no_sbom"], ""
text = text.strip()
issues = []
checks = {}
detected = "Unknown"
components_found = 0
score = 0
total = 10
# Try CycloneDX JSON
if text.lstrip().startswith("{") and "bomFormat" in text:
detected = "CycloneDX JSON"
try:
data = json.loads(text)
except json.JSONDecodeError:
return "Invalid JSON", ""
checks["bomFormat"] = data.get("bomFormat") == "CycloneDX"
checks["specVersion"] = data.get("specVersion", "") in ("1.4", "1.5", "1.6")
checks["serialNumber"] = bool(data.get("serialNumber"))
checks["metadata.timestamp"] = bool(data.get("metadata", {}).get("timestamp"))
checks["metadata.tools"] = bool(data.get("metadata", {}).get("tools"))
checks["components"] = isinstance(data.get("components"), list) and len(data.get("components", [])) > 0
components_found = len(data.get("components", []))
comps = data.get("components", [])
all_have_name = all("name" in c for c in comps)
all_have_version = all("version" in c for c in comps)
all_have_purl = all("purl" in c for c in comps)
all_have_license = all(c.get("licenses") for c in comps)
all_have_hash = all(c.get("hashes") for c in comps)
checks["component.name"] = all_have_name
checks["component.version"] = all_have_version
checks["component.purl"] = all_have_purl
checks["component.license"] = all_have_license
checks["component.hash"] = all_have_hash
checks["dependencies"] = isinstance(data.get("dependencies"), list)
# Try SPDX JSON
elif text.lstrip().startswith("{") and "spdxVersion" in text:
detected = "SPDX JSON"
try:
data = json.loads(text)
except json.JSONDecodeError:
return "Invalid JSON", ""
checks["spdxVersion"] = data.get("spdxVersion", "").startswith("SPDX-2.")
checks["dataLicense"] = data.get("dataLicense") == "CC0-1.0"
checks["SPDXID"] = data.get("SPDXID") == "SPDXRef-DOCUMENT"
checks["documentNamespace"] = bool(data.get("documentNamespace"))
checks["creationInfo"] = bool(data.get("creationInfo", {}).get("created"))
checks["creators"] = bool(data.get("creationInfo", {}).get("creators"))
pkgs = data.get("packages", [])
components_found = len(pkgs)
checks["packages"] = len(pkgs) > 0
checks["package.name"] = all("name" in p for p in pkgs)
checks["package.version"] = all("versionInfo" in p for p in pkgs)
checks["package.supplier"] = all("supplier" in p for p in pkgs)
checks["package.license"] = all("licenseConcluded" in p for p in pkgs)
checks["relationships"] = isinstance(data.get("relationships"), list)
# Try SPDX Tag-Value
elif "SPDXVersion:" in text:
detected = "SPDX Tag-Value"
checks["SPDXVersion"] = bool(re.search(r"SPDXVersion:\s*SPDX-2\.\d", text))
checks["DataLicense"] = "DataLicense: CC0-1.0" in text
checks["SPDXID"] = "SPDXID: SPDXRef-DOCUMENT" in text
checks["DocumentNamespace"] = "DocumentNamespace:" in text
checks["Creator"] = "Creator:" in text
checks["Created"] = "Created:" in text
pkg_names = re.findall(r"PackageName:\s*(.+)", text)
components_found = len(pkg_names)
checks["packages"] = components_found > 0
checks["PackageVersion"] = "PackageVersion:" in text
checks["PackageSupplier"] = "PackageSupplier:" in text
checks["PackageLicense"] = "PackageLicenseConcluded:" in text
checks["PackageChecksum"] = "PackageChecksum:" in text
# Try CycloneDX XML
elif "<bom" in text and "cyclonedx" in text.lower():
detected = "CycloneDX XML"
checks["namespace"] = "cyclonedx.org" in text
checks["serialNumber"] = 'serialNumber="' in text
checks["metadata"] = "<metadata>" in text
checks["timestamp"] = "<timestamp>" in text
checks["tools"] = "<tools>" in text
checks["components"] = "<components>" in text
comp_matches = re.findall(r"<component", text)
components_found = len(comp_matches)
checks["component.name"] = "<name>" in text
checks["component.version"] = "<version>" in text
checks["component.purl"] = "<purl>" in text
checks["component.license"] = "<license>" in text or "<licenses>" in text
checks["component.hash"] = "<hash" in text
else:
return "Unrecognized SBOM format. Supported: CycloneDX JSON/XML, SPDX JSON/Tag-Value.", ""
passed = sum(1 for v in checks.values() if v)
total = len(checks)
pct = int(100 * passed / total) if total else 0
is_pass = pct >= 80
# NTIA minimum elements
ntia = {
"Supplier Name": checks.get("package.supplier", checks.get("PackageSupplier", checks.get("metadata.tools", False))),
"Component Name": checks.get("component.name", checks.get("package.name", checks.get("packages", False))),
"Component Version": checks.get("component.version", checks.get("package.version", checks.get("PackageVersion", False))),
"Unique Identifier": checks.get("component.purl", checks.get("SPDXID", checks.get("serialNumber", False))),
"Dependency Relationship": checks.get("dependencies", checks.get("relationships", False)),
"Author of SBOM Data": checks.get("metadata.tools", checks.get("creators", checks.get("Creator", False))),
"Timestamp": checks.get("metadata.timestamp", checks.get("creationInfo", checks.get("Created", False))),
}
# Build result
status_icon = "PASS" if is_pass else "FAIL"
result_msg = t["result_pass"] if is_pass else t["result_fail"]
result = f"**{status_icon}** - {result_msg}\n\n"
result += f"**Format**: {detected}\n"
result += f"**{t['components']}**: {components_found}\n"
result += f"**{t['score']}**: {pct}% ({passed}/{total})\n"
# Details
details = "### Validation checks\n\n"
for k, v in checks.items():
mark = "PASS" if v else "FAIL"
details += f"- [{mark}] {k}\n"
failed = [k for k, v in checks.items() if not v]
if failed:
details += f"\n### {t['missing']}\n\n"
for f_item in failed:
details += f"- {f_item}\n"
details += f"\n### {t['ntia_title']}\n\n"
for k, v in ntia.items():
mark = "PASS" if v else "FAIL"
details += f"- [{mark}] {k}\n"
if not is_pass:
details += f"\n### {t['recommendations']}\n\n"
if not checks.get("component.hash", checks.get("PackageChecksum", checks.get("component.hash", True))):
details += "- Add SHA-256 checksums to all components\n"
if not checks.get("component.license", checks.get("package.license", checks.get("PackageLicense", True))):
details += "- Add SPDX license identifiers to all components\n"
if not checks.get("component.purl", checks.get("SPDXID", True)):
details += "- Add Package URLs (purl) for unique identification\n"
if not checks.get("dependencies", checks.get("relationships", True)):
details += "- Include dependency relationships\n"
return result, details
# ---------------------------------------------------------------------------
# Static content builders
# ---------------------------------------------------------------------------
def build_regulations(lang):
if lang == "fr":
return """## Exigences SBOM par regulation
| Regulation | Exigence | Format attendu | Echeance | Sanction | Perimetre |
|---|---|---|---|---|---|
| **EU Cyber Resilience Act (CRA)** | SBOM obligatoire pour tout produit contenant des elements numeriques | CycloneDX ou SPDX | 2027 (application) | Jusqu'a 15M EUR ou 2,5% du CA mondial | Tous produits avec elements numeriques vendus dans l'UE |
| **NIS 2** | Securite de la chaine d'approvisionnement, inventaire logiciel | Non specifie (CycloneDX/SPDX recommande) | Octobre 2024 (transposition) | Jusqu'a 10M EUR ou 2% du CA | Entites essentielles et importantes (17 secteurs) |
| **DORA** | Gestion des risques lies aux tiers TIC, registre des actifs | Non specifie | Janvier 2025 | Sanctions administratives | Entites financieres de l'UE |
| **US Executive Order 14028** | SBOM pour tout logiciel vendu au gouvernement federal | SPDX ou CycloneDX (NTIA minimum elements) | En vigueur | Exclusion des marches federaux | Fournisseurs du gouvernement federal US |
| **FDA (Medical Devices)** | SBOM obligatoire pour les dispositifs medicaux connectes | CycloneDX ou SPDX | En vigueur (2023) | Refus de mise sur le marche | Dispositifs medicaux avec composants logiciels |
| **PCI DSS 4.0** | Inventaire des composants logiciels, gestion des vulnerabilites | Non specifie | Mars 2025 (exigences futures) | Non-conformite PCI | Organisations traitant des donnees de cartes de paiement |
"""
return """## SBOM Requirements by Regulation
| Regulation | Requirement | Expected Format | Deadline | Penalty | Scope |
|---|---|---|---|---|---|
| **EU Cyber Resilience Act (CRA)** | Mandatory SBOM for all products with digital elements | CycloneDX or SPDX | 2027 (enforcement) | Up to 15M EUR or 2.5% global turnover | All products with digital elements sold in the EU |
| **NIS 2** | Supply chain security, software inventory | Not specified (CycloneDX/SPDX recommended) | October 2024 (transposition) | Up to 10M EUR or 2% turnover | Essential and important entities (17 sectors) |
| **DORA** | ICT third-party risk management, asset registry | Not specified | January 2025 | Administrative sanctions | EU financial entities |
| **US Executive Order 14028** | SBOM for all software sold to federal government | SPDX or CycloneDX (NTIA minimum elements) | In force | Exclusion from federal contracts | US federal government suppliers |
| **FDA (Medical Devices)** | Mandatory SBOM for connected medical devices | CycloneDX or SPDX | In force (2023) | Market authorization refusal | Medical devices with software components |
| **PCI DSS 4.0** | Software component inventory, vulnerability management | Not specified | March 2025 (future requirements) | PCI non-compliance | Organizations processing payment card data |
"""
def build_comparison(lang):
if lang == "fr":
return """## Comparaison des formats SBOM
| Critere | CycloneDX | SPDX | SWID |
|---|---|---|---|
| **Organisation** | OWASP | Linux Foundation | ISO/IEC 19770-2 |
| **Version actuelle** | 1.6 | 2.3 | 2016 |
| **Formats** | JSON, XML, Protobuf | JSON, RDF, Tag-Value, XLSX | XML |
| **Licence du schema** | Apache-2.0 | CC-BY-3.0 | ISO Standard |
| **Focus principal** | Securite, vulnerabilites | Conformite licences | Identification logicielle |
| **Composants imbriques** | Oui (natif) | Oui (via relations) | Limite |
| **Vulnerabilites (VEX)** | Integre (CycloneDX VEX) | Via SPDX Security | Non |
| **Services & API** | Oui | Non | Non |
| **Licences SPDX** | Oui | Oui (natif) | Non |
| **PURL (Package URL)** | Oui (natif) | Oui | Non |
| **Signature numerique** | Oui (JSF, XML Sig) | Non | Oui (XML Sig) |
| **Machine readable** | Oui | Oui | Oui |
| **Adoption industrie** | Elevee (securite) | Elevee (open source) | Faible |
| **NTIA conforme** | Oui | Oui | Partiel |
| **CRA conforme** | Oui | Oui | Non recommande |
### Recommandations par cas d'usage
- **Securite et gestion des vulnerabilites** : CycloneDX (VEX integre, focus securite)
- **Conformite open source et licences** : SPDX (standard Linux Foundation, focus licences)
- **Inventaire IT et gestion des actifs** : SWID (standard ISO, identification)
- **Conformite CRA / NIS 2** : CycloneDX ou SPDX (les deux sont acceptes)
- **Marches publics US** : SPDX ou CycloneDX (NTIA minimum elements)
"""
return """## SBOM Format Comparison
| Criteria | CycloneDX | SPDX | SWID |
|---|---|---|---|
| **Organization** | OWASP | Linux Foundation | ISO/IEC 19770-2 |
| **Current version** | 1.6 | 2.3 | 2016 |
| **Formats** | JSON, XML, Protobuf | JSON, RDF, Tag-Value, XLSX | XML |
| **Schema license** | Apache-2.0 | CC-BY-3.0 | ISO Standard |
| **Primary focus** | Security, vulnerabilities | License compliance | Software identification |
| **Nested components** | Yes (native) | Yes (via relationships) | Limited |
| **Vulnerabilities (VEX)** | Built-in (CycloneDX VEX) | Via SPDX Security | No |
| **Services & API** | Yes | No | No |
| **SPDX licenses** | Yes | Yes (native) | No |
| **PURL (Package URL)** | Yes (native) | Yes | No |
| **Digital signature** | Yes (JSF, XML Sig) | No | Yes (XML Sig) |
| **Machine readable** | Yes | Yes | Yes |
| **Industry adoption** | High (security) | High (open source) | Low |
| **NTIA compliant** | Yes | Yes | Partial |
| **CRA compliant** | Yes | Yes | Not recommended |
### Recommendations by Use Case
- **Security and vulnerability management**: CycloneDX (built-in VEX, security focus)
- **Open source and license compliance**: SPDX (Linux Foundation standard, license focus)
- **IT inventory and asset management**: SWID (ISO standard, identification)
- **CRA / NIS 2 compliance**: CycloneDX or SPDX (both accepted)
- **US federal procurement**: SPDX or CycloneDX (NTIA minimum elements)
"""
def build_tools(lang):
if lang == "fr":
header = "## Ecosysteme des outils SBOM\n\n"
else:
header = "## SBOM Tool Ecosystem\n\n"
cat_gen = "Generation" if lang == "fr" else "Generation"
cat_ana = "Analyse" if lang == "fr" else "Analysis"
cat_scan = "Scan de vulnerabilites" if lang == "fr" else "Vulnerability Scanning"
cat_mgmt = "Gestion" if lang == "fr" else "Management"
tools = [
(cat_gen, "Syft", "SBOM generation from container images and filesystems", "CycloneDX, SPDX", "Open Source (Apache-2.0)", "https://github.com/anchore/syft"),
(cat_gen, "cdxgen", "CycloneDX SBOM generator for many languages", "CycloneDX", "Open Source (Apache-2.0)", "https://github.com/CycloneDX/cdxgen"),
(cat_gen, "Microsoft sbom-tool", "SBOM generation tool by Microsoft", "SPDX", "Open Source (MIT)", "https://github.com/microsoft/sbom-tool"),
(cat_gen, "SPDX Tools", "Official SPDX format tools and libraries", "SPDX", "Open Source", "https://github.com/spdx/tools-java"),
(cat_ana, "Grype", "Vulnerability scanner for container images and SBOM", "CycloneDX, SPDX", "Open Source (Apache-2.0)", "https://github.com/anchore/grype"),
(cat_ana, "Dependency-Track", "Intelligent component analysis platform", "CycloneDX", "Open Source (Apache-2.0)", "https://dependencytrack.org/"),
(cat_ana, "GUAC", "Graph for Understanding Artifact Composition", "CycloneDX, SPDX", "Open Source (Apache-2.0)", "https://guac.sh/"),
(cat_ana, "OSS Review Toolkit", "Suite for open source review and compliance", "SPDX, CycloneDX", "Open Source (Apache-2.0)", "https://github.com/oss-review-toolkit/ort"),
(cat_scan, "Trivy", "All-in-one security scanner (containers, IaC, SBOM)", "CycloneDX, SPDX", "Open Source (Apache-2.0)", "https://trivy.dev/"),
(cat_scan, "Snyk", "Developer-first security platform", "CycloneDX, SPDX", "Freemium / Enterprise", "https://snyk.io/"),
(cat_scan, "Black Duck", "Software composition analysis (SCA)", "SPDX, CycloneDX", "Commercial", "https://www.blackducksoftware.com/"),
(cat_scan, "Mend (WhiteSource)", "Open source security and compliance", "CycloneDX, SPDX", "Freemium / Enterprise", "https://www.mend.io/"),
(cat_mgmt, "OWASP Dependency-Track", "Component analysis and SBOM management", "CycloneDX", "Open Source (Apache-2.0)", "https://dependencytrack.org/"),
(cat_mgmt, "JFrog Xray", "Universal artifact analysis and SBOM management", "CycloneDX, SPDX", "Commercial", "https://jfrog.com/xray/"),
(cat_mgmt, "Sonatype Nexus Lifecycle", "Software supply chain management", "CycloneDX, SPDX", "Commercial", "https://www.sonatype.com/products/open-source-security-dependency-management"),
]
if lang == "fr":
table = "| Categorie | Outil | Description | Formats | Tarification | Lien |\n"
else:
table = "| Category | Tool | Description | Formats | Pricing | Link |\n"
table += "|---|---|---|---|---|---|\n"
for cat, name, desc, fmts, price, link in tools:
table += f"| {cat} | **{name}** | {desc} | {fmts} | {price} | [Site]({link}) |\n"
return header + table
def build_resources(lang):
if lang == "fr":
return """## Ressources et articles
### AYI-NEDJIMI Consultants - Expertise conformite et cybersecurite
- [SBOM 2026 : obligation et securite de la chaine logicielle](https://ayinedjimi-consultants.fr/articles/conformite/sbom-2026-obligation-securite.html) - Guide complet sur les obligations SBOM a venir et les bonnes pratiques de securite de la chaine d'approvisionnement logicielle.
- [Cyber Resilience Act 2026 : se preparer a la conformite](https://ayinedjimi-consultants.fr/articles/conformite/cyber-resilience-act-2026.html) - Tout ce qu'il faut savoir sur le CRA europeen et comment preparer votre organisation.
- [AYI-NEDJIMI Consultants - Accueil](https://ayinedjimi-consultants.fr) - Cabinet de conseil specialise en conformite reglementaire, cybersecurite et gouvernance des donnees.
### Standards et specifications
- [CycloneDX Specification](https://cyclonedx.org/specification/overview/) - Specification officielle du format CycloneDX (OWASP).
- [SPDX Specification](https://spdx.github.io/spdx-spec/v2.3/) - Specification officielle du format SPDX (Linux Foundation).
- [NTIA SBOM Minimum Elements](https://www.ntia.gov/page/software-bill-materials) - Elements minimaux NTIA pour les SBOM.
- [EU Cyber Resilience Act](https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act) - Texte officiel du CRA europeen.
### Communaute
- [OWASP Software Component Verification Standard](https://owasp.org/www-project-software-component-verification-standard/) - Standard OWASP pour la verification des composants logiciels.
- [OpenSSF Scorecard](https://securityscorecards.dev/) - Evaluation automatisee de la securite des projets open source.
"""
return """## Resources and Articles
### AYI-NEDJIMI Consultants - Compliance and Cybersecurity Expertise
- [SBOM 2026: Obligation and Software Supply Chain Security](https://ayinedjimi-consultants.fr/articles/conformite/sbom-2026-obligation-securite.html) - Comprehensive guide on upcoming SBOM obligations and software supply chain security best practices.
- [Cyber Resilience Act 2026: Preparing for Compliance](https://ayinedjimi-consultants.fr/articles/conformite/cyber-resilience-act-2026.html) - Everything you need to know about the European CRA and how to prepare your organization.
- [AYI-NEDJIMI Consultants - Home](https://ayinedjimi-consultants.fr) - Consulting firm specialized in regulatory compliance, cybersecurity, and data governance.
### Standards and Specifications
- [CycloneDX Specification](https://cyclonedx.org/specification/overview/) - Official CycloneDX format specification (OWASP).
- [SPDX Specification](https://spdx.github.io/spdx-spec/v2.3/) - Official SPDX format specification (Linux Foundation).
- [NTIA SBOM Minimum Elements](https://www.ntia.gov/page/software-bill-materials) - NTIA minimum elements for SBOMs.
- [EU Cyber Resilience Act](https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act) - Official EU CRA text.
### Community
- [OWASP Software Component Verification Standard](https://owasp.org/www-project-software-component-verification-standard/) - OWASP standard for software component verification.
- [OpenSSF Scorecard](https://securityscorecards.dev/) - Automated security assessment for open source projects.
"""
# ---------------------------------------------------------------------------
# Gradio UI
# ---------------------------------------------------------------------------
FOOTER_HTML = (
'<div style="text-align:center; margin-top:24px; padding:16px; '
'border-top:1px solid #ddd; color:#666; font-size:0.9em;">'
'Powered by <a href="https://ayinedjimi-consultants.fr" target="_blank">'
'AYI-NEDJIMI Consultants</a> | '
'<a href="https://ayinedjimi-consultants.fr/articles/conformite/'
'sbom-2026-obligation-securite.html" target="_blank">SBOM 2026 Guide</a> | '
'<a href="https://ayinedjimi-consultants.fr/articles/conformite/'
'cyber-resilience-act-2026.html" target="_blank">Cyber Resilience Act</a>'
'</div>'
)
def build_app():
with gr.Blocks(
title="SBOM Generator & Validator",
theme=gr.themes.Soft(),
css=".footer-link a { color: #2563eb; text-decoration: none; }"
) as demo:
lang_state = gr.State("fr")
gr.Markdown("# Generateur & Validateur SBOM")
gr.Markdown("Creez et validez vos Software Bill of Materials (SBOM) conformes CycloneDX & SPDX")
lang_btn = gr.Button("English", variant="secondary", elem_id="lang-btn")
# ------------------------------------------------------------------
# Tab 1: Generator
# ------------------------------------------------------------------
with gr.Tab("Generateur SBOM"):
with gr.Row():
with gr.Column():
gen_input = gr.Textbox(
label="Contenu du fichier de dependances",
placeholder="Collez ici le contenu de requirements.txt, package.json, pom.xml ou go.mod ...",
lines=12,
)
gen_format = gr.Dropdown(
choices=["CycloneDX JSON", "CycloneDX XML", "SPDX JSON", "SPDX Tag-Value"],
value="CycloneDX JSON",
label="Format de sortie",
)
gen_btn = gr.Button("Generer le SBOM", variant="primary")
with gr.Column():
gen_stats = gr.Markdown(label="Statistiques")
gen_output = gr.Textbox(label="Document SBOM genere", lines=20, show_copy_button=True)
gen_file = gr.File(label="Telecharger le SBOM")
gen_btn.click(
fn=generate_sbom,
inputs=[gen_input, gen_format, lang_state],
outputs=[gen_output, gen_stats, gen_file],
)
# ------------------------------------------------------------------
# Tab 2: Validator
# ------------------------------------------------------------------
with gr.Tab("Validateur SBOM"):
with gr.Row():
with gr.Column():
val_input = gr.Textbox(
label="Document SBOM a valider",
placeholder="Collez ici un document SBOM (CycloneDX JSON/XML ou SPDX JSON/Tag-Value) ...",
lines=15,
)
val_btn = gr.Button("Valider le SBOM", variant="primary")
with gr.Column():
val_result = gr.Markdown(label="Resultat de la validation")
val_details = gr.Markdown(label="Details et recommandations")
val_btn.click(
fn=validate_sbom,
inputs=[val_input, lang_state],
outputs=[val_result, val_details],
)
# ------------------------------------------------------------------
# Tab 3: Regulations
# ------------------------------------------------------------------
with gr.Tab("Exigences reglementaires"):
reg_md = gr.Markdown(value=build_regulations("fr"))
# ------------------------------------------------------------------
# Tab 4: Format Comparison
# ------------------------------------------------------------------
with gr.Tab("Comparaison de formats"):
cmp_md = gr.Markdown(value=build_comparison("fr"))
# ------------------------------------------------------------------
# Tab 5: Tool Ecosystem
# ------------------------------------------------------------------
with gr.Tab("Ecosysteme outils"):
tools_md = gr.Markdown(value=build_tools("fr"))
# ------------------------------------------------------------------
# Tab 6: Resources
# ------------------------------------------------------------------
with gr.Tab("Ressources"):
res_md = gr.Markdown(value=build_resources("fr"))
# ------------------------------------------------------------------
# Footer
# ------------------------------------------------------------------
gr.HTML(FOOTER_HTML)
# ------------------------------------------------------------------
# Language toggle
# ------------------------------------------------------------------
def toggle_language(current_lang):
new_lang = "en" if current_lang == "fr" else "fr"
t = TR[new_lang]
return (
new_lang,
t["lang_btn"],
build_regulations(new_lang),
build_comparison(new_lang),
build_tools(new_lang),
build_resources(new_lang),
)
lang_btn.click(
fn=toggle_language,
inputs=[lang_state],
outputs=[lang_state, lang_btn, reg_md, cmp_md, tools_md, res_md],
)
return demo
if __name__ == "__main__":
demo = build_app()
demo.launch(server_name="0.0.0.0", server_port=7860)