1use super::{ReportConfig, ReportError, ReportFormat, ReportGenerator, ReportType};
4use crate::diff::{DiffResult, SlaStatus, VulnerabilityDetail};
5use crate::model::NormalizedSbom;
6use crate::quality::{
7 ComplianceChecker, ComplianceLevel, ComplianceResult, ViolationSeverity, rule_meta,
8};
9use serde::Serialize;
10
11pub struct SarifReporter {
13 include_info: bool,
15}
16
17impl SarifReporter {
18 #[must_use]
20 pub const fn new() -> Self {
21 Self { include_info: true }
22 }
23
24 #[must_use]
26 pub const fn include_info(mut self, include: bool) -> Self {
27 self.include_info = include;
28 self
29 }
30}
31
32impl Default for SarifReporter {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl ReportGenerator for SarifReporter {
39 fn generate_diff_report(
40 &self,
41 result: &DiffResult,
42 old_sbom: &NormalizedSbom,
43 new_sbom: &NormalizedSbom,
44 config: &ReportConfig,
45 ) -> Result<String, ReportError> {
46 let mut results = Vec::new();
47
48 if config.includes(ReportType::Components) {
50 for comp in &result.components.added {
51 if self.include_info {
52 results.push(SarifResult {
53 rule_id: "SBOM-TOOLS-001".to_string(),
54 level: SarifLevel::Note,
55 message: SarifMessage {
56 text: format!(
57 "Component added: {} {}",
58 comp.name,
59 comp.new_version.as_deref().unwrap_or("")
60 ),
61 },
62 locations: vec![],
63 properties: None,
64 });
65 }
66 }
67
68 for comp in &result.components.removed {
69 results.push(SarifResult {
70 rule_id: "SBOM-TOOLS-002".to_string(),
71 level: SarifLevel::Warning,
72 message: SarifMessage {
73 text: format!(
74 "Component removed: {} {}",
75 comp.name,
76 comp.old_version.as_deref().unwrap_or("")
77 ),
78 },
79 locations: vec![],
80 properties: None,
81 });
82 }
83
84 for comp in &result.components.modified {
85 if self.include_info {
86 results.push(SarifResult {
87 rule_id: "SBOM-TOOLS-003".to_string(),
88 level: SarifLevel::Note,
89 message: SarifMessage {
90 text: format!(
91 "Component modified: {} {} -> {}",
92 comp.name,
93 comp.old_version.as_deref().unwrap_or("unknown"),
94 comp.new_version.as_deref().unwrap_or("unknown")
95 ),
96 },
97 locations: vec![],
98 properties: None,
99 });
100 }
101 }
102 }
103
104 if config.includes(ReportType::Vulnerabilities) {
106 for vuln in &result.vulnerabilities.introduced {
107 let depth_label = match vuln.component_depth {
108 Some(1) => " [Direct]",
109 Some(_) => " [Transitive]",
110 None => "",
111 };
112 let sla_label = format_sla_label(vuln);
113 let vex_label = format_vex_label(vuln.vex_state.as_ref());
114 results.push(SarifResult {
115 rule_id: "SBOM-TOOLS-005".to_string(),
116 level: severity_to_level(&vuln.severity),
117 message: SarifMessage {
118 text: format!(
119 "Vulnerability introduced: {} ({}){}{}{} in {} {}",
120 vuln.id,
121 vuln.severity,
122 depth_label,
123 sla_label,
124 vex_label,
125 vuln.component_name,
126 vuln.version.as_deref().unwrap_or("")
127 ),
128 },
129 locations: vec![],
130 properties: None,
131 });
132 }
133
134 for vuln in &result.vulnerabilities.resolved {
135 if self.include_info {
136 let depth_label = match vuln.component_depth {
137 Some(1) => " [Direct]",
138 Some(_) => " [Transitive]",
139 None => "",
140 };
141 let sla_label = format_sla_label(vuln);
142 let vex_label = format_vex_label(vuln.vex_state.as_ref());
143 results.push(SarifResult {
144 rule_id: "SBOM-TOOLS-006".to_string(),
145 level: SarifLevel::Note,
146 message: SarifMessage {
147 text: format!(
148 "Vulnerability resolved: {} ({}){}{}{} was in {}",
149 vuln.id,
150 vuln.severity,
151 depth_label,
152 sla_label,
153 vex_label,
154 vuln.component_name
155 ),
156 },
157 locations: vec![],
158 properties: None,
159 });
160 }
161 }
162 }
163
164 if config.includes(ReportType::Licenses) {
166 for license in &result.licenses.new_licenses {
167 results.push(SarifResult {
168 rule_id: "SBOM-TOOLS-004".to_string(),
169 level: SarifLevel::Warning,
170 message: SarifMessage {
171 text: format!(
172 "New license introduced: {} in components: {}",
173 license.license,
174 license.components.join(", ")
175 ),
176 },
177 locations: vec![],
178 properties: None,
179 });
180 }
181 }
182
183 for change in &result.metadata_changes {
185 let old = change.old_value.as_deref().unwrap_or("(none)");
186 let new = change.new_value.as_deref().unwrap_or("(none)");
187 results.push(SarifResult {
188 rule_id: "SBOM-TOOLS-008".to_string(),
189 level: SarifLevel::Note,
190 message: SarifMessage {
191 text: format!(
192 "Metadata {}: {} ({old} -> {new})",
193 change.kind, change.field
194 ),
195 },
196 locations: vec![],
197 properties: None,
198 });
199 }
200
201 for comp in new_sbom.components.values() {
203 if let Some(eol) = &comp.eol {
204 match eol.status {
205 crate::model::EolStatus::EndOfLife => {
206 let eol_date_str = eol
207 .eol_date
208 .map_or_else(String::new, |d| format!(" (EOL: {d})"));
209 results.push(SarifResult {
210 rule_id: "SBOM-EOL-001".to_string(),
211 level: SarifLevel::Error,
212 message: SarifMessage {
213 text: format!(
214 "Component '{}' version '{}' has reached end-of-life{} (product: {})",
215 comp.name,
216 comp.version.as_deref().unwrap_or("unknown"),
217 eol_date_str,
218 eol.product,
219 ),
220 },
221 locations: vec![],
222 properties: None,
223 });
224 }
225 crate::model::EolStatus::ApproachingEol => {
226 let days_str = eol
227 .days_until_eol
228 .map_or_else(String::new, |d| format!(" ({d} days remaining)"));
229 results.push(SarifResult {
230 rule_id: "SBOM-EOL-002".to_string(),
231 level: SarifLevel::Warning,
232 message: SarifMessage {
233 text: format!(
234 "Component '{}' version '{}' is approaching end-of-life{} (product: {})",
235 comp.name,
236 comp.version.as_deref().unwrap_or("unknown"),
237 days_str,
238 eol.product,
239 ),
240 },
241 locations: vec![],
242 properties: None,
243 });
244 }
245 _ => {}
246 }
247 }
248 }
249
250 let cra_old = config
252 .old_cra_compliance
253 .clone()
254 .unwrap_or_else(|| ComplianceChecker::new(ComplianceLevel::CraPhase2).check(old_sbom));
255 let cra_new = config
256 .new_cra_compliance
257 .clone()
258 .unwrap_or_else(|| ComplianceChecker::new(ComplianceLevel::CraPhase2).check(new_sbom));
259 results.extend(compliance_results_to_sarif(&cra_old, Some("Old SBOM")));
260 results.extend(compliance_results_to_sarif(&cra_new, Some("New SBOM")));
261
262 let sarif = SarifReport {
263 schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json".to_string(),
264 version: "2.1.0".to_string(),
265 runs: vec![SarifRun {
266 tool: SarifTool {
267 driver: SarifDriver {
268 name: "sbom-tools".to_string(),
269 version: env!("CARGO_PKG_VERSION").to_string(),
270 information_uri: "https://github.com/binarly-io/sbom-tools".to_string(),
271 rules: SarifRuleWithUri::wrap_all(get_sarif_rules()),
272 },
273 },
274 results,
275 properties: None,
276 }],
277 };
278
279 serde_json::to_string_pretty(&sarif)
280 .map_err(|e| ReportError::SerializationError(e.to_string()))
281 }
282
283 fn generate_view_report(
284 &self,
285 sbom: &NormalizedSbom,
286 config: &ReportConfig,
287 ) -> Result<String, ReportError> {
288 let mut results = Vec::new();
289
290 for (comp, vuln) in sbom.all_vulnerabilities() {
292 let severity_str = vuln
293 .severity
294 .as_ref()
295 .map_or_else(|| "Unknown".to_string(), std::string::ToString::to_string);
296 let vex_state = vuln
297 .vex_status
298 .as_ref()
299 .map(|v| &v.status)
300 .or_else(|| comp.vex_status.as_ref().map(|v| &v.status));
301 let vex_label = format_vex_label(vex_state);
302 results.push(SarifResult {
303 rule_id: "SBOM-VIEW-001".to_string(),
304 level: severity_to_level(&severity_str),
305 message: SarifMessage {
306 text: format!(
307 "Vulnerability {} ({}){} in {} {}",
308 vuln.id,
309 severity_str,
310 vex_label,
311 comp.name,
312 comp.version.as_deref().unwrap_or("")
313 ),
314 },
315 locations: vec![],
316 properties: None,
317 });
318 }
319
320 let cra_result = config
322 .view_cra_compliance
323 .clone()
324 .unwrap_or_else(|| ComplianceChecker::new(ComplianceLevel::CraPhase2).check(sbom));
325 results.extend(compliance_results_to_sarif(&cra_result, None));
326
327 let sarif = SarifReport {
328 schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json".to_string(),
329 version: "2.1.0".to_string(),
330 runs: vec![SarifRun {
331 tool: SarifTool {
332 driver: SarifDriver {
333 name: "sbom-tools".to_string(),
334 version: env!("CARGO_PKG_VERSION").to_string(),
335 information_uri: "https://github.com/binarly-io/sbom-tools".to_string(),
336 rules: SarifRuleWithUri::wrap_all(get_sarif_view_rules()),
337 },
338 },
339 results,
340 properties: None,
341 }],
342 };
343
344 serde_json::to_string_pretty(&sarif)
345 .map_err(|e| ReportError::SerializationError(e.to_string()))
346 }
347
348 fn format(&self) -> ReportFormat {
349 ReportFormat::Sarif
350 }
351}
352
353fn ai_check_to_rule_id(check_id: &str) -> &'static str {
357 match check_id {
358 "AI-001" => "SBOM-AIBOM-001",
359 "AI-002" => "SBOM-AIBOM-002",
360 "AI-003" => "SBOM-AIBOM-003",
361 "AI-004" => "SBOM-AIBOM-004",
362 "AI-005" => "SBOM-AIBOM-005",
363 "AI-006" => "SBOM-AIBOM-006",
364 "AI-007" => "SBOM-AIBOM-007",
365 "AI-008" => "SBOM-AIBOM-008",
366 "AI-009" => "SBOM-AIBOM-009",
367 "AI-010" => "SBOM-AIBOM-010",
368 "AI-011" => "SBOM-AIBOM-011",
369 _ => "SBOM-AIBOM-GENERAL",
370 }
371}
372
373fn aibom_level(check_id: &str) -> SarifLevel {
378 match check_id {
379 "AI-001" | "AI-002" | "AI-003" | "AI-005" | "AI-009" | "AI-010" | "AI-011" => {
385 SarifLevel::Warning
386 }
387 _ => SarifLevel::Note,
388 }
389}
390
391fn get_sarif_aibom_rules() -> Vec<SarifRule> {
394 [
397 (
398 "SBOM-AIBOM-001",
399 "AI-001",
400 "AibomModelCardUrl",
401 "Model card URL present",
402 ),
403 (
404 "SBOM-AIBOM-002",
405 "AI-002",
406 "AibomArchitectureFamily",
407 "Architecture family declared",
408 ),
409 (
410 "SBOM-AIBOM-003",
411 "AI-003",
412 "AibomTrainingDatasets",
413 "Training datasets referenced",
414 ),
415 (
416 "SBOM-AIBOM-004",
417 "AI-004",
418 "AibomQuantitativeAnalysis",
419 "Quantitative analysis present",
420 ),
421 (
422 "SBOM-AIBOM-005",
423 "AI-005",
424 "AibomFairnessAssessment",
425 "Fairness assessments included",
426 ),
427 (
428 "SBOM-AIBOM-006",
429 "AI-006",
430 "AibomEnergyConsumption",
431 "Energy consumption disclosed",
432 ),
433 (
434 "SBOM-AIBOM-007",
435 "AI-007",
436 "AibomUseCases",
437 "Use-cases documented",
438 ),
439 (
440 "SBOM-AIBOM-008",
441 "AI-008",
442 "AibomLimitations",
443 "Known limitations stated",
444 ),
445 (
446 "SBOM-AIBOM-009",
447 "AI-009",
448 "AibomEthicalConsiderations",
449 "Ethical considerations present",
450 ),
451 (
452 "SBOM-AIBOM-010",
453 "AI-010",
454 "AibomModelWeightHashes",
455 "Model weight hashes present",
456 ),
457 (
458 "SBOM-AIBOM-011",
459 "AI-011",
460 "AibomExploitabilityReference",
461 "Exploitability/advisory reference present",
462 ),
463 (
464 "SBOM-AIBOM-GENERAL",
465 "AI-GENERAL",
466 "AibomGeneral",
467 "AI BOM model-card completeness",
468 ),
469 ]
470 .into_iter()
471 .map(|(rule_id, check_id, name, desc)| SarifRule {
472 id: rule_id.to_string(),
473 name: name.to_string(),
474 short_description: SarifMessage {
475 text: desc.to_string(),
476 },
477 default_configuration: SarifConfiguration {
478 level: aibom_level(check_id),
479 },
480 })
481 .collect()
482}
483
484pub fn generate_ai_readiness_sarif(
489 metrics: &crate::quality::AiReadinessMetrics,
490 sbom_name: &str,
491 profile: &str,
492 overall_score: Option<f32>,
493 grade: &str,
494) -> Result<String, ReportError> {
495 let results: Vec<SarifResult> = metrics
496 .checks
497 .iter()
498 .filter(|check| !check.passed)
499 .map(|check| {
500 let rule_id = ai_check_to_rule_id(&check.id);
501 let detail_suffix = check
502 .detail
503 .as_ref()
504 .map(|d| format!(" — {d}"))
505 .unwrap_or_default();
506 SarifResult {
507 rule_id: rule_id.to_string(),
508 level: aibom_level(&check.id),
509 message: SarifMessage {
510 text: format!(
511 "AIBOM check {} failed: {} ({:.0}% weight){detail_suffix}",
512 check.id,
513 check.name,
514 check.weight * 100.0
515 ),
516 },
517 locations: vec![],
518 properties: Some(SarifResultProperties {
519 standard_ids: vec![format!("AIBOM:{}", check.id)],
520 standard_help_uris: rule_help_uri(rule_id)
521 .map(|u| vec![u.to_string()])
522 .unwrap_or_default(),
523 }),
524 }
525 })
526 .collect();
527
528 let sarif = SarifReport {
529 schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json".to_string(),
530 version: "2.1.0".to_string(),
531 runs: vec![SarifRun {
532 tool: SarifTool {
533 driver: SarifDriver {
534 name: "sbom-tools".to_string(),
535 version: env!("CARGO_PKG_VERSION").to_string(),
536 information_uri: "https://github.com/binarly-io/sbom-tools".to_string(),
537 rules: SarifRuleWithUri::wrap_all(get_sarif_aibom_rules()),
538 },
539 },
540 results,
541 properties: Some(SarifRunProperties {
542 applicable: !metrics.is_not_applicable(),
543 overall_score,
544 grade: grade.to_string(),
545 sbom: sbom_name.to_string(),
546 profile: profile.to_string(),
547 }),
548 }],
549 };
550
551 serde_json::to_string_pretty(&sarif).map_err(|e| ReportError::SerializationError(e.to_string()))
552}
553
554pub fn generate_compliance_sarif(result: &ComplianceResult) -> Result<String, ReportError> {
555 let rules = SarifRuleWithUri::wrap_all(get_sarif_rules_for_standard(result.level));
556 let sarif = SarifReport {
557 schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json".to_string(),
558 version: "2.1.0".to_string(),
559 runs: vec![SarifRun {
560 tool: SarifTool {
561 driver: SarifDriver {
562 name: "sbom-tools".to_string(),
563 version: env!("CARGO_PKG_VERSION").to_string(),
564 information_uri: "https://github.com/binarly-io/sbom-tools".to_string(),
565 rules,
566 },
567 },
568 results: compliance_results_to_sarif(result, None),
569 properties: None,
570 }],
571 };
572
573 serde_json::to_string_pretty(&sarif).map_err(|e| ReportError::SerializationError(e.to_string()))
574}
575
576pub fn generate_multi_compliance_sarif(
578 results: &[ComplianceResult],
579) -> Result<String, ReportError> {
580 let mut all_rules = Vec::new();
582 let mut all_results = Vec::new();
583
584 for result in results {
585 let rules = get_sarif_rules_for_standard(result.level);
586 all_rules.extend(rules);
587 all_results.extend(compliance_results_to_sarif(result, None));
588 }
589
590 let sarif = SarifReport {
591 schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json".to_string(),
592 version: "2.1.0".to_string(),
593 runs: vec![SarifRun {
594 tool: SarifTool {
595 driver: SarifDriver {
596 name: "sbom-tools".to_string(),
597 version: env!("CARGO_PKG_VERSION").to_string(),
598 information_uri: "https://github.com/binarly-io/sbom-tools".to_string(),
599 rules: SarifRuleWithUri::wrap_all(all_rules),
600 },
601 },
602 results: all_results,
603 properties: None,
604 }],
605 };
606
607 serde_json::to_string_pretty(&sarif).map_err(|e| ReportError::SerializationError(e.to_string()))
608}
609
610fn severity_to_level(severity: &str) -> SarifLevel {
611 match severity.to_lowercase().as_str() {
612 "critical" | "high" => SarifLevel::Error,
613 "low" | "info" => SarifLevel::Note,
614 _ => SarifLevel::Warning,
615 }
616}
617
618fn format_sla_label(vuln: &VulnerabilityDetail) -> String {
620 match vuln.sla_status() {
621 SlaStatus::Overdue(days) => format!(" [SLA: {days}d late]"),
622 SlaStatus::DueSoon(days) | SlaStatus::OnTrack(days) => format!(" [SLA: {days}d left]"),
623 SlaStatus::NoDueDate => vuln
624 .days_since_published
625 .map(|d| format!(" [Age: {d}d]"))
626 .unwrap_or_default(),
627 }
628}
629
630fn format_vex_label(vex_state: Option<&crate::model::VexState>) -> String {
631 match vex_state {
632 Some(crate::model::VexState::NotAffected) => " [VEX: Not Affected]".to_string(),
633 Some(crate::model::VexState::Fixed) => " [VEX: Fixed]".to_string(),
634 Some(crate::model::VexState::Affected) => " [VEX: Affected]".to_string(),
635 Some(crate::model::VexState::UnderInvestigation) => {
636 " [VEX: Under Investigation]".to_string()
637 }
638 None => String::new(),
639 }
640}
641
642const fn violation_severity_to_level(severity: ViolationSeverity) -> SarifLevel {
643 match severity {
644 ViolationSeverity::Error => SarifLevel::Error,
645 ViolationSeverity::Warning => SarifLevel::Warning,
646 ViolationSeverity::Info => SarifLevel::Note,
647 }
648}
649
650fn compliance_results_to_sarif(result: &ComplianceResult, label: Option<&str>) -> Vec<SarifResult> {
651 let prefix = label.map(|l| format!("{l} - ")).unwrap_or_default();
652 result
653 .violations
654 .iter()
655 .map(|v| {
656 let element = v.element.as_deref().unwrap_or("unknown");
657 let standard_ids: Vec<String> = v
658 .standard_refs
659 .iter()
660 .map(|sr| format!("{}:{}", sarif_standard_label(sr.standard), sr.id))
661 .collect();
662 let standard_help_uris: Vec<String> = v
663 .standard_refs
664 .iter()
665 .filter_map(|sr| sr.help_uri.clone())
666 .collect();
667 let properties = if standard_ids.is_empty() && standard_help_uris.is_empty() {
668 None
669 } else {
670 Some(SarifResultProperties {
671 standard_ids,
672 standard_help_uris,
673 })
674 };
675 let sarif_rule_id = rule_meta(v.rule_id)
680 .map_or("SBOM-CRA-GENERAL", |m| m.sarif_id)
681 .to_string();
682 SarifResult {
683 rule_id: sarif_rule_id,
684 level: violation_severity_to_level(v.severity),
685 message: SarifMessage {
686 text: format!(
687 "{}{}: {} (Requirement: {}) [Element: {}]",
688 prefix,
689 result.level.name(),
690 v.message,
691 v.requirement,
692 element
693 ),
694 },
695 locations: vec![],
696 properties,
697 }
698 })
699 .collect()
700}
701
702fn rule_help_uri(rule_id: &str) -> Option<&'static str> {
706 if rule_id.starts_with("SBOM-CRA-") {
707 Some("https://eur-lex.europa.eu/eli/reg/2024/2847/oj/eng")
708 } else if rule_id.starts_with("SBOM-BSI-") {
709 Some(
710 "https://www.bsi.bund.de/EN/Themen/Unternehmen-und-Organisationen/Standards-und-Zertifizierung/Technische-Richtlinien/TR-nach-Thema-sortiert/tr03183/TR-03183_node.html",
711 )
712 } else if rule_id.starts_with("SBOM-NIST-SSDF-") || rule_id.starts_with("SBOM-SSDF-") {
713 Some("https://doi.org/10.6028/NIST.SP.800-218")
714 } else if rule_id.starts_with("SBOM-EO14028-") || rule_id.starts_with("SBOM-EO-14028-") {
715 Some("https://www.federalregister.gov/d/2021-10460")
716 } else if rule_id.starts_with("SBOM-FDA-") {
717 Some(
718 "https://www.fda.gov/regulatory-information/search-fda-guidance-documents/cybersecurity-medical-devices-quality-system-considerations-and-content-premarket-submissions",
719 )
720 } else if rule_id.starts_with("SBOM-NTIA-") {
721 Some("https://www.ntia.doc.gov/files/ntia/publications/sbom_minimum_elements_report.pdf")
722 } else if rule_id.starts_with("SBOM-PQC-") || rule_id.starts_with("SBOM-NIST-PQC-") {
723 Some("https://csrc.nist.gov/projects/post-quantum-cryptography")
724 } else if rule_id.starts_with("SBOM-CNSA-") {
725 Some(
726 "https://media.defense.gov/2022/Sep/07/2003071834/-1/-1/0/CSA_CNSA_2.0_ALGORITHMS_.PDF",
727 )
728 } else if rule_id.starts_with("SBOM-CSAF-") {
729 Some("https://docs.oasis-open.org/csaf/csaf/v2.0/csaf-v2.0.html")
730 } else if rule_id.starts_with("SBOM-AIBOM-") {
731 Some("https://cyclonedx.org/capabilities/mlbom/")
732 } else if rule_id.starts_with("SBOM-AIACT-") {
733 Some("https://eur-lex.europa.eu/eli/reg/2024/1689/oj/eng")
734 } else if rule_id.starts_with("SBOM-BSIAI-") {
735 Some("https://www.bsi.bund.de")
736 } else {
737 None
738 }
739}
740
741fn sarif_standard_label(kind: crate::quality::StandardKind) -> &'static str {
744 use crate::quality::StandardKind;
745 match kind {
746 StandardKind::CraArticle => "CRA",
747 StandardKind::CraAnnex => "CRA-Annex",
748 StandardKind::Pren40000_1_3 => "prEN-40000-1-3",
749 StandardKind::BsiTr03183_2 => "BSI-TR-03183-2",
750 StandardKind::NistSsdf => "NIST-SSDF",
751 StandardKind::Eo14028 => "EO-14028",
752 StandardKind::FdaPremarket => "FDA",
753 StandardKind::NtiaMinimum => "NTIA",
754 StandardKind::Csaf2 => "CSAF",
755 StandardKind::Cnsa2 => "CNSA-2.0",
756 StandardKind::NistPqc => "NIST-PQC",
757 StandardKind::EuAiAct => "EU-AI-Act",
758 StandardKind::BsiSbomForAi => "BSI-G7-SBOM-for-AI",
759 StandardKind::Other => "Other",
760 }
761}
762
763fn get_sarif_rules() -> Vec<SarifRule> {
764 let mut rules = vec![
765 SarifRule {
766 id: "SBOM-TOOLS-001".to_string(),
767 name: "ComponentAdded".to_string(),
768 short_description: SarifMessage {
769 text: "A new component was added to the SBOM".to_string(),
770 },
771 default_configuration: SarifConfiguration {
772 level: SarifLevel::Note,
773 },
774 },
775 SarifRule {
776 id: "SBOM-TOOLS-002".to_string(),
777 name: "ComponentRemoved".to_string(),
778 short_description: SarifMessage {
779 text: "A component was removed from the SBOM".to_string(),
780 },
781 default_configuration: SarifConfiguration {
782 level: SarifLevel::Warning,
783 },
784 },
785 SarifRule {
786 id: "SBOM-TOOLS-003".to_string(),
787 name: "VersionChanged".to_string(),
788 short_description: SarifMessage {
789 text: "A component version was changed".to_string(),
790 },
791 default_configuration: SarifConfiguration {
792 level: SarifLevel::Note,
793 },
794 },
795 SarifRule {
796 id: "SBOM-TOOLS-004".to_string(),
797 name: "LicenseChanged".to_string(),
798 short_description: SarifMessage {
799 text: "A license was added or changed".to_string(),
800 },
801 default_configuration: SarifConfiguration {
802 level: SarifLevel::Warning,
803 },
804 },
805 SarifRule {
806 id: "SBOM-TOOLS-005".to_string(),
807 name: "VulnerabilityIntroduced".to_string(),
808 short_description: SarifMessage {
809 text: "A new vulnerability was introduced".to_string(),
810 },
811 default_configuration: SarifConfiguration {
812 level: SarifLevel::Error,
813 },
814 },
815 SarifRule {
816 id: "SBOM-TOOLS-006".to_string(),
817 name: "VulnerabilityResolved".to_string(),
818 short_description: SarifMessage {
819 text: "A vulnerability was resolved".to_string(),
820 },
821 default_configuration: SarifConfiguration {
822 level: SarifLevel::Note,
823 },
824 },
825 SarifRule {
826 id: "SBOM-TOOLS-007".to_string(),
827 name: "SupplierChanged".to_string(),
828 short_description: SarifMessage {
829 text: "A component supplier was changed".to_string(),
830 },
831 default_configuration: SarifConfiguration {
832 level: SarifLevel::Warning,
833 },
834 },
835 SarifRule {
836 id: "SBOM-TOOLS-008".to_string(),
837 name: "MetadataChanged".to_string(),
838 short_description: SarifMessage {
839 text: "A document-level metadata field was changed".to_string(),
840 },
841 default_configuration: SarifConfiguration {
842 level: SarifLevel::Note,
843 },
844 },
845 SarifRule {
846 id: "SBOM-EOL-001".to_string(),
847 name: "ComponentEndOfLife".to_string(),
848 short_description: SarifMessage {
849 text: "A component has reached end-of-life".to_string(),
850 },
851 default_configuration: SarifConfiguration {
852 level: SarifLevel::Error,
853 },
854 },
855 SarifRule {
856 id: "SBOM-EOL-002".to_string(),
857 name: "ComponentApproachingEol".to_string(),
858 short_description: SarifMessage {
859 text: "A component is approaching end-of-life".to_string(),
860 },
861 default_configuration: SarifConfiguration {
862 level: SarifLevel::Warning,
863 },
864 },
865 ];
866 rules.extend(get_sarif_compliance_rules());
867 rules
868}
869
870fn get_sarif_view_rules() -> Vec<SarifRule> {
871 let mut rules = vec![SarifRule {
872 id: "SBOM-VIEW-001".to_string(),
873 name: "VulnerabilityPresent".to_string(),
874 short_description: SarifMessage {
875 text: "A vulnerability is present in a component".to_string(),
876 },
877 default_configuration: SarifConfiguration {
878 level: SarifLevel::Warning,
879 },
880 }];
881 rules.extend(get_sarif_compliance_rules());
882 rules
883}
884
885fn get_sarif_rules_for_standard(level: ComplianceLevel) -> Vec<SarifRule> {
887 match level {
888 ComplianceLevel::NtiaMinimum => get_sarif_ntia_rules(),
889 ComplianceLevel::FdaMedicalDevice => get_sarif_fda_rules(),
890 ComplianceLevel::NistSsdf => get_sarif_ssdf_rules(),
891 ComplianceLevel::Eo14028 => get_sarif_eo14028_rules(),
892 _ => get_sarif_compliance_rules(),
893 }
894}
895
896fn get_sarif_ntia_rules() -> Vec<SarifRule> {
897 vec![
898 SarifRule {
899 id: "SBOM-NTIA-AUTHOR".to_string(),
900 name: "NtiaAuthor".to_string(),
901 short_description: SarifMessage {
902 text: "NTIA Minimum Elements: Author/creator information".to_string(),
903 },
904 default_configuration: SarifConfiguration {
905 level: SarifLevel::Error,
906 },
907 },
908 SarifRule {
909 id: "SBOM-NTIA-NAME".to_string(),
910 name: "NtiaComponentName".to_string(),
911 short_description: SarifMessage {
912 text: "NTIA Minimum Elements: Component name".to_string(),
913 },
914 default_configuration: SarifConfiguration {
915 level: SarifLevel::Error,
916 },
917 },
918 SarifRule {
919 id: "SBOM-NTIA-VERSION".to_string(),
920 name: "NtiaVersion".to_string(),
921 short_description: SarifMessage {
922 text: "NTIA Minimum Elements: Component version string".to_string(),
923 },
924 default_configuration: SarifConfiguration {
925 level: SarifLevel::Warning,
926 },
927 },
928 SarifRule {
929 id: "SBOM-NTIA-SUPPLIER".to_string(),
930 name: "NtiaSupplier".to_string(),
931 short_description: SarifMessage {
932 text: "NTIA Minimum Elements: Supplier name".to_string(),
933 },
934 default_configuration: SarifConfiguration {
935 level: SarifLevel::Warning,
936 },
937 },
938 SarifRule {
939 id: "SBOM-NTIA-IDENTIFIER".to_string(),
940 name: "NtiaUniqueIdentifier".to_string(),
941 short_description: SarifMessage {
942 text: "NTIA Minimum Elements: Unique identifier (PURL/CPE/SWID)".to_string(),
943 },
944 default_configuration: SarifConfiguration {
945 level: SarifLevel::Warning,
946 },
947 },
948 SarifRule {
949 id: "SBOM-NTIA-DEPENDENCY".to_string(),
950 name: "NtiaDependency".to_string(),
951 short_description: SarifMessage {
952 text: "NTIA Minimum Elements: Dependency relationship".to_string(),
953 },
954 default_configuration: SarifConfiguration {
955 level: SarifLevel::Error,
956 },
957 },
958 SarifRule {
959 id: "SBOM-NTIA-GENERAL".to_string(),
960 name: "NtiaGeneralRequirement".to_string(),
961 short_description: SarifMessage {
962 text: "NTIA Minimum Elements: General requirement".to_string(),
963 },
964 default_configuration: SarifConfiguration {
965 level: SarifLevel::Warning,
966 },
967 },
968 ]
969}
970
971fn get_sarif_fda_rules() -> Vec<SarifRule> {
972 vec![
973 SarifRule {
974 id: "SBOM-FDA-CREATOR".to_string(),
975 name: "FdaCreator".to_string(),
976 short_description: SarifMessage {
977 text: "FDA Medical Device: SBOM creator/manufacturer information".to_string(),
978 },
979 default_configuration: SarifConfiguration {
980 level: SarifLevel::Warning,
981 },
982 },
983 SarifRule {
984 id: "SBOM-FDA-NAMESPACE".to_string(),
985 name: "FdaNamespace".to_string(),
986 short_description: SarifMessage {
987 text: "FDA Medical Device: SBOM serial number or document namespace".to_string(),
988 },
989 default_configuration: SarifConfiguration {
990 level: SarifLevel::Warning,
991 },
992 },
993 SarifRule {
994 id: "SBOM-FDA-NAME".to_string(),
995 name: "FdaDocumentName".to_string(),
996 short_description: SarifMessage {
997 text: "FDA Medical Device: SBOM document name/title".to_string(),
998 },
999 default_configuration: SarifConfiguration {
1000 level: SarifLevel::Warning,
1001 },
1002 },
1003 SarifRule {
1004 id: "SBOM-FDA-SUPPLIER".to_string(),
1005 name: "FdaSupplier".to_string(),
1006 short_description: SarifMessage {
1007 text: "FDA Medical Device: Component supplier/manufacturer information".to_string(),
1008 },
1009 default_configuration: SarifConfiguration {
1010 level: SarifLevel::Error,
1011 },
1012 },
1013 SarifRule {
1014 id: "SBOM-FDA-HASH".to_string(),
1015 name: "FdaHash".to_string(),
1016 short_description: SarifMessage {
1017 text: "FDA Medical Device: Component cryptographic hash".to_string(),
1018 },
1019 default_configuration: SarifConfiguration {
1020 level: SarifLevel::Error,
1021 },
1022 },
1023 SarifRule {
1024 id: "SBOM-FDA-IDENTIFIER".to_string(),
1025 name: "FdaIdentifier".to_string(),
1026 short_description: SarifMessage {
1027 text: "FDA Medical Device: Component unique identifier (PURL/CPE/SWID)".to_string(),
1028 },
1029 default_configuration: SarifConfiguration {
1030 level: SarifLevel::Error,
1031 },
1032 },
1033 SarifRule {
1034 id: "SBOM-FDA-VERSION".to_string(),
1035 name: "FdaVersion".to_string(),
1036 short_description: SarifMessage {
1037 text: "FDA Medical Device: Component version information".to_string(),
1038 },
1039 default_configuration: SarifConfiguration {
1040 level: SarifLevel::Error,
1041 },
1042 },
1043 SarifRule {
1044 id: "SBOM-FDA-DEPENDENCY".to_string(),
1045 name: "FdaDependency".to_string(),
1046 short_description: SarifMessage {
1047 text: "FDA Medical Device: Dependency relationships".to_string(),
1048 },
1049 default_configuration: SarifConfiguration {
1050 level: SarifLevel::Error,
1051 },
1052 },
1053 SarifRule {
1054 id: "SBOM-FDA-SUPPORT".to_string(),
1055 name: "FdaSupport".to_string(),
1056 short_description: SarifMessage {
1057 text: "FDA Medical Device: Component support/contact information".to_string(),
1058 },
1059 default_configuration: SarifConfiguration {
1060 level: SarifLevel::Note,
1061 },
1062 },
1063 SarifRule {
1064 id: "SBOM-FDA-SECURITY".to_string(),
1065 name: "FdaSecurity".to_string(),
1066 short_description: SarifMessage {
1067 text: "FDA Medical Device: Security vulnerability information".to_string(),
1068 },
1069 default_configuration: SarifConfiguration {
1070 level: SarifLevel::Warning,
1071 },
1072 },
1073 SarifRule {
1074 id: "SBOM-FDA-GENERAL".to_string(),
1075 name: "FdaGeneralRequirement".to_string(),
1076 short_description: SarifMessage {
1077 text: "FDA Medical Device: General SBOM requirement".to_string(),
1078 },
1079 default_configuration: SarifConfiguration {
1080 level: SarifLevel::Warning,
1081 },
1082 },
1083 ]
1084}
1085
1086fn get_sarif_ssdf_rules() -> Vec<SarifRule> {
1087 vec![
1088 SarifRule {
1089 id: "SBOM-SSDF-PS1".to_string(),
1090 name: "SsdfProvenance".to_string(),
1091 short_description: SarifMessage {
1092 text: "NIST SSDF PS.1: Provenance and creator identification".to_string(),
1093 },
1094 default_configuration: SarifConfiguration {
1095 level: SarifLevel::Error,
1096 },
1097 },
1098 SarifRule {
1099 id: "SBOM-SSDF-PS2".to_string(),
1100 name: "SsdfBuildIntegrity".to_string(),
1101 short_description: SarifMessage {
1102 text: "NIST SSDF PS.2: Build integrity — component cryptographic hashes"
1103 .to_string(),
1104 },
1105 default_configuration: SarifConfiguration {
1106 level: SarifLevel::Warning,
1107 },
1108 },
1109 SarifRule {
1110 id: "SBOM-SSDF-PS3".to_string(),
1111 name: "SsdfSupplierIdentification".to_string(),
1112 short_description: SarifMessage {
1113 text: "NIST SSDF PS.3: Supplier identification for components".to_string(),
1114 },
1115 default_configuration: SarifConfiguration {
1116 level: SarifLevel::Warning,
1117 },
1118 },
1119 SarifRule {
1120 id: "SBOM-SSDF-PO1".to_string(),
1121 name: "SsdfSourceProvenance".to_string(),
1122 short_description: SarifMessage {
1123 text: "NIST SSDF PO.1: Source code provenance — VCS references".to_string(),
1124 },
1125 default_configuration: SarifConfiguration {
1126 level: SarifLevel::Warning,
1127 },
1128 },
1129 SarifRule {
1130 id: "SBOM-SSDF-PO3".to_string(),
1131 name: "SsdfBuildMetadata".to_string(),
1132 short_description: SarifMessage {
1133 text: "NIST SSDF PO.3: Build provenance — build system metadata".to_string(),
1134 },
1135 default_configuration: SarifConfiguration {
1136 level: SarifLevel::Note,
1137 },
1138 },
1139 SarifRule {
1140 id: "SBOM-SSDF-PW4".to_string(),
1141 name: "SsdfDependencyManagement".to_string(),
1142 short_description: SarifMessage {
1143 text: "NIST SSDF PW.4: Dependency management — relationships".to_string(),
1144 },
1145 default_configuration: SarifConfiguration {
1146 level: SarifLevel::Error,
1147 },
1148 },
1149 SarifRule {
1150 id: "SBOM-SSDF-PW6".to_string(),
1151 name: "SsdfVulnerabilityInfo".to_string(),
1152 short_description: SarifMessage {
1153 text: "NIST SSDF PW.6: Vulnerability information and security references"
1154 .to_string(),
1155 },
1156 default_configuration: SarifConfiguration {
1157 level: SarifLevel::Note,
1158 },
1159 },
1160 SarifRule {
1161 id: "SBOM-SSDF-RV1".to_string(),
1162 name: "SsdfComponentIdentification".to_string(),
1163 short_description: SarifMessage {
1164 text: "NIST SSDF RV.1: Component identification — unique identifiers".to_string(),
1165 },
1166 default_configuration: SarifConfiguration {
1167 level: SarifLevel::Warning,
1168 },
1169 },
1170 SarifRule {
1171 id: "SBOM-SSDF-GENERAL".to_string(),
1172 name: "SsdfGeneralRequirement".to_string(),
1173 short_description: SarifMessage {
1174 text: "NIST SSDF: General secure development requirement".to_string(),
1175 },
1176 default_configuration: SarifConfiguration {
1177 level: SarifLevel::Warning,
1178 },
1179 },
1180 ]
1181}
1182
1183fn get_sarif_eo14028_rules() -> Vec<SarifRule> {
1184 vec![
1185 SarifRule {
1186 id: "SBOM-EO14028-FORMAT".to_string(),
1187 name: "Eo14028MachineReadable".to_string(),
1188 short_description: SarifMessage {
1189 text: "EO 14028 Sec 4(e): Machine-readable SBOM format requirement".to_string(),
1190 },
1191 default_configuration: SarifConfiguration {
1192 level: SarifLevel::Error,
1193 },
1194 },
1195 SarifRule {
1196 id: "SBOM-EO14028-AUTOGEN".to_string(),
1197 name: "Eo14028AutoGeneration".to_string(),
1198 short_description: SarifMessage {
1199 text: "EO 14028 Sec 4(e): Automated SBOM generation".to_string(),
1200 },
1201 default_configuration: SarifConfiguration {
1202 level: SarifLevel::Warning,
1203 },
1204 },
1205 SarifRule {
1206 id: "SBOM-EO14028-CREATOR".to_string(),
1207 name: "Eo14028Creator".to_string(),
1208 short_description: SarifMessage {
1209 text: "EO 14028 Sec 4(e): SBOM creator identification".to_string(),
1210 },
1211 default_configuration: SarifConfiguration {
1212 level: SarifLevel::Error,
1213 },
1214 },
1215 SarifRule {
1216 id: "SBOM-EO14028-IDENTIFIER".to_string(),
1217 name: "Eo14028Identifier".to_string(),
1218 short_description: SarifMessage {
1219 text: "EO 14028 Sec 4(e): Component unique identification".to_string(),
1220 },
1221 default_configuration: SarifConfiguration {
1222 level: SarifLevel::Error,
1223 },
1224 },
1225 SarifRule {
1226 id: "SBOM-EO14028-DEPENDENCY".to_string(),
1227 name: "Eo14028Dependency".to_string(),
1228 short_description: SarifMessage {
1229 text: "EO 14028 Sec 4(e): Dependency relationship information".to_string(),
1230 },
1231 default_configuration: SarifConfiguration {
1232 level: SarifLevel::Error,
1233 },
1234 },
1235 SarifRule {
1236 id: "SBOM-EO14028-VERSION".to_string(),
1237 name: "Eo14028Version".to_string(),
1238 short_description: SarifMessage {
1239 text: "EO 14028 Sec 4(e): Component version information".to_string(),
1240 },
1241 default_configuration: SarifConfiguration {
1242 level: SarifLevel::Error,
1243 },
1244 },
1245 SarifRule {
1246 id: "SBOM-EO14028-INTEGRITY".to_string(),
1247 name: "Eo14028Integrity".to_string(),
1248 short_description: SarifMessage {
1249 text: "EO 14028 Sec 4(e): Component integrity verification (hashes)".to_string(),
1250 },
1251 default_configuration: SarifConfiguration {
1252 level: SarifLevel::Warning,
1253 },
1254 },
1255 SarifRule {
1256 id: "SBOM-EO14028-DISCLOSURE".to_string(),
1257 name: "Eo14028Disclosure".to_string(),
1258 short_description: SarifMessage {
1259 text: "EO 14028 Sec 4(g): Vulnerability disclosure process".to_string(),
1260 },
1261 default_configuration: SarifConfiguration {
1262 level: SarifLevel::Warning,
1263 },
1264 },
1265 SarifRule {
1266 id: "SBOM-EO14028-SUPPLIER".to_string(),
1267 name: "Eo14028Supplier".to_string(),
1268 short_description: SarifMessage {
1269 text: "EO 14028 Sec 4(e): Supplier identification".to_string(),
1270 },
1271 default_configuration: SarifConfiguration {
1272 level: SarifLevel::Warning,
1273 },
1274 },
1275 SarifRule {
1276 id: "SBOM-EO14028-GENERAL".to_string(),
1277 name: "Eo14028GeneralRequirement".to_string(),
1278 short_description: SarifMessage {
1279 text: "EO 14028: General SBOM requirement".to_string(),
1280 },
1281 default_configuration: SarifConfiguration {
1282 level: SarifLevel::Warning,
1283 },
1284 },
1285 ]
1286}
1287
1288fn get_sarif_compliance_rules() -> Vec<SarifRule> {
1289 vec![
1290 SarifRule {
1291 id: "SBOM-CRA-ART-13-3".to_string(),
1292 name: "CraUpdateFrequency".to_string(),
1293 short_description: SarifMessage {
1294 text: "CRA Art. 13(3): SBOM update frequency — timely regeneration after changes".to_string(),
1295 },
1296 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1297 },
1298 SarifRule {
1299 id: "SBOM-CRA-ART-13-4".to_string(),
1300 name: "CraMachineReadableFormat".to_string(),
1301 short_description: SarifMessage {
1302 text: "CRA Art. 13(4): SBOM must be in a machine-readable format (CycloneDX 1.4+, SPDX 2.3+, or SPDX 3.0+)".to_string(),
1303 },
1304 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1305 },
1306 SarifRule {
1307 id: "SBOM-CRA-ART-13-6".to_string(),
1308 name: "CraVulnerabilityDisclosure".to_string(),
1309 short_description: SarifMessage {
1310 text: "CRA Art. 13(6): Vulnerability disclosure contact and metadata completeness".to_string(),
1311 },
1312 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1313 },
1314 SarifRule {
1315 id: "SBOM-CRA-ART-13-5".to_string(),
1316 name: "CraLicensedComponentTracking".to_string(),
1317 short_description: SarifMessage {
1318 text: "CRA Art. 13(5): Licensed component tracking — license information for all components".to_string(),
1319 },
1320 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1321 },
1322 SarifRule {
1323 id: "SBOM-CRA-ART-13-7".to_string(),
1324 name: "CraCoordinatedDisclosure".to_string(),
1325 short_description: SarifMessage {
1326 text: "CRA Art. 13(7): Coordinated vulnerability disclosure policy reference".to_string(),
1327 },
1328 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1329 },
1330 SarifRule {
1331 id: "SBOM-CRA-ART-13-8".to_string(),
1332 name: "CraSupportPeriod".to_string(),
1333 short_description: SarifMessage {
1334 text: "CRA Art. 13(8): Support period and security update end date".to_string(),
1335 },
1336 default_configuration: SarifConfiguration { level: SarifLevel::Note },
1337 },
1338 SarifRule {
1339 id: "SBOM-CRA-ART-13-11".to_string(),
1340 name: "CraComponentLifecycle".to_string(),
1341 short_description: SarifMessage {
1342 text: "CRA Art. 13(11): Component lifecycle and end-of-support status".to_string(),
1343 },
1344 default_configuration: SarifConfiguration { level: SarifLevel::Note },
1345 },
1346 SarifRule {
1347 id: "SBOM-CRA-ART-13-12".to_string(),
1348 name: "CraProductIdentification".to_string(),
1349 short_description: SarifMessage {
1350 text: "CRA Art. 13(12): Product name and version identification".to_string(),
1351 },
1352 default_configuration: SarifConfiguration { level: SarifLevel::Error },
1353 },
1354 SarifRule {
1355 id: "SBOM-CRA-ART-13-15".to_string(),
1356 name: "CraManufacturerIdentification".to_string(),
1357 short_description: SarifMessage {
1358 text: "CRA Art. 13(15): Manufacturer identification and contact information".to_string(),
1359 },
1360 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1361 },
1362 SarifRule {
1363 id: "SBOM-CRA-ART-13-9".to_string(),
1364 name: "CraKnownVulnerabilities".to_string(),
1365 short_description: SarifMessage {
1366 text: "CRA Art. 13(9): Known vulnerabilities statement — vulnerability data or assertion".to_string(),
1367 },
1368 default_configuration: SarifConfiguration { level: SarifLevel::Note },
1369 },
1370 SarifRule {
1371 id: "SBOM-CRA-ANNEX-I".to_string(),
1372 name: "CraTechnicalDocumentation".to_string(),
1373 short_description: SarifMessage {
1374 text: "CRA Annex I: Technical documentation (unique identifiers, dependencies, primary component)".to_string(),
1375 },
1376 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1377 },
1378 SarifRule {
1379 id: "SBOM-CRA-ANNEX-III".to_string(),
1380 name: "CraDocumentIntegrity".to_string(),
1381 short_description: SarifMessage {
1382 text: "CRA Annex III: Document signature/integrity — serial number, hash, or digital signature".to_string(),
1383 },
1384 default_configuration: SarifConfiguration { level: SarifLevel::Note },
1385 },
1386 SarifRule {
1387 id: "SBOM-CRA-ANNEX-VII".to_string(),
1388 name: "CraDeclarationOfConformity".to_string(),
1389 short_description: SarifMessage {
1390 text: "CRA Annex VII: EU Declaration of Conformity reference".to_string(),
1391 },
1392 default_configuration: SarifConfiguration { level: SarifLevel::Note },
1393 },
1394 SarifRule {
1395 id: "SBOM-CRA-GENERAL".to_string(),
1396 name: "CraGeneralRequirement".to_string(),
1397 short_description: SarifMessage {
1398 text: "CRA general SBOM readiness requirement".to_string(),
1399 },
1400 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1401 },
1402 SarifRule {
1403 id: "SBOM-CRA-PRE-8-RQ-02".to_string(),
1404 name: "CraHardwareInventory".to_string(),
1405 short_description: SarifMessage {
1406 text: "CRA prEN 40000-1-3 [PRE-8-RQ-02]: Hardware components must be inventoried with producer, name, identifier, and firmware version"
1407 .to_string(),
1408 },
1409 default_configuration: SarifConfiguration { level: SarifLevel::Error },
1410 },
1411 SarifRule {
1412 id: "SBOM-CRA-PRE-7-RQ-07-RE".to_string(),
1413 name: "CraVendorHashCarryThrough".to_string(),
1414 short_description: SarifMessage {
1415 text: "CRA prEN 40000-1-3 [PRE-7-RQ-07-RE]: Upstream vendor-supplied component hashes must be carried through into the SBOM"
1416 .to_string(),
1417 },
1418 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1419 },
1420 SarifRule {
1421 id: "SBOM-BSI-TR-03183-2-5-1".to_string(),
1422 name: "BsiTr03183AuthorTool".to_string(),
1423 short_description: SarifMessage {
1424 text: "BSI TR-03183-2 §5.1: SBOM author/tool identification".to_string(),
1425 },
1426 default_configuration: SarifConfiguration { level: SarifLevel::Error },
1427 },
1428 SarifRule {
1429 id: "SBOM-BSI-TR-03183-2-5-2".to_string(),
1430 name: "BsiTr03183Timestamp".to_string(),
1431 short_description: SarifMessage {
1432 text: "BSI TR-03183-2 §5.2: ISO-8601 timestamp".to_string(),
1433 },
1434 default_configuration: SarifConfiguration { level: SarifLevel::Error },
1435 },
1436 SarifRule {
1437 id: "SBOM-BSI-TR-03183-2-5-3".to_string(),
1438 name: "BsiTr03183ComponentIdentifier".to_string(),
1439 short_description: SarifMessage {
1440 text: "BSI TR-03183-2 §5.3: Component name and unique identifier".to_string(),
1441 },
1442 default_configuration: SarifConfiguration { level: SarifLevel::Error },
1443 },
1444 SarifRule {
1445 id: "SBOM-BSI-TR-03183-2-5-4".to_string(),
1446 name: "BsiTr03183ComponentHash".to_string(),
1447 short_description: SarifMessage {
1448 text: "BSI TR-03183-2 §5.4: Component cryptographic hash (SHA-256+)".to_string(),
1449 },
1450 default_configuration: SarifConfiguration { level: SarifLevel::Error },
1451 },
1452 SarifRule {
1453 id: "SBOM-BSI-TR-03183-2-5-5".to_string(),
1454 name: "BsiTr03183Dependencies".to_string(),
1455 short_description: SarifMessage {
1456 text: "BSI TR-03183-2 §5.5: Dependency relationships".to_string(),
1457 },
1458 default_configuration: SarifConfiguration { level: SarifLevel::Error },
1459 },
1460 SarifRule {
1461 id: "SBOM-BSI-TR-03183-2-6".to_string(),
1462 name: "BsiTr03183Recommended".to_string(),
1463 short_description: SarifMessage {
1464 text: "BSI TR-03183-2 §6: Recommended fields (license, supplier, lifecycle)".to_string(),
1465 },
1466 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1467 },
1468 SarifRule {
1469 id: "SBOM-BSI-TR-03183-2-GENERAL".to_string(),
1470 name: "BsiTr03183General".to_string(),
1471 short_description: SarifMessage {
1472 text: "BSI TR-03183-2 general SBOM requirement".to_string(),
1473 },
1474 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1475 },
1476 SarifRule {
1478 id: "SBOM-AIACT-NA".to_string(),
1479 name: "AiActNotApplicable".to_string(),
1480 short_description: SarifMessage {
1481 text: "EU AI Act Annex IV readiness not applicable — SBOM has no ML-model or dataset components".to_string(),
1482 },
1483 default_configuration: SarifConfiguration { level: SarifLevel::Note },
1484 },
1485 SarifRule {
1486 id: "SBOM-AIACT-ANNEX-IV-1".to_string(),
1487 name: "AiActGeneralDescription".to_string(),
1488 short_description: SarifMessage {
1489 text: "EU AI Act Annex IV §1: general description of the AI system (architecture, intended purpose)".to_string(),
1490 },
1491 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1492 },
1493 SarifRule {
1494 id: "SBOM-AIACT-ANNEX-IV-2D".to_string(),
1495 name: "AiActTrainingData".to_string(),
1496 short_description: SarifMessage {
1497 text: "EU AI Act Annex IV §2(d): training-data characteristics, provenance, and sensitivity classification".to_string(),
1498 },
1499 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1500 },
1501 SarifRule {
1502 id: "SBOM-AIACT-ANNEX-IV-2G".to_string(),
1503 name: "AiActValidationMetrics".to_string(),
1504 short_description: SarifMessage {
1505 text: "EU AI Act Annex IV §2(g): validation/testing metrics and computational-resources disclosure".to_string(),
1506 },
1507 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1508 },
1509 SarifRule {
1510 id: "SBOM-AIACT-ANNEX-IV-3".to_string(),
1511 name: "AiActLimitations".to_string(),
1512 short_description: SarifMessage {
1513 text: "EU AI Act Annex IV §3: foreseeable limitations and risks".to_string(),
1514 },
1515 default_configuration: SarifConfiguration { level: SarifLevel::Note },
1516 },
1517 SarifRule {
1519 id: "SBOM-BSIAI-NA".to_string(),
1520 name: "BsiSbomForAiNotApplicable".to_string(),
1521 short_description: SarifMessage {
1522 text: "BSI/G7 SBOM-for-AI minimum-elements readiness not applicable — SBOM has no ML-model or dataset components".to_string(),
1523 },
1524 default_configuration: SarifConfiguration { level: SarifLevel::Note },
1525 },
1526 SarifRule {
1527 id: "SBOM-BSIAI-META".to_string(),
1528 name: "BsiSbomForAiMetadata".to_string(),
1529 short_description: SarifMessage {
1530 text: "BSI/G7 SBOM-for-AI Metadata cluster: author, data-format name + version, timestamp, generation tool, signature".to_string(),
1531 },
1532 default_configuration: SarifConfiguration { level: SarifLevel::Error },
1533 },
1534 SarifRule {
1535 id: "SBOM-BSIAI-SYS".to_string(),
1536 name: "BsiSbomForAiSystemLevel".to_string(),
1537 short_description: SarifMessage {
1538 text: "BSI/G7 SBOM-for-AI System-Level cluster: primary AI system, producer, data flow & usage".to_string(),
1539 },
1540 default_configuration: SarifConfiguration { level: SarifLevel::Warning },
1541 },
1542 SarifRule {
1543 id: "SBOM-BSIAI-MODEL".to_string(),
1544 name: "BsiSbomForAiModels".to_string(),
1545 short_description: SarifMessage {
1546 text: "BSI/G7 SBOM-for-AI Models cluster: name, version, identifier, weight hash (NIST-approved algorithm), model card, architecture, datasets, limitations, license".to_string(),
1547 },
1548 default_configuration: SarifConfiguration { level: SarifLevel::Error },
1549 },
1550 SarifRule {
1551 id: "SBOM-BSIAI-DATASET".to_string(),
1552 name: "BsiSbomForAiDatasets".to_string(),
1553 short_description: SarifMessage {
1554 text: "BSI/G7 SBOM-for-AI Datasets cluster: name, identifier, hash, license, sensitivity classification, provenance & intended use".to_string(),
1555 },
1556 default_configuration: SarifConfiguration { level: SarifLevel::Error },
1557 },
1558 SarifRule {
1559 id: "SBOM-BSIAI-INFRA".to_string(),
1560 name: "BsiSbomForAiInfrastructure".to_string(),
1561 short_description: SarifMessage {
1562 text: "BSI/G7 SBOM-for-AI Infrastructure cluster: runtime / framework dependency links".to_string(),
1563 },
1564 default_configuration: SarifConfiguration { level: SarifLevel::Note },
1565 },
1566 SarifRule {
1567 id: "SBOM-BSIAI-SEC".to_string(),
1568 name: "BsiSbomForAiSecurity".to_string(),
1569 short_description: SarifMessage {
1570 text: "BSI/G7 SBOM-for-AI Security cluster: AI-specific security controls, exploitability references".to_string(),
1571 },
1572 default_configuration: SarifConfiguration { level: SarifLevel::Note },
1573 },
1574 ]
1575}
1576
1577#[derive(Serialize)]
1580#[serde(rename_all = "camelCase")]
1581struct SarifReport {
1582 #[serde(rename = "$schema")]
1583 schema: String,
1584 version: String,
1585 runs: Vec<SarifRun>,
1586}
1587
1588#[derive(Serialize)]
1589#[serde(rename_all = "camelCase")]
1590struct SarifRun {
1591 tool: SarifTool,
1592 results: Vec<SarifResult>,
1593 #[serde(skip_serializing_if = "Option::is_none")]
1594 properties: Option<SarifRunProperties>,
1595}
1596
1597#[derive(Serialize)]
1603#[serde(rename_all = "camelCase")]
1604struct SarifRunProperties {
1605 applicable: bool,
1606 overall_score: Option<f32>,
1607 grade: String,
1608 sbom: String,
1609 profile: String,
1610}
1611
1612#[derive(Serialize)]
1613#[serde(rename_all = "camelCase")]
1614struct SarifTool {
1615 driver: SarifDriver,
1616}
1617
1618#[derive(Serialize)]
1619#[serde(rename_all = "camelCase")]
1620struct SarifDriver {
1621 name: String,
1622 version: String,
1623 information_uri: String,
1624 rules: Vec<SarifRuleWithUri>,
1625}
1626
1627#[derive(Serialize)]
1628#[serde(rename_all = "camelCase")]
1629struct SarifRule {
1630 id: String,
1631 name: String,
1632 short_description: SarifMessage,
1633 default_configuration: SarifConfiguration,
1634}
1635
1636#[derive(Serialize)]
1641#[serde(rename_all = "camelCase")]
1642struct SarifRuleWithUri {
1643 #[serde(flatten)]
1644 inner: SarifRule,
1645 #[serde(skip_serializing_if = "Option::is_none")]
1646 help_uri: Option<&'static str>,
1647}
1648
1649impl SarifRuleWithUri {
1650 fn wrap(inner: SarifRule) -> Self {
1651 let help_uri = rule_help_uri(&inner.id);
1652 Self { inner, help_uri }
1653 }
1654
1655 fn wrap_all(rules: Vec<SarifRule>) -> Vec<Self> {
1656 rules.into_iter().map(Self::wrap).collect()
1657 }
1658}
1659
1660#[derive(Serialize)]
1661#[serde(rename_all = "camelCase")]
1662struct SarifConfiguration {
1663 level: SarifLevel,
1664}
1665
1666#[derive(Serialize)]
1667#[serde(rename_all = "camelCase")]
1668struct SarifResult {
1669 rule_id: String,
1670 level: SarifLevel,
1671 message: SarifMessage,
1672 locations: Vec<SarifLocation>,
1673 #[serde(skip_serializing_if = "Option::is_none")]
1677 properties: Option<SarifResultProperties>,
1678}
1679
1680#[derive(Serialize)]
1681#[serde(rename_all = "camelCase")]
1682struct SarifResultProperties {
1683 #[serde(skip_serializing_if = "Vec::is_empty")]
1687 standard_ids: Vec<String>,
1688 #[serde(skip_serializing_if = "Vec::is_empty")]
1692 standard_help_uris: Vec<String>,
1693}
1694
1695#[derive(Serialize)]
1696#[serde(rename_all = "camelCase")]
1697struct SarifMessage {
1698 text: String,
1699}
1700
1701#[derive(Serialize)]
1702#[serde(rename_all = "camelCase")]
1703struct SarifLocation {
1704 physical_location: Option<SarifPhysicalLocation>,
1705}
1706
1707#[derive(Serialize)]
1708#[serde(rename_all = "camelCase")]
1709struct SarifPhysicalLocation {
1710 artifact_location: SarifArtifactLocation,
1711}
1712
1713#[derive(Serialize)]
1714#[serde(rename_all = "camelCase")]
1715struct SarifArtifactLocation {
1716 uri: String,
1717}
1718
1719#[derive(Serialize, Clone, Copy)]
1720#[serde(rename_all = "lowercase")]
1721enum SarifLevel {
1722 #[allow(dead_code)]
1723 None,
1724 Note,
1725 Warning,
1726 Error,
1727}