// SPDX-License-Identifier: MPL-2.0 //! Scanner — subprocess wrapper for the `panic-attack` binary. //! //! Panicbot does NOT link against panic-attack as a Rust crate. Instead, it //! invokes `panic-attack` as a subprocess with `--output-format json` and //! parses stdout. This matches how Hypatia already integrates with it, and //! keeps the coupling loose — panic-attack can be upgraded independently. //! //! Three entry points: //! - [`run_assail`] — core static analysis (WeakPoints, taint matrix, stats) //! - [`run_adjudicate`] — multi-report verdict across several scan outputs //! - [`run_diagnostics`] — preflight health check (binary presence, version) use anyhow::{bail, Context, Result}; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use std::process::Command; use std::time::Duration; /// Report from `panic-attack assail --output-format json`. /// /// This is the primary scan output. Contains weak points (static analysis /// findings), program statistics, taint matrix, and recommended attack axes. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AssailReport { /// Path to the scanned program/repository. pub program_path: PathBuf, /// Detected primary language. pub language: String, /// Detected frameworks. #[serde(default)] pub frameworks: Vec, /// All detected weak points (the core findings). pub weak_points: Vec, /// Aggregate program statistics. #[serde(default)] pub statistics: serde_json::Value, /// Per-file statistics. #[serde(default)] pub file_statistics: Vec, /// Recommended attack axes (for dynamic testing). #[serde(default)] pub recommended_attacks: Vec, /// Dependency graph summary. #[serde(default)] pub dependency_graph: serde_json::Value, /// Taint matrix (source → sink tracking). #[serde(default)] pub taint_matrix: serde_json::Value, } /// A single weak point detected by panic-attack's static analysis. /// /// Maps 1:1 to panic-attack's `WeakPoint` struct. The `category` field /// determines which fleet category and triangle tier this finding maps to /// (see `translator.rs`). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WeakPoint { /// Category of the weak point (e.g., "UnsafeCode", "CommandInjection"). pub category: String, /// File location (e.g., "src/ffi.rs:42"). pub location: Option, /// Severity level (Low, Medium, High, Critical). pub severity: String, /// Human-readable description of the issue. pub description: String, /// Recommended attack axes for dynamic validation. #[serde(default)] pub recommended_attack: Vec, /// Set by panic-attack's context-aware FP suppression engine. /// Suppressed items are excluded from fleet counts and CI gates. #[serde(default)] pub suppressed: bool, /// Test-vs-production classification of the finding's location /// (panic-attack v2.5.5+). `None` means the scanner predates the /// classification field; treat as "Production" for downstream /// routing decisions. `"test_only"` and `"doc"` indicate the /// finding is in test or documentation code respectively — usually /// the panic-attack engine has ALREADY set `suppressed: true` for /// these, but the metadata is preserved so the translator can emit /// audit-trail context (e.g. "PanicPath in tests/foo.rs — accepted /// by test_context rule"). #[serde(default)] pub test_context: Option, } /// Report from `panic-attack adjudicate` — cross-report verdict. /// /// NOTE: this models a report shape panic-attack 2.5.5 does not write (it /// writes [`AdjudicateVerdict`]). Kept because it is public; the suite uses /// [`run_adjudicate_verdict`]. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AdjudicateReport { /// Number of reports adjudicated. pub reports_analyzed: usize, /// Overall robustness score (0-100). pub robustness_score: f64, /// Critical issues that appeared across multiple reports. #[serde(default)] pub critical_issues: Vec, /// Consolidated recommendations. #[serde(default)] pub recommendations: Vec, } /// Report from `panic-attack migration-snapshot --label