Skip to main content

fleetreach_cli/
cli.rs

1//! The command-line surface: the clap command tree, the shared value-enums it
2//! parses into, and the top-level dispatch. The thin binary calls
3//! [`Cli::try_parse`] then [`dispatch`]; the per-command logic lives in
4//! [`crate::scan`] and [`crate::vex`].
5
6use clap::{Parser, Subcommand, ValueEnum};
7use fleetreach_core::Severity;
8use fleetreach_report as report;
9
10use crate::diff::DiffArgs;
11use crate::scan::ScanArgs;
12use crate::vex::{VexCheckArgs, VexVerifyArgs};
13
14#[derive(Parser)]
15#[command(
16    name = "fleetreach",
17    version,
18    about = "Fleet-native dependency security audit across 12 ecosystems"
19)]
20pub struct Cli {
21    #[command(subcommand)]
22    command: Commands,
23}
24
25#[derive(Subcommand)]
26// `ScanArgs` is far larger than the VEX subcommands; boxing the variant fights
27// clap's `Args` derive, so accept the size skew on this top-level command enum.
28#[allow(clippy::large_enum_variant)]
29enum Commands {
30    /// Scan the fleet for advisories.
31    Scan(ScanArgs),
32    /// Compare two saved JSON reports: new vs. fixed vs. still-open findings.
33    Diff(DiffArgs),
34    /// Operate on OpenVEX documents (drift gate, witness verification).
35    #[command(subcommand)]
36    Vex(VexCommand),
37}
38
39#[derive(Subcommand)]
40enum VexCommand {
41    /// Fail when a committed OpenVEX document has drifted from a fresh scan (§10).
42    Check(VexCheckArgs),
43    /// Re-derive each machine `not_affected` witness against current source (§9.2).
44    Verify(VexVerifyArgs),
45}
46
47/// Run the parsed command, returning the process exit code (§8).
48pub fn dispatch(cli: Cli) -> u8 {
49    match cli.command {
50        Commands::Scan(args) => crate::scan::run_scan(args),
51        Commands::Diff(args) => crate::diff::run_diff(args),
52        Commands::Vex(VexCommand::Check(args)) => crate::vex::run_vex_check(args),
53        Commands::Vex(VexCommand::Verify(args)) => crate::vex::run_vex_verify(args),
54    }
55}
56
57/// A could-not-scan failure (exit `2`): config/DB/render error — loud, never clean.
58pub(crate) fn fail(message: &str) -> u8 {
59    eprintln!("error: {message}");
60    2
61}
62
63/// A usage error (exit `3`), distinct from could-not-scan (`2`); used for the
64/// fail-closed VEX author/timestamp/consent checks.
65pub(crate) fn usage_fail(message: &str) -> u8 {
66    eprintln!("error: {message}");
67    3
68}
69
70#[derive(Clone, Copy, PartialEq, Eq, ValueEnum)]
71pub(crate) enum ReachMode {
72    /// Grep the repo's own source for affected-function names (zero-build).
73    Heuristic,
74    /// Sound call-graph reachability over the compiled crate closure.
75    Static,
76}
77
78#[derive(Clone, Copy, PartialEq, Eq, ValueEnum)]
79pub(crate) enum BuildSandbox {
80    /// Confine if a mechanism is available, else warn and run unconfined.
81    Auto,
82    /// Never confine — run the build with full user privileges.
83    Off,
84    /// Confine, or refuse to build (→ Unknown) if no mechanism is available.
85    Require,
86}
87
88impl From<BuildSandbox> for fleetreach_reach::SandboxPolicy {
89    fn from(b: BuildSandbox) -> Self {
90        match b {
91            BuildSandbox::Auto => fleetreach_reach::SandboxPolicy::Auto,
92            BuildSandbox::Off => fleetreach_reach::SandboxPolicy::Off,
93            BuildSandbox::Require => fleetreach_reach::SandboxPolicy::Require,
94        }
95    }
96}
97
98#[derive(Clone, Copy, PartialEq, Eq, ValueEnum)]
99pub(crate) enum Format {
100    Table,
101    Json,
102    /// SARIF 2.1.0 for GitHub code scanning.
103    Sarif,
104    /// Blast-radius view: advisories ranked by repos affected.
105    Impact,
106    /// Blast view: blast radius split into direct vs transitive reach, with a fix-path
107    /// hint (manifest / upstream / mixed).
108    Blast,
109    /// Package-impact view: vulnerable dependencies ranked by fleet reach (which one
110    /// bump clears the most), with the direct/transitive split and advisory count.
111    Packages,
112    /// The package-impact rollup as JSON (the `packages` view, machine-readable).
113    PackagesJson,
114    /// Fix-first view: advisories ranked by remediation priority
115    /// (KEV, then severity, then blast radius).
116    FixFirst,
117    /// Remediation view: the actionable fix queue — batched dependency bumps,
118    /// reachability-gated, fix-first-ordered.
119    Remediation,
120    /// The remediation fix queue as JSON (the `remediation` view, machine-readable).
121    RemediationJson,
122    /// OpenVEX 0.2.0 suppression document (§14).
123    Vex,
124}
125
126#[derive(Clone, Copy, PartialEq, Eq, ValueEnum)]
127pub(crate) enum VexScopeArg {
128    Runtime,
129    Build,
130}
131
132impl From<VexScopeArg> for report::VexScope {
133    fn from(s: VexScopeArg) -> Self {
134        match s {
135            VexScopeArg::Runtime => report::VexScope::Runtime,
136            VexScopeArg::Build => report::VexScope::Build,
137        }
138    }
139}
140
141#[derive(Clone, Copy, ValueEnum)]
142pub(crate) enum SeverityArg {
143    Low,
144    Medium,
145    High,
146    Critical,
147}
148
149impl From<SeverityArg> for Severity {
150    fn from(s: SeverityArg) -> Self {
151        match s {
152            SeverityArg::Low => Severity::Low,
153            SeverityArg::Medium => Severity::Medium,
154            SeverityArg::High => Severity::High,
155            SeverityArg::Critical => Severity::Critical,
156        }
157    }
158}