Skip to content

Commit ecfe2bf

Browse files
authored
Merge pull request #45 from mdabir1203/codex/integrate-shadowmap-cli-into-project-39ay8e
[shadowmap] fix: persist simulated pipeline artifacts
2 parents ab1fc65 + e07cca1 commit ecfe2bf

1 file changed

Lines changed: 158 additions & 4 deletions

File tree

src/cli.rs

Lines changed: 158 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use clap::{Parser, Subcommand};
22
use colored::*;
33
use std::fmt;
4+
use std::fs;
45
use std::io::{self, Write};
5-
use std::path::PathBuf;
6+
use std::path::{Path, PathBuf};
7+
use chrono::Utc;
8+
use serde_json::json;
69

710
use crate::{run, Args, BoxError};
811

@@ -98,6 +101,7 @@ const SIMULATED_VULNERABILITIES: &[SimulatedVulnerability] = &[
98101
summary: "Verbose logging can expose bearer tokens in debug mode.",
99102
},
100103
];
104+
101105
// ============================================================================
102106
// CLI STRUCTURE - Elegant User Experience
103107
// ============================================================================
@@ -567,6 +571,9 @@ impl ShadowMapCLI {
567571
TerminalUI::show_progress("Resolving transitive dependencies");
568572
TerminalUI::show_progress(&format!("Generating {} SBOM", format));
569573

574+
let sbom_payload = Self::render_sbom_stub(&manifest, &format);
575+
Self::write_stub_file(&output, &sbom_payload)?;
576+
570577
TerminalUI::print_success(&format!("SBOM generated: {}", output.display()));
571578
TerminalUI::print_section_end();
572579

@@ -585,7 +592,8 @@ impl ShadowMapCLI {
585592
TerminalUI::show_progress("Loading SBOM");
586593
TerminalUI::show_progress("Querying vulnerability databases");
587594
TerminalUI::show_progress("Analyzing CVE matches");
588-
595+
596+
589597
let vulnerabilities = SIMULATED_VULNERABILITIES;
590598
let (critical, high, medium, low) = Self::count_vulnerabilities(vulnerabilities);
591599

@@ -615,6 +623,9 @@ impl ShadowMapCLI {
615623
TerminalUI::show_progress(&format!("Generating {} report", format.to_uppercase()));
616624
TerminalUI::show_progress("Checking compliance standards");
617625

626+
let report_payload = Self::render_report_stub(&format, SIMULATED_VULNERABILITIES);
627+
Self::write_stub_file(&output, &report_payload)?;
628+
618629
TerminalUI::print_success(&format!("Report generated: {}", output.display()));
619630
TerminalUI::print_info("✓ EO 14028 compliant");
620631
TerminalUI::print_info("✓ NIS2 compliant");
@@ -651,11 +662,16 @@ impl ShadowMapCLI {
651662
if let Some(threshold) = &fail_on {
652663
TerminalUI::print_info(&format!("Fail threshold: {}", threshold));
653664
}
665+
666+
fs::create_dir_all(&output_dir).map_err(|err| -> BoxError { Box::new(err) })?;
654667

655668
// Step 1: Generate SBOM
656669
TerminalUI::print_step(1, 3, "Generate SBOM");
657670
TerminalUI::show_progress("Analyzing project dependencies");
658-
TerminalUI::print_success("SBOM created");
671+
let sbom_path = output_dir.join("sbom.json");
672+
let sbom_payload = Self::render_sbom_stub(&manifest, "cyclonedx");
673+
Self::write_stub_file(&sbom_path, &sbom_payload)?;
674+
TerminalUI::print_success(&format!("SBOM created: {}", sbom_path.display()));
659675

660676
// Step 2: Vulnerability Scan
661677
TerminalUI::print_step(2, 3, "Vulnerability Scan");
@@ -664,6 +680,13 @@ impl ShadowMapCLI {
664680
let (critical, high, medium, low) = Self::count_vulnerabilities(vulnerabilities);
665681
TerminalUI::print_vulnerability_summary(critical, high, medium, low);
666682
TerminalUI::print_vulnerability_table(vulnerabilities);
683+
let scan_results_path = output_dir.join("scan-results.json");
684+
let scan_payload = Self::render_scan_results_stub(vulnerabilities);
685+
Self::write_stub_file(&scan_results_path, &scan_payload)?;
686+
TerminalUI::print_info(&format!(
687+
"Scan results saved: {}",
688+
scan_results_path.display()
689+
));
667690

668691
if let Some(threshold) = &fail_on {
669692
if Self::should_fail(threshold, critical, high, medium, low) {
@@ -679,7 +702,10 @@ impl ShadowMapCLI {
679702
// Step 3: Generate Reports
680703
TerminalUI::print_step(3, 3, "Generate Reports");
681704
TerminalUI::show_progress("Creating compliance reports");
682-
TerminalUI::print_success(&format!("Reports saved to {}/", output_dir.display()));
705+
let report_path = output_dir.join("report.json");
706+
let report_payload = Self::render_report_stub("json", vulnerabilities);
707+
Self::write_stub_file(&report_path, &report_payload)?;
708+
TerminalUI::print_success(&format!("Report saved: {}", report_path.display()));
683709

684710
println!();
685711
println!("{}", "═".repeat(70).bright_green());
@@ -785,6 +811,8 @@ impl ShadowMapCLI {
785811
}
786812

787813
// Helper functions
814+
815+
788816
fn should_fail(threshold: &str, critical: u32, high: u32, medium: u32, low: u32) -> bool {
789817
match threshold.to_lowercase().as_str() {
790818
"critical" => critical > 0,
@@ -809,4 +837,130 @@ impl ShadowMapCLI {
809837

810838
counts
811839
}
840+
841+
fn render_sbom_stub(manifest: &Path, format: &str) -> String {
842+
let sbom = json!({
843+
"format": format,
844+
"manifest": manifest.display().to_string(),
845+
"generated_at": Utc::now().to_rfc3339(),
846+
"components": [
847+
{
848+
"name": "shadowmap",
849+
"version": VERSION,
850+
"type": "application",
851+
"licenses": ["Apache-2.0", "MIT"],
852+
},
853+
{
854+
"name": "openssl",
855+
"version": "3.2.1",
856+
"type": "library",
857+
"licenses": ["Apache-2.0"],
858+
},
859+
{
860+
"name": "tokio",
861+
"version": "1.40.0",
862+
"type": "library",
863+
"licenses": ["MIT"],
864+
},
865+
],
866+
});
867+
868+
serde_json::to_string_pretty(&sbom).unwrap_or_else(|_| "{}".to_string())
869+
}
870+
871+
fn render_scan_results_stub(vulnerabilities: &[SimulatedVulnerability]) -> String {
872+
let (critical, high, medium, low) = Self::count_vulnerabilities(vulnerabilities);
873+
let total = critical + high + medium + low;
874+
875+
let findings: Vec<_> = vulnerabilities
876+
.iter()
877+
.map(|v| {
878+
json!({
879+
"severity": v.severity.to_string(),
880+
"package": v.package,
881+
"identifier": v.identifier,
882+
"summary": v.summary,
883+
})
884+
})
885+
.collect();
886+
887+
let scan = json!({
888+
"generated_at": Utc::now().to_rfc3339(),
889+
"summary": {
890+
"critical": critical,
891+
"high": high,
892+
"medium": medium,
893+
"low": low,
894+
"total": total,
895+
},
896+
"findings": findings,
897+
});
898+
899+
serde_json::to_string_pretty(&scan).unwrap_or_else(|_| "{}".to_string())
900+
}
901+
902+
fn render_report_stub(format: &str, vulnerabilities: &[SimulatedVulnerability]) -> String {
903+
let (critical, high, medium, low) = Self::count_vulnerabilities(vulnerabilities);
904+
let total = critical + high + medium + low;
905+
let generated_at = Utc::now().to_rfc3339();
906+
907+
match format.to_lowercase().as_str() {
908+
"json" => {
909+
let report = json!({
910+
"generated_at": generated_at,
911+
"summary": {
912+
"critical": critical,
913+
"high": high,
914+
"medium": medium,
915+
"low": low,
916+
"total": total,
917+
},
918+
"compliance": {
919+
"eo_14028": true,
920+
"nis2": true,
921+
"cra": true,
922+
},
923+
"notes": "Simulated data – replace with real scan findings in production.",
924+
});
925+
926+
serde_json::to_string_pretty(&report).unwrap_or_else(|_| "{}".to_string())
927+
}
928+
"markdown" => {
929+
format!(
930+
"# ShadowMap Security Report\n\nGenerated: {}\n\n| Severity | Count |\n| --- | ---: |\n| Critical | {} |\n| High | {} |\n| Medium | {} |\n| Low | {} |\n| **Total** | **{}** |\n\n- EO 14028 compliant\n- NIS2 compliant\n- CRA compliant\n",
931+
generated_at, critical, high, medium, low, total
932+
)
933+
}
934+
"html" => {
935+
format!(
936+
"<html><head><title>ShadowMap Report</title></head><body><h1>ShadowMap Security Report</h1><p>Generated: {}</p><table border=\"1\" cellpadding=\"6\"><thead><tr><th>Severity</th><th>Count</th></tr></thead><tbody><tr><td>Critical</td><td>{}</td></tr><tr><td>High</td><td>{}</td></tr><tr><td>Medium</td><td>{}</td></tr><tr><td>Low</td><td>{}</td></tr><tr><td><strong>Total</strong></td><td><strong>{}</strong></td></tr></tbody></table><p>Compliance: EO 14028 ✅ | NIS2 ✅ | CRA ✅</p></body></html>",
937+
generated_at, critical, high, medium, low, total
938+
)
939+
}
940+
"pdf" => {
941+
format!(
942+
"ShadowMap PDF report placeholder\nGenerated: {}\nTotal findings: {} (Critical: {}, High: {}, Medium: {}, Low: {})\nCompliance: EO 14028 ✓, NIS2 ✓, CRA ✓\n",
943+
generated_at, total, critical, high, medium, low
944+
)
945+
}
946+
_ => {
947+
format!(
948+
"ShadowMap report generated at {}\nTotal findings: {} (Critical: {}, High: {}, Medium: {}, Low: {})\nCompliance targets met: EO 14028, NIS2, CRA\n",
949+
generated_at, total, critical, high, medium, low
950+
)
951+
}
952+
}
953+
}
954+
955+
fn write_stub_file(path: &Path, contents: &str) -> Result<(), BoxError> {
956+
if let Some(parent) = path.parent() {
957+
if !parent.as_os_str().is_empty() {
958+
fs::create_dir_all(parent).map_err(|err| -> BoxError { Box::new(err) })?;
959+
}
960+
}
961+
962+
fs::write(path, contents).map_err(|err| -> BoxError { Box::new(err) })?;
963+
964+
Ok(())
965+
}
812966
}

0 commit comments

Comments
 (0)