AYI-NEDJIMI commited on
Commit
e6a2523
·
verified ·
1 Parent(s): 193687d

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. README.md +31 -6
  2. app.py +864 -0
  3. requirements.txt +2 -0
README.md CHANGED
@@ -1,12 +1,37 @@
1
  ---
2
- title: Sbom Generator
3
- emoji: ⚡
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 6.5.1
8
  app_file: app.py
9
  pinned: false
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: SBOM Generator & Validator
3
+ emoji: "\U0001F4E6"
4
+ colorFrom: green
5
+ colorTo: blue
6
  sdk: gradio
7
+ sdk_version: "4.44.0"
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
+ tags:
12
+ - cybersecurity
13
+ - sbom
14
+ - software-supply-chain
15
+ - cyclonedx
16
+ - spdx
17
+ - compliance
18
+ - ayinedjimi-consultants
19
  ---
20
 
21
+ # SBOM Generator & Validator
22
+
23
+ Generate and validate Software Bill of Materials (SBOM) documents in CycloneDX and SPDX formats.
24
+
25
+ ## Features
26
+
27
+ - **SBOM Generator**: Parse requirements.txt, package.json, pom.xml, or go.mod and generate SBOM in CycloneDX JSON/XML or SPDX JSON/Tag-Value formats
28
+ - **SBOM Validator**: Validate SBOM documents against format specifications and NTIA minimum elements
29
+ - **Regulatory Requirements**: Overview of SBOM regulations (CRA, NIS 2, DORA, EO 14028, FDA, PCI DSS 4.0)
30
+ - **Format Comparison**: Side-by-side comparison of CycloneDX, SPDX, and SWID formats
31
+ - **Tool Ecosystem**: Reference of 15+ SBOM tools for generation, analysis, scanning, and management
32
+
33
+ ## Links
34
+
35
+ - [AYI-NEDJIMI Consultants](https://ayinedjimi-consultants.fr)
36
+ - [SBOM 2026 Guide](https://ayinedjimi-consultants.fr/articles/conformite/sbom-2026-obligation-securite.html)
37
+ - [Cyber Resilience Act 2026](https://ayinedjimi-consultants.fr/articles/conformite/cyber-resilience-act-2026.html)
app.py ADDED
@@ -0,0 +1,864 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SBOM Generator & Validator - Interactive Gradio Space
3
+ Powered by AYI-NEDJIMI Consultants | https://ayinedjimi-consultants.fr
4
+ """
5
+
6
+ import gradio as gr
7
+ import json
8
+ import hashlib
9
+ import uuid
10
+ import re
11
+ from datetime import datetime, timezone
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Translations
15
+ # ---------------------------------------------------------------------------
16
+ TR = {
17
+ "fr": {
18
+ "title": "Generateur & Validateur SBOM",
19
+ "subtitle": "Creez et validez vos Software Bill of Materials (SBOM) conformes CycloneDX & SPDX",
20
+ "lang_btn": "English",
21
+ "tab_gen": "Generateur SBOM",
22
+ "tab_val": "Validateur SBOM",
23
+ "tab_reg": "Exigences reglementaires",
24
+ "tab_cmp": "Comparaison de formats",
25
+ "tab_tools": "Ecosysteme outils",
26
+ "tab_res": "Ressources",
27
+ "input_label": "Contenu du fichier de dependances",
28
+ "input_placeholder": "Collez ici le contenu de requirements.txt, package.json, pom.xml ou go.mod ...",
29
+ "format_label": "Format de sortie",
30
+ "generate_btn": "Generer le SBOM",
31
+ "output_label": "Document SBOM genere",
32
+ "stats_label": "Statistiques",
33
+ "val_input_label": "Document SBOM a valider",
34
+ "val_input_placeholder": "Collez ici un document SBOM (CycloneDX JSON/XML ou SPDX JSON/Tag-Value) ...",
35
+ "validate_btn": "Valider le SBOM",
36
+ "val_result_label": "Resultat de la validation",
37
+ "val_details_label": "Details et recommandations",
38
+ "no_input": "Veuillez fournir un contenu de fichier de dependances.",
39
+ "no_sbom": "Veuillez fournir un document SBOM a valider.",
40
+ "parsed_ok": "composants detectes",
41
+ "gen_ok": "SBOM genere avec succes",
42
+ "components": "Composants",
43
+ "licenses": "Distribution des licences",
44
+ "format_version": "Version du format",
45
+ "score": "Score de conformite",
46
+ "result_pass": "SUCCES - Le SBOM est conforme",
47
+ "result_fail": "ECHEC - Le SBOM presente des problemes",
48
+ "missing": "Champs manquants",
49
+ "recommendations": "Recommandations",
50
+ "ntia_title": "Checklist NTIA Minimum Elements",
51
+ "reg_title": "Exigences SBOM par regulation",
52
+ "cmp_title": "Comparaison des formats SBOM",
53
+ "tools_title": "Ecosysteme des outils SBOM",
54
+ "res_title": "Ressources et articles",
55
+ "footer": "Propulse par",
56
+ },
57
+ "en": {
58
+ "title": "SBOM Generator & Validator",
59
+ "subtitle": "Create and validate your Software Bill of Materials (SBOM) compliant with CycloneDX & SPDX",
60
+ "lang_btn": "Francais",
61
+ "tab_gen": "SBOM Generator",
62
+ "tab_val": "SBOM Validator",
63
+ "tab_reg": "Regulatory Requirements",
64
+ "tab_cmp": "Format Comparison",
65
+ "tab_tools": "Tool Ecosystem",
66
+ "tab_res": "Resources",
67
+ "input_label": "Dependency file content",
68
+ "input_placeholder": "Paste requirements.txt, package.json, pom.xml or go.mod content here ...",
69
+ "format_label": "Output format",
70
+ "generate_btn": "Generate SBOM",
71
+ "output_label": "Generated SBOM document",
72
+ "stats_label": "Statistics",
73
+ "val_input_label": "SBOM document to validate",
74
+ "val_input_placeholder": "Paste a SBOM document (CycloneDX JSON/XML or SPDX JSON/Tag-Value) here ...",
75
+ "validate_btn": "Validate SBOM",
76
+ "val_result_label": "Validation result",
77
+ "val_details_label": "Details and recommendations",
78
+ "no_input": "Please provide dependency file content.",
79
+ "no_sbom": "Please provide an SBOM document to validate.",
80
+ "parsed_ok": "components detected",
81
+ "gen_ok": "SBOM generated successfully",
82
+ "components": "Components",
83
+ "licenses": "License distribution",
84
+ "format_version": "Format version",
85
+ "score": "Compliance score",
86
+ "result_pass": "PASS - SBOM is compliant",
87
+ "result_fail": "FAIL - SBOM has issues",
88
+ "missing": "Missing fields",
89
+ "recommendations": "Recommendations",
90
+ "ntia_title": "NTIA Minimum Elements Checklist",
91
+ "reg_title": "SBOM requirements by regulation",
92
+ "cmp_title": "SBOM Format Comparison",
93
+ "tools_title": "SBOM Tool Ecosystem",
94
+ "res_title": "Resources and articles",
95
+ "footer": "Powered by",
96
+ },
97
+ }
98
+
99
+ KNOWN_LICENSES = {
100
+ "MIT": "MIT", "Apache-2.0": "Apache-2.0", "BSD-3-Clause": "BSD-3-Clause",
101
+ "BSD-2-Clause": "BSD-2-Clause", "GPL-3.0-only": "GPL-3.0-only",
102
+ "GPL-2.0-only": "GPL-2.0-only", "LGPL-3.0-only": "LGPL-3.0-only",
103
+ "ISC": "ISC", "MPL-2.0": "MPL-2.0", "AGPL-3.0-only": "AGPL-3.0-only",
104
+ "Unlicense": "Unlicense", "0BSD": "0BSD",
105
+ }
106
+
107
+ DEFAULT_LICENSE_MAP = {
108
+ "flask": "BSD-3-Clause", "django": "BSD-3-Clause", "requests": "Apache-2.0",
109
+ "numpy": "BSD-3-Clause", "pandas": "BSD-3-Clause", "fastapi": "MIT",
110
+ "gradio": "Apache-2.0", "express": "MIT", "react": "MIT", "lodash": "MIT",
111
+ "axios": "MIT", "vue": "MIT", "typescript": "Apache-2.0", "webpack": "MIT",
112
+ "spring-boot-starter-web": "Apache-2.0", "junit-jupiter": "EPL-2.0",
113
+ "lombok": "MIT", "gin": "MIT", "cobra": "Apache-2.0", "logrus": "MIT",
114
+ "pytest": "MIT", "black": "MIT", "uvicorn": "BSD-3-Clause",
115
+ "sqlalchemy": "MIT", "pydantic": "MIT", "boto3": "Apache-2.0",
116
+ "tensorflow": "Apache-2.0", "torch": "BSD-3-Clause", "scipy": "BSD-3-Clause",
117
+ "scikit-learn": "BSD-3-Clause", "matplotlib": "PSF-2.0",
118
+ }
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # Parsers
122
+ # ---------------------------------------------------------------------------
123
+
124
+ def _sim_hash(name: str, version: str) -> str:
125
+ return hashlib.sha256(f"{name}@{version}".encode()).hexdigest()
126
+
127
+
128
+ def _guess_license(name: str) -> str:
129
+ key = name.lower().replace("_", "-")
130
+ return DEFAULT_LICENSE_MAP.get(key, "MIT")
131
+
132
+
133
+ def parse_requirements(text: str):
134
+ components = []
135
+ for line in text.strip().splitlines():
136
+ line = line.strip()
137
+ if not line or line.startswith("#"):
138
+ continue
139
+ m = re.match(r"^([A-Za-z0-9_\-\.]+)\s*[=~<>!]=?\s*([0-9][^\s,;]*)?", line)
140
+ if m:
141
+ name = m.group(1)
142
+ version = m.group(2) or "0.0.0"
143
+ components.append({"name": name, "version": version,
144
+ "license": _guess_license(name), "type": "library",
145
+ "purl": f"pkg:pypi/{name}@{version}"})
146
+ return components
147
+
148
+
149
+ def parse_package_json(text: str):
150
+ components = []
151
+ try:
152
+ data = json.loads(text)
153
+ except json.JSONDecodeError:
154
+ return components
155
+ deps = {}
156
+ deps.update(data.get("dependencies", {}))
157
+ deps.update(data.get("devDependencies", {}))
158
+ for name, ver in deps.items():
159
+ version = re.sub(r"[^0-9\.]", "", ver) or "0.0.0"
160
+ components.append({"name": name, "version": version,
161
+ "license": _guess_license(name), "type": "library",
162
+ "purl": f"pkg:npm/{name}@{version}"})
163
+ return components
164
+
165
+
166
+ def parse_pom_xml(text: str):
167
+ components = []
168
+ pattern = r"<dependency>\s*<groupId>([^<]+)</groupId>\s*<artifactId>([^<]+)</artifactId>\s*<version>([^<]+)</version>"
169
+ for m in re.finditer(pattern, text, re.DOTALL):
170
+ group, artifact, version = m.group(1), m.group(2), m.group(3)
171
+ components.append({"name": f"{group}:{artifact}", "version": version,
172
+ "license": _guess_license(artifact), "type": "library",
173
+ "purl": f"pkg:maven/{group}/{artifact}@{version}"})
174
+ return components
175
+
176
+
177
+ def parse_go_mod(text: str):
178
+ components = []
179
+ for line in text.strip().splitlines():
180
+ line = line.strip()
181
+ m = re.match(r"^([\w\.\-/]+)\s+(v?[\d\.]+)", line)
182
+ if m and "/" in m.group(1):
183
+ mod = m.group(1)
184
+ version = m.group(2)
185
+ short = mod.split("/")[-1]
186
+ components.append({"name": mod, "version": version,
187
+ "license": _guess_license(short), "type": "library",
188
+ "purl": f"pkg:golang/{mod}@{version}"})
189
+ return components
190
+
191
+
192
+ def auto_parse(text: str):
193
+ text = text.strip()
194
+ if not text:
195
+ return []
196
+ if text.lstrip().startswith("{"):
197
+ comps = parse_package_json(text)
198
+ if comps:
199
+ return comps
200
+ if "<dependency>" in text:
201
+ comps = parse_pom_xml(text)
202
+ if comps:
203
+ return comps
204
+ if "module " in text or "require (" in text or "go " in text:
205
+ comps = parse_go_mod(text)
206
+ if comps:
207
+ return comps
208
+ return parse_requirements(text)
209
+
210
+ # ---------------------------------------------------------------------------
211
+ # SBOM generators
212
+ # ---------------------------------------------------------------------------
213
+ NOW_ISO = lambda: datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
214
+
215
+
216
+ def gen_cyclonedx_json(components):
217
+ bom = {
218
+ "bomFormat": "CycloneDX",
219
+ "specVersion": "1.5",
220
+ "serialNumber": f"urn:uuid:{uuid.uuid4()}",
221
+ "version": 1,
222
+ "metadata": {
223
+ "timestamp": NOW_ISO(),
224
+ "tools": [{"vendor": "AYI-NEDJIMI Consultants", "name": "sbom-generator", "version": "1.0.0"}],
225
+ "component": {"type": "application", "name": "my-application", "version": "1.0.0"},
226
+ },
227
+ "components": [],
228
+ "dependencies": [{"ref": "my-application", "dependsOn": []}],
229
+ }
230
+ for c in components:
231
+ comp = {
232
+ "type": c["type"],
233
+ "name": c["name"],
234
+ "version": c["version"],
235
+ "purl": c["purl"],
236
+ "licenses": [{"license": {"id": c["license"]}}],
237
+ "hashes": [{"alg": "SHA-256", "content": _sim_hash(c["name"], c["version"])}],
238
+ }
239
+ bom["components"].append(comp)
240
+ bom["dependencies"][0]["dependsOn"].append(c["purl"])
241
+ return json.dumps(bom, indent=2)
242
+
243
+
244
+ def gen_cyclonedx_xml(components):
245
+ ts = NOW_ISO()
246
+ serial = f"urn:uuid:{uuid.uuid4()}"
247
+ lines = [
248
+ '<?xml version="1.0" encoding="UTF-8"?>',
249
+ '<bom xmlns="http://cyclonedx.org/schema/bom/1.5" serialNumber="' + serial + '" version="1">',
250
+ " <metadata>",
251
+ f" <timestamp>{ts}</timestamp>",
252
+ " <tools><tool><vendor>AYI-NEDJIMI Consultants</vendor><name>sbom-generator</name><version>1.0.0</version></tool></tools>",
253
+ ' <component type="application"><name>my-application</name><version>1.0.0</version></component>',
254
+ " </metadata>",
255
+ " <components>",
256
+ ]
257
+ refs = []
258
+ for c in components:
259
+ h = _sim_hash(c["name"], c["version"])
260
+ lines.append(f' <component type="{c["type"]}">')
261
+ lines.append(f" <name>{c['name']}</name>")
262
+ lines.append(f" <version>{c['version']}</version>")
263
+ lines.append(f" <purl>{c['purl']}</purl>")
264
+ lines.append(f' <licenses><license><id>{c["license"]}</id></license></licenses>')
265
+ lines.append(f' <hashes><hash alg="SHA-256">{h}</hash></hashes>')
266
+ lines.append(" </component>")
267
+ refs.append(c["purl"])
268
+ lines.append(" </components>")
269
+ lines.append(" <dependencies>")
270
+ lines.append(' <dependency ref="my-application">')
271
+ for r in refs:
272
+ lines.append(f' <dependency ref="{r}"/>')
273
+ lines.append(" </dependency>")
274
+ lines.append(" </dependencies>")
275
+ lines.append("</bom>")
276
+ return "\n".join(lines)
277
+
278
+
279
+ def gen_spdx_json(components):
280
+ doc_ns = f"https://spdx.org/spdxdocs/sbom-generator-{uuid.uuid4()}"
281
+ doc = {
282
+ "spdxVersion": "SPDX-2.3",
283
+ "dataLicense": "CC0-1.0",
284
+ "SPDXID": "SPDXRef-DOCUMENT",
285
+ "name": "my-application",
286
+ "documentNamespace": doc_ns,
287
+ "creationInfo": {
288
+ "created": NOW_ISO(),
289
+ "creators": ["Tool: sbom-generator-1.0.0", "Organization: AYI-NEDJIMI Consultants"],
290
+ },
291
+ "packages": [],
292
+ "relationships": [],
293
+ }
294
+ for i, c in enumerate(components):
295
+ spdx_id = f"SPDXRef-Package-{i+1}"
296
+ pkg = {
297
+ "SPDXID": spdx_id,
298
+ "name": c["name"],
299
+ "versionInfo": c["version"],
300
+ "downloadLocation": "https://example.com/download",
301
+ "supplier": "Organization: Open Source",
302
+ "licenseConcluded": c["license"],
303
+ "licenseDeclared": c["license"],
304
+ "copyrightText": "NOASSERTION",
305
+ "externalRefs": [
306
+ {"referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": c["purl"]}
307
+ ],
308
+ "checksums": [{"algorithm": "SHA256", "checksumValue": _sim_hash(c["name"], c["version"])}],
309
+ }
310
+ doc["packages"].append(pkg)
311
+ doc["relationships"].append({
312
+ "spdxElementId": "SPDXRef-DOCUMENT",
313
+ "relatedSpdxElement": spdx_id,
314
+ "relationshipType": "DESCRIBES",
315
+ })
316
+ return json.dumps(doc, indent=2)
317
+
318
+
319
+ def gen_spdx_tv(components):
320
+ ts = NOW_ISO()
321
+ doc_ns = f"https://spdx.org/spdxdocs/sbom-generator-{uuid.uuid4()}"
322
+ lines = [
323
+ "SPDXVersion: SPDX-2.3",
324
+ "DataLicense: CC0-1.0",
325
+ "SPDXID: SPDXRef-DOCUMENT",
326
+ "DocumentName: my-application",
327
+ f"DocumentNamespace: {doc_ns}",
328
+ f"Creator: Tool: sbom-generator-1.0.0",
329
+ f"Creator: Organization: AYI-NEDJIMI Consultants",
330
+ f"Created: {ts}",
331
+ "",
332
+ ]
333
+ for i, c in enumerate(components):
334
+ sid = f"SPDXRef-Package-{i+1}"
335
+ lines += [
336
+ f"PackageName: {c['name']}",
337
+ f"SPDXID: {sid}",
338
+ f"PackageVersion: {c['version']}",
339
+ "PackageDownloadLocation: https://example.com/download",
340
+ "PackageSupplier: Organization: Open Source",
341
+ f"PackageLicenseConcluded: {c['license']}",
342
+ f"PackageLicenseDeclared: {c['license']}",
343
+ "PackageCopyrightText: NOASSERTION",
344
+ f"PackageChecksum: SHA256: {_sim_hash(c['name'], c['version'])}",
345
+ f"ExternalRef: PACKAGE-MANAGER purl {c['purl']}",
346
+ f"Relationship: SPDXRef-DOCUMENT DESCRIBES {sid}",
347
+ "",
348
+ ]
349
+ return "\n".join(lines)
350
+
351
+
352
+ FORMAT_FUNCS = {
353
+ "CycloneDX JSON": gen_cyclonedx_json,
354
+ "CycloneDX XML": gen_cyclonedx_xml,
355
+ "SPDX JSON": gen_spdx_json,
356
+ "SPDX Tag-Value": gen_spdx_tv,
357
+ }
358
+
359
+ FORMAT_VERSIONS = {
360
+ "CycloneDX JSON": "CycloneDX 1.5",
361
+ "CycloneDX XML": "CycloneDX 1.5",
362
+ "SPDX JSON": "SPDX 2.3",
363
+ "SPDX Tag-Value": "SPDX 2.3",
364
+ }
365
+
366
+ # ---------------------------------------------------------------------------
367
+ # Generate handler
368
+ # ---------------------------------------------------------------------------
369
+
370
+ def generate_sbom(text: str, fmt: str, lang: str):
371
+ t = TR[lang]
372
+ if not text or not text.strip():
373
+ return t["no_input"], "", None
374
+ components = auto_parse(text)
375
+ if not components:
376
+ return t["no_input"], "", None
377
+ sbom_text = FORMAT_FUNCS[fmt](components)
378
+ lic_dist = {}
379
+ for c in components:
380
+ lic_dist[c["license"]] = lic_dist.get(c["license"], 0) + 1
381
+ lic_lines = ", ".join(f"{k}: {v}" for k, v in sorted(lic_dist.items(), key=lambda x: -x[1]))
382
+ stats = (
383
+ f"{t['gen_ok']}\n\n"
384
+ f"**{t['components']}**: {len(components)} {t['parsed_ok']}\n\n"
385
+ f"**{t['licenses']}**: {lic_lines}\n\n"
386
+ f"**{t['format_version']}**: {FORMAT_VERSIONS[fmt]}"
387
+ )
388
+ ext = {"CycloneDX JSON": ".json", "CycloneDX XML": ".xml",
389
+ "SPDX JSON": ".json", "SPDX Tag-Value": ".spdx"}[fmt]
390
+ tmp_path = f"/tmp/sbom{ext}"
391
+ with open(tmp_path, "w") as f:
392
+ f.write(sbom_text)
393
+ return sbom_text, stats, tmp_path
394
+
395
+ # ---------------------------------------------------------------------------
396
+ # Validator
397
+ # ---------------------------------------------------------------------------
398
+
399
+ def validate_sbom(text: str, lang: str):
400
+ t = TR[lang]
401
+ if not text or not text.strip():
402
+ return t["no_sbom"], ""
403
+ text = text.strip()
404
+ issues = []
405
+ checks = {}
406
+ detected = "Unknown"
407
+ components_found = 0
408
+ score = 0
409
+ total = 10
410
+
411
+ # Try CycloneDX JSON
412
+ if text.lstrip().startswith("{") and "bomFormat" in text:
413
+ detected = "CycloneDX JSON"
414
+ try:
415
+ data = json.loads(text)
416
+ except json.JSONDecodeError:
417
+ return "Invalid JSON", ""
418
+ checks["bomFormat"] = data.get("bomFormat") == "CycloneDX"
419
+ checks["specVersion"] = data.get("specVersion", "") in ("1.4", "1.5", "1.6")
420
+ checks["serialNumber"] = bool(data.get("serialNumber"))
421
+ checks["metadata.timestamp"] = bool(data.get("metadata", {}).get("timestamp"))
422
+ checks["metadata.tools"] = bool(data.get("metadata", {}).get("tools"))
423
+ checks["components"] = isinstance(data.get("components"), list) and len(data.get("components", [])) > 0
424
+ components_found = len(data.get("components", []))
425
+ comps = data.get("components", [])
426
+ all_have_name = all("name" in c for c in comps)
427
+ all_have_version = all("version" in c for c in comps)
428
+ all_have_purl = all("purl" in c for c in comps)
429
+ all_have_license = all(c.get("licenses") for c in comps)
430
+ all_have_hash = all(c.get("hashes") for c in comps)
431
+ checks["component.name"] = all_have_name
432
+ checks["component.version"] = all_have_version
433
+ checks["component.purl"] = all_have_purl
434
+ checks["component.license"] = all_have_license
435
+ checks["component.hash"] = all_have_hash
436
+ checks["dependencies"] = isinstance(data.get("dependencies"), list)
437
+
438
+ # Try SPDX JSON
439
+ elif text.lstrip().startswith("{") and "spdxVersion" in text:
440
+ detected = "SPDX JSON"
441
+ try:
442
+ data = json.loads(text)
443
+ except json.JSONDecodeError:
444
+ return "Invalid JSON", ""
445
+ checks["spdxVersion"] = data.get("spdxVersion", "").startswith("SPDX-2.")
446
+ checks["dataLicense"] = data.get("dataLicense") == "CC0-1.0"
447
+ checks["SPDXID"] = data.get("SPDXID") == "SPDXRef-DOCUMENT"
448
+ checks["documentNamespace"] = bool(data.get("documentNamespace"))
449
+ checks["creationInfo"] = bool(data.get("creationInfo", {}).get("created"))
450
+ checks["creators"] = bool(data.get("creationInfo", {}).get("creators"))
451
+ pkgs = data.get("packages", [])
452
+ components_found = len(pkgs)
453
+ checks["packages"] = len(pkgs) > 0
454
+ checks["package.name"] = all("name" in p for p in pkgs)
455
+ checks["package.version"] = all("versionInfo" in p for p in pkgs)
456
+ checks["package.supplier"] = all("supplier" in p for p in pkgs)
457
+ checks["package.license"] = all("licenseConcluded" in p for p in pkgs)
458
+ checks["relationships"] = isinstance(data.get("relationships"), list)
459
+
460
+ # Try SPDX Tag-Value
461
+ elif "SPDXVersion:" in text:
462
+ detected = "SPDX Tag-Value"
463
+ checks["SPDXVersion"] = bool(re.search(r"SPDXVersion:\s*SPDX-2\.\d", text))
464
+ checks["DataLicense"] = "DataLicense: CC0-1.0" in text
465
+ checks["SPDXID"] = "SPDXID: SPDXRef-DOCUMENT" in text
466
+ checks["DocumentNamespace"] = "DocumentNamespace:" in text
467
+ checks["Creator"] = "Creator:" in text
468
+ checks["Created"] = "Created:" in text
469
+ pkg_names = re.findall(r"PackageName:\s*(.+)", text)
470
+ components_found = len(pkg_names)
471
+ checks["packages"] = components_found > 0
472
+ checks["PackageVersion"] = "PackageVersion:" in text
473
+ checks["PackageSupplier"] = "PackageSupplier:" in text
474
+ checks["PackageLicense"] = "PackageLicenseConcluded:" in text
475
+ checks["PackageChecksum"] = "PackageChecksum:" in text
476
+
477
+ # Try CycloneDX XML
478
+ elif "<bom" in text and "cyclonedx" in text.lower():
479
+ detected = "CycloneDX XML"
480
+ checks["namespace"] = "cyclonedx.org" in text
481
+ checks["serialNumber"] = 'serialNumber="' in text
482
+ checks["metadata"] = "<metadata>" in text
483
+ checks["timestamp"] = "<timestamp>" in text
484
+ checks["tools"] = "<tools>" in text
485
+ checks["components"] = "<components>" in text
486
+ comp_matches = re.findall(r"<component", text)
487
+ components_found = len(comp_matches)
488
+ checks["component.name"] = "<name>" in text
489
+ checks["component.version"] = "<version>" in text
490
+ checks["component.purl"] = "<purl>" in text
491
+ checks["component.license"] = "<license>" in text or "<licenses>" in text
492
+ checks["component.hash"] = "<hash" in text
493
+ else:
494
+ return "Unrecognized SBOM format. Supported: CycloneDX JSON/XML, SPDX JSON/Tag-Value.", ""
495
+
496
+ passed = sum(1 for v in checks.values() if v)
497
+ total = len(checks)
498
+ pct = int(100 * passed / total) if total else 0
499
+ is_pass = pct >= 80
500
+
501
+ # NTIA minimum elements
502
+ ntia = {
503
+ "Supplier Name": checks.get("package.supplier", checks.get("PackageSupplier", checks.get("metadata.tools", False))),
504
+ "Component Name": checks.get("component.name", checks.get("package.name", checks.get("packages", False))),
505
+ "Component Version": checks.get("component.version", checks.get("package.version", checks.get("PackageVersion", False))),
506
+ "Unique Identifier": checks.get("component.purl", checks.get("SPDXID", checks.get("serialNumber", False))),
507
+ "Dependency Relationship": checks.get("dependencies", checks.get("relationships", False)),
508
+ "Author of SBOM Data": checks.get("metadata.tools", checks.get("creators", checks.get("Creator", False))),
509
+ "Timestamp": checks.get("metadata.timestamp", checks.get("creationInfo", checks.get("Created", False))),
510
+ }
511
+
512
+ # Build result
513
+ status_icon = "PASS" if is_pass else "FAIL"
514
+ result_msg = t["result_pass"] if is_pass else t["result_fail"]
515
+ result = f"**{status_icon}** - {result_msg}\n\n"
516
+ result += f"**Format**: {detected}\n"
517
+ result += f"**{t['components']}**: {components_found}\n"
518
+ result += f"**{t['score']}**: {pct}% ({passed}/{total})\n"
519
+
520
+ # Details
521
+ details = "### Validation checks\n\n"
522
+ for k, v in checks.items():
523
+ mark = "PASS" if v else "FAIL"
524
+ details += f"- [{mark}] {k}\n"
525
+
526
+ failed = [k for k, v in checks.items() if not v]
527
+ if failed:
528
+ details += f"\n### {t['missing']}\n\n"
529
+ for f_item in failed:
530
+ details += f"- {f_item}\n"
531
+
532
+ details += f"\n### {t['ntia_title']}\n\n"
533
+ for k, v in ntia.items():
534
+ mark = "PASS" if v else "FAIL"
535
+ details += f"- [{mark}] {k}\n"
536
+
537
+ if not is_pass:
538
+ details += f"\n### {t['recommendations']}\n\n"
539
+ if not checks.get("component.hash", checks.get("PackageChecksum", checks.get("component.hash", True))):
540
+ details += "- Add SHA-256 checksums to all components\n"
541
+ if not checks.get("component.license", checks.get("package.license", checks.get("PackageLicense", True))):
542
+ details += "- Add SPDX license identifiers to all components\n"
543
+ if not checks.get("component.purl", checks.get("SPDXID", True)):
544
+ details += "- Add Package URLs (purl) for unique identification\n"
545
+ if not checks.get("dependencies", checks.get("relationships", True)):
546
+ details += "- Include dependency relationships\n"
547
+
548
+ return result, details
549
+
550
+ # ---------------------------------------------------------------------------
551
+ # Static content builders
552
+ # ---------------------------------------------------------------------------
553
+
554
+ def build_regulations(lang):
555
+ if lang == "fr":
556
+ return """## Exigences SBOM par regulation
557
+
558
+ | Regulation | Exigence | Format attendu | Echeance | Sanction | Perimetre |
559
+ |---|---|---|---|---|---|
560
+ | **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 |
561
+ | **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) |
562
+ | **DORA** | Gestion des risques lies aux tiers TIC, registre des actifs | Non specifie | Janvier 2025 | Sanctions administratives | Entites financieres de l'UE |
563
+ | **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 |
564
+ | **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 |
565
+ | **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 |
566
+ """
567
+ return """## SBOM Requirements by Regulation
568
+
569
+ | Regulation | Requirement | Expected Format | Deadline | Penalty | Scope |
570
+ |---|---|---|---|---|---|
571
+ | **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 |
572
+ | **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) |
573
+ | **DORA** | ICT third-party risk management, asset registry | Not specified | January 2025 | Administrative sanctions | EU financial entities |
574
+ | **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 |
575
+ | **FDA (Medical Devices)** | Mandatory SBOM for connected medical devices | CycloneDX or SPDX | In force (2023) | Market authorization refusal | Medical devices with software components |
576
+ | **PCI DSS 4.0** | Software component inventory, vulnerability management | Not specified | March 2025 (future requirements) | PCI non-compliance | Organizations processing payment card data |
577
+ """
578
+
579
+
580
+ def build_comparison(lang):
581
+ if lang == "fr":
582
+ return """## Comparaison des formats SBOM
583
+
584
+ | Critere | CycloneDX | SPDX | SWID |
585
+ |---|---|---|---|
586
+ | **Organisation** | OWASP | Linux Foundation | ISO/IEC 19770-2 |
587
+ | **Version actuelle** | 1.6 | 2.3 | 2016 |
588
+ | **Formats** | JSON, XML, Protobuf | JSON, RDF, Tag-Value, XLSX | XML |
589
+ | **Licence du schema** | Apache-2.0 | CC-BY-3.0 | ISO Standard |
590
+ | **Focus principal** | Securite, vulnerabilites | Conformite licences | Identification logicielle |
591
+ | **Composants imbriques** | Oui (natif) | Oui (via relations) | Limite |
592
+ | **Vulnerabilites (VEX)** | Integre (CycloneDX VEX) | Via SPDX Security | Non |
593
+ | **Services & API** | Oui | Non | Non |
594
+ | **Licences SPDX** | Oui | Oui (natif) | Non |
595
+ | **PURL (Package URL)** | Oui (natif) | Oui | Non |
596
+ | **Signature numerique** | Oui (JSF, XML Sig) | Non | Oui (XML Sig) |
597
+ | **Machine readable** | Oui | Oui | Oui |
598
+ | **Adoption industrie** | Elevee (securite) | Elevee (open source) | Faible |
599
+ | **NTIA conforme** | Oui | Oui | Partiel |
600
+ | **CRA conforme** | Oui | Oui | Non recommande |
601
+
602
+ ### Recommandations par cas d'usage
603
+
604
+ - **Securite et gestion des vulnerabilites** : CycloneDX (VEX integre, focus securite)
605
+ - **Conformite open source et licences** : SPDX (standard Linux Foundation, focus licences)
606
+ - **Inventaire IT et gestion des actifs** : SWID (standard ISO, identification)
607
+ - **Conformite CRA / NIS 2** : CycloneDX ou SPDX (les deux sont acceptes)
608
+ - **Marches publics US** : SPDX ou CycloneDX (NTIA minimum elements)
609
+ """
610
+ return """## SBOM Format Comparison
611
+
612
+ | Criteria | CycloneDX | SPDX | SWID |
613
+ |---|---|---|---|
614
+ | **Organization** | OWASP | Linux Foundation | ISO/IEC 19770-2 |
615
+ | **Current version** | 1.6 | 2.3 | 2016 |
616
+ | **Formats** | JSON, XML, Protobuf | JSON, RDF, Tag-Value, XLSX | XML |
617
+ | **Schema license** | Apache-2.0 | CC-BY-3.0 | ISO Standard |
618
+ | **Primary focus** | Security, vulnerabilities | License compliance | Software identification |
619
+ | **Nested components** | Yes (native) | Yes (via relationships) | Limited |
620
+ | **Vulnerabilities (VEX)** | Built-in (CycloneDX VEX) | Via SPDX Security | No |
621
+ | **Services & API** | Yes | No | No |
622
+ | **SPDX licenses** | Yes | Yes (native) | No |
623
+ | **PURL (Package URL)** | Yes (native) | Yes | No |
624
+ | **Digital signature** | Yes (JSF, XML Sig) | No | Yes (XML Sig) |
625
+ | **Machine readable** | Yes | Yes | Yes |
626
+ | **Industry adoption** | High (security) | High (open source) | Low |
627
+ | **NTIA compliant** | Yes | Yes | Partial |
628
+ | **CRA compliant** | Yes | Yes | Not recommended |
629
+
630
+ ### Recommendations by Use Case
631
+
632
+ - **Security and vulnerability management**: CycloneDX (built-in VEX, security focus)
633
+ - **Open source and license compliance**: SPDX (Linux Foundation standard, license focus)
634
+ - **IT inventory and asset management**: SWID (ISO standard, identification)
635
+ - **CRA / NIS 2 compliance**: CycloneDX or SPDX (both accepted)
636
+ - **US federal procurement**: SPDX or CycloneDX (NTIA minimum elements)
637
+ """
638
+
639
+
640
+ def build_tools(lang):
641
+ if lang == "fr":
642
+ header = "## Ecosysteme des outils SBOM\n\n"
643
+ else:
644
+ header = "## SBOM Tool Ecosystem\n\n"
645
+
646
+ cat_gen = "Generation" if lang == "fr" else "Generation"
647
+ cat_ana = "Analyse" if lang == "fr" else "Analysis"
648
+ cat_scan = "Scan de vulnerabilites" if lang == "fr" else "Vulnerability Scanning"
649
+ cat_mgmt = "Gestion" if lang == "fr" else "Management"
650
+
651
+ tools = [
652
+ (cat_gen, "Syft", "SBOM generation from container images and filesystems", "CycloneDX, SPDX", "Open Source (Apache-2.0)", "https://github.com/anchore/syft"),
653
+ (cat_gen, "cdxgen", "CycloneDX SBOM generator for many languages", "CycloneDX", "Open Source (Apache-2.0)", "https://github.com/CycloneDX/cdxgen"),
654
+ (cat_gen, "Microsoft sbom-tool", "SBOM generation tool by Microsoft", "SPDX", "Open Source (MIT)", "https://github.com/microsoft/sbom-tool"),
655
+ (cat_gen, "SPDX Tools", "Official SPDX format tools and libraries", "SPDX", "Open Source", "https://github.com/spdx/tools-java"),
656
+ (cat_ana, "Grype", "Vulnerability scanner for container images and SBOM", "CycloneDX, SPDX", "Open Source (Apache-2.0)", "https://github.com/anchore/grype"),
657
+ (cat_ana, "Dependency-Track", "Intelligent component analysis platform", "CycloneDX", "Open Source (Apache-2.0)", "https://dependencytrack.org/"),
658
+ (cat_ana, "GUAC", "Graph for Understanding Artifact Composition", "CycloneDX, SPDX", "Open Source (Apache-2.0)", "https://guac.sh/"),
659
+ (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"),
660
+ (cat_scan, "Trivy", "All-in-one security scanner (containers, IaC, SBOM)", "CycloneDX, SPDX", "Open Source (Apache-2.0)", "https://trivy.dev/"),
661
+ (cat_scan, "Snyk", "Developer-first security platform", "CycloneDX, SPDX", "Freemium / Enterprise", "https://snyk.io/"),
662
+ (cat_scan, "Black Duck", "Software composition analysis (SCA)", "SPDX, CycloneDX", "Commercial", "https://www.blackducksoftware.com/"),
663
+ (cat_scan, "Mend (WhiteSource)", "Open source security and compliance", "CycloneDX, SPDX", "Freemium / Enterprise", "https://www.mend.io/"),
664
+ (cat_mgmt, "OWASP Dependency-Track", "Component analysis and SBOM management", "CycloneDX", "Open Source (Apache-2.0)", "https://dependencytrack.org/"),
665
+ (cat_mgmt, "JFrog Xray", "Universal artifact analysis and SBOM management", "CycloneDX, SPDX", "Commercial", "https://jfrog.com/xray/"),
666
+ (cat_mgmt, "Sonatype Nexus Lifecycle", "Software supply chain management", "CycloneDX, SPDX", "Commercial", "https://www.sonatype.com/products/open-source-security-dependency-management"),
667
+ ]
668
+
669
+ if lang == "fr":
670
+ table = "| Categorie | Outil | Description | Formats | Tarification | Lien |\n"
671
+ else:
672
+ table = "| Category | Tool | Description | Formats | Pricing | Link |\n"
673
+ table += "|---|---|---|---|---|---|\n"
674
+ for cat, name, desc, fmts, price, link in tools:
675
+ table += f"| {cat} | **{name}** | {desc} | {fmts} | {price} | [Site]({link}) |\n"
676
+
677
+ return header + table
678
+
679
+
680
+ def build_resources(lang):
681
+ if lang == "fr":
682
+ return """## Ressources et articles
683
+
684
+ ### AYI-NEDJIMI Consultants - Expertise conformite et cybersecurite
685
+
686
+ - [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.
687
+
688
+ - [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.
689
+
690
+ - [AYI-NEDJIMI Consultants - Accueil](https://ayinedjimi-consultants.fr) - Cabinet de conseil specialise en conformite reglementaire, cybersecurite et gouvernance des donnees.
691
+
692
+ ### Standards et specifications
693
+
694
+ - [CycloneDX Specification](https://cyclonedx.org/specification/overview/) - Specification officielle du format CycloneDX (OWASP).
695
+ - [SPDX Specification](https://spdx.github.io/spdx-spec/v2.3/) - Specification officielle du format SPDX (Linux Foundation).
696
+ - [NTIA SBOM Minimum Elements](https://www.ntia.gov/page/software-bill-materials) - Elements minimaux NTIA pour les SBOM.
697
+ - [EU Cyber Resilience Act](https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act) - Texte officiel du CRA europeen.
698
+
699
+ ### Communaute
700
+
701
+ - [OWASP Software Component Verification Standard](https://owasp.org/www-project-software-component-verification-standard/) - Standard OWASP pour la verification des composants logiciels.
702
+ - [OpenSSF Scorecard](https://securityscorecards.dev/) - Evaluation automatisee de la securite des projets open source.
703
+ """
704
+ return """## Resources and Articles
705
+
706
+ ### AYI-NEDJIMI Consultants - Compliance and Cybersecurity Expertise
707
+
708
+ - [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.
709
+
710
+ - [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.
711
+
712
+ - [AYI-NEDJIMI Consultants - Home](https://ayinedjimi-consultants.fr) - Consulting firm specialized in regulatory compliance, cybersecurity, and data governance.
713
+
714
+ ### Standards and Specifications
715
+
716
+ - [CycloneDX Specification](https://cyclonedx.org/specification/overview/) - Official CycloneDX format specification (OWASP).
717
+ - [SPDX Specification](https://spdx.github.io/spdx-spec/v2.3/) - Official SPDX format specification (Linux Foundation).
718
+ - [NTIA SBOM Minimum Elements](https://www.ntia.gov/page/software-bill-materials) - NTIA minimum elements for SBOMs.
719
+ - [EU Cyber Resilience Act](https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act) - Official EU CRA text.
720
+
721
+ ### Community
722
+
723
+ - [OWASP Software Component Verification Standard](https://owasp.org/www-project-software-component-verification-standard/) - OWASP standard for software component verification.
724
+ - [OpenSSF Scorecard](https://securityscorecards.dev/) - Automated security assessment for open source projects.
725
+ """
726
+
727
+
728
+ # ---------------------------------------------------------------------------
729
+ # Gradio UI
730
+ # ---------------------------------------------------------------------------
731
+
732
+ FOOTER_HTML = (
733
+ '<div style="text-align:center; margin-top:24px; padding:16px; '
734
+ 'border-top:1px solid #ddd; color:#666; font-size:0.9em;">'
735
+ 'Powered by <a href="https://ayinedjimi-consultants.fr" target="_blank">'
736
+ 'AYI-NEDJIMI Consultants</a> | '
737
+ '<a href="https://ayinedjimi-consultants.fr/articles/conformite/'
738
+ 'sbom-2026-obligation-securite.html" target="_blank">SBOM 2026 Guide</a> | '
739
+ '<a href="https://ayinedjimi-consultants.fr/articles/conformite/'
740
+ 'cyber-resilience-act-2026.html" target="_blank">Cyber Resilience Act</a>'
741
+ '</div>'
742
+ )
743
+
744
+
745
+ def build_app():
746
+ with gr.Blocks(
747
+ title="SBOM Generator & Validator",
748
+ theme=gr.themes.Soft(),
749
+ css=".footer-link a { color: #2563eb; text-decoration: none; }"
750
+ ) as demo:
751
+
752
+ lang_state = gr.State("fr")
753
+
754
+ gr.Markdown("# Generateur & Validateur SBOM")
755
+ gr.Markdown("Creez et validez vos Software Bill of Materials (SBOM) conformes CycloneDX & SPDX")
756
+
757
+ lang_btn = gr.Button("English", variant="secondary", elem_id="lang-btn")
758
+
759
+ # ------------------------------------------------------------------
760
+ # Tab 1: Generator
761
+ # ------------------------------------------------------------------
762
+ with gr.Tab("Generateur SBOM"):
763
+ with gr.Row():
764
+ with gr.Column():
765
+ gen_input = gr.Textbox(
766
+ label="Contenu du fichier de dependances",
767
+ placeholder="Collez ici le contenu de requirements.txt, package.json, pom.xml ou go.mod ...",
768
+ lines=12,
769
+ )
770
+ gen_format = gr.Dropdown(
771
+ choices=["CycloneDX JSON", "CycloneDX XML", "SPDX JSON", "SPDX Tag-Value"],
772
+ value="CycloneDX JSON",
773
+ label="Format de sortie",
774
+ )
775
+ gen_btn = gr.Button("Generer le SBOM", variant="primary")
776
+ with gr.Column():
777
+ gen_stats = gr.Markdown(label="Statistiques")
778
+ gen_output = gr.Textbox(label="Document SBOM genere", lines=20, show_copy_button=True)
779
+ gen_file = gr.File(label="Telecharger le SBOM")
780
+
781
+ gen_btn.click(
782
+ fn=generate_sbom,
783
+ inputs=[gen_input, gen_format, lang_state],
784
+ outputs=[gen_output, gen_stats, gen_file],
785
+ )
786
+
787
+ # ------------------------------------------------------------------
788
+ # Tab 2: Validator
789
+ # ------------------------------------------------------------------
790
+ with gr.Tab("Validateur SBOM"):
791
+ with gr.Row():
792
+ with gr.Column():
793
+ val_input = gr.Textbox(
794
+ label="Document SBOM a valider",
795
+ placeholder="Collez ici un document SBOM (CycloneDX JSON/XML ou SPDX JSON/Tag-Value) ...",
796
+ lines=15,
797
+ )
798
+ val_btn = gr.Button("Valider le SBOM", variant="primary")
799
+ with gr.Column():
800
+ val_result = gr.Markdown(label="Resultat de la validation")
801
+ val_details = gr.Markdown(label="Details et recommandations")
802
+
803
+ val_btn.click(
804
+ fn=validate_sbom,
805
+ inputs=[val_input, lang_state],
806
+ outputs=[val_result, val_details],
807
+ )
808
+
809
+ # ------------------------------------------------------------------
810
+ # Tab 3: Regulations
811
+ # ------------------------------------------------------------------
812
+ with gr.Tab("Exigences reglementaires"):
813
+ reg_md = gr.Markdown(value=build_regulations("fr"))
814
+
815
+ # ------------------------------------------------------------------
816
+ # Tab 4: Format Comparison
817
+ # ------------------------------------------------------------------
818
+ with gr.Tab("Comparaison de formats"):
819
+ cmp_md = gr.Markdown(value=build_comparison("fr"))
820
+
821
+ # ------------------------------------------------------------------
822
+ # Tab 5: Tool Ecosystem
823
+ # ------------------------------------------------------------------
824
+ with gr.Tab("Ecosysteme outils"):
825
+ tools_md = gr.Markdown(value=build_tools("fr"))
826
+
827
+ # ------------------------------------------------------------------
828
+ # Tab 6: Resources
829
+ # ------------------------------------------------------------------
830
+ with gr.Tab("Ressources"):
831
+ res_md = gr.Markdown(value=build_resources("fr"))
832
+
833
+ # ------------------------------------------------------------------
834
+ # Footer
835
+ # ------------------------------------------------------------------
836
+ gr.HTML(FOOTER_HTML)
837
+
838
+ # ------------------------------------------------------------------
839
+ # Language toggle
840
+ # ------------------------------------------------------------------
841
+ def toggle_language(current_lang):
842
+ new_lang = "en" if current_lang == "fr" else "fr"
843
+ t = TR[new_lang]
844
+ return (
845
+ new_lang,
846
+ t["lang_btn"],
847
+ build_regulations(new_lang),
848
+ build_comparison(new_lang),
849
+ build_tools(new_lang),
850
+ build_resources(new_lang),
851
+ )
852
+
853
+ lang_btn.click(
854
+ fn=toggle_language,
855
+ inputs=[lang_state],
856
+ outputs=[lang_state, lang_btn, reg_md, cmp_md, tools_md, res_md],
857
+ )
858
+
859
+ return demo
860
+
861
+
862
+ if __name__ == "__main__":
863
+ demo = build_app()
864
+ demo.launch(server_name="0.0.0.0", server_port=7860)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio==4.44.0
2
+ huggingface_hub==0.24.7