mod archive; mod html; mod pdf; use compliance_core::models::dast::DastFinding; use compliance_core::models::finding::Finding; use compliance_core::models::pentest::{ AttackChainNode, CodeContextHint, PentestConfig, PentestSession, }; use compliance_core::models::sbom::SbomEntry; use sha2::{Digest, Sha256}; /// Report archive with metadata pub struct ReportArchive { /// The password-protected ZIP bytes pub archive: Vec, /// SHA-256 hex digest of the archive pub sha256: String, } /// Report context gathered from the database pub struct ReportContext { pub session: PentestSession, pub target_name: String, pub target_url: String, pub findings: Vec, pub attack_chain: Vec, pub requester_name: String, pub requester_email: String, pub config: Option, /// SAST findings for the linked repository (for code-level correlation) pub sast_findings: Vec, /// Vulnerable dependencies from SBOM pub sbom_entries: Vec, /// Code knowledge graph entry points linked to SAST findings pub code_context: Vec, } /// Generate a password-protected ZIP archive containing the pentest report. /// /// The archive contains: /// - `report.pdf` — Professional pentest report (PDF) /// - `report.html` — HTML source (fallback) /// - `findings.json` — Raw findings data /// - `attack-chain.json` — Attack chain timeline /// /// Files are encrypted with AES-256 inside the ZIP (standard WinZip AES format, /// supported by 7-Zip, WinRAR, macOS Archive Utility, etc.). pub async fn generate_encrypted_report( ctx: &ReportContext, password: &str, ) -> Result { let html = html::build_html_report(ctx); // Convert HTML to PDF via headless Chrome let pdf_bytes = pdf::html_to_pdf(&html).await?; let zip_bytes = archive::build_zip(ctx, password, &html, &pdf_bytes) .map_err(|e| format!("Failed to create archive: {e}"))?; let mut hasher = Sha256::new(); hasher.update(&zip_bytes); let sha256 = hex::encode(hasher.finalize()); Ok(ReportArchive { archive: zip_bytes, sha256, }) }