// ----------------------------------------------------------------------------- // BinaryCommandGroup.cs // Sprint: SPRINT_3850_0001_0001_oci_storage_cli // Tasks: T3, T4, T5, T6 // Description: CLI command group for binary reachability operations. // ----------------------------------------------------------------------------- using System.CommandLine; using Microsoft.Extensions.DependencyInjection; using StellaOps.Cli.Extensions; namespace StellaOps.Cli.Commands.Binary; /// /// CLI command group for binary reachability operations. /// internal static class BinaryCommandGroup { internal static Command BuildBinaryCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var binary = new Command("binary", "Binary reachability analysis operations."); binary.Add(BuildSubmitCommand(services, verboseOption, cancellationToken)); binary.Add(BuildInfoCommand(services, verboseOption, cancellationToken)); binary.Add(BuildSymbolsCommand(services, verboseOption, cancellationToken)); binary.Add(BuildVerifyCommand(services, verboseOption, cancellationToken)); // Sprint: SPRINT_20251226_014_BINIDX - New binary analysis commands binary.Add(BuildInspectCommand(services, verboseOption, cancellationToken)); binary.Add(BuildLookupCommand(services, verboseOption, cancellationToken)); binary.Add(BuildFingerprintCommand(services, verboseOption, cancellationToken)); // Sprint: SPRINT_20260104_001_CLI - Binary call graph digest extraction binary.Add(BuildCallGraphCommand(services, verboseOption, cancellationToken)); // Sprint: SPRINT_20260112_006_CLI - BinaryIndex ops commands binary.Add(BinaryIndexOpsCommandGroup.BuildOpsCommand(services, verboseOption, cancellationToken)); // Sprint: SPRINT_20260117_003_BINDEX - Delta-sig predicate operations binary.Add(DeltaSigCommandGroup.BuildDeltaSigCommand(services, verboseOption, cancellationToken)); return binary; } // SCANINT-14: stella binary inspect private static Command BuildInspectCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var fileArg = new Argument("file") { Description = "Path to binary file to inspect." }; var formatOption = new Option("--format", new[] { "-f" }) { Description = "Output format: text (default), json." }.SetDefaultValue("text").FromAmong("text", "json"); var command = new Command("inspect", "Inspect binary identity (Build-ID, hashes, architecture).") { fileArg, formatOption, verboseOption }; command.SetAction(parseResult => { var file = parseResult.GetValue(fileArg)!; var format = parseResult.GetValue(formatOption)!; var verbose = parseResult.GetValue(verboseOption); return BinaryCommandHandlers.HandleInspectAsync( services, file, format, verbose, cancellationToken); }); return command; } // SCANINT-15: stella binary lookup private static Command BuildLookupCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var buildIdArg = new Argument("build-id") { Description = "GNU Build-ID to look up (hex string)." }; var distroOption = new Option("--distro", new[] { "-d" }) { Description = "Distribution (debian, ubuntu, alpine, rhel)." }; var releaseOption = new Option("--release", new[] { "-r" }) { Description = "Distribution release (bookworm, jammy, v3.19)." }; var formatOption = new Option("--format", new[] { "-f" }) { Description = "Output format: text (default), json." }.SetDefaultValue("text").FromAmong("text", "json"); var command = new Command("lookup", "Look up vulnerabilities by Build-ID.") { buildIdArg, distroOption, releaseOption, formatOption, verboseOption }; command.SetAction(parseResult => { var buildId = parseResult.GetValue(buildIdArg)!; var distro = parseResult.GetValue(distroOption); var release = parseResult.GetValue(releaseOption); var format = parseResult.GetValue(formatOption)!; var verbose = parseResult.GetValue(verboseOption); return BinaryCommandHandlers.HandleLookupAsync( services, buildId, distro, release, format, verbose, cancellationToken); }); return command; } // SCANINT-16: stella binary fingerprint private static Command BuildFingerprintCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var fileArg = new Argument("file") { Description = "Path to binary file to fingerprint." }; var algorithmOption = new Option("--algorithm", new[] { "-a" }) { Description = "Fingerprint algorithm: combined (default), basic-block, cfg, string-refs." }.SetDefaultValue("combined").FromAmong("combined", "basic-block", "cfg", "string-refs"); var functionOption = new Option("--function") { Description = "Specific function to fingerprint." }; var formatOption = new Option("--format", new[] { "-f" }) { Description = "Output format: text (default), json, hex." }.SetDefaultValue("text").FromAmong("text", "json", "hex"); var command = new Command("fingerprint", "Generate fingerprint for a binary or function.") { fileArg, algorithmOption, functionOption, formatOption, verboseOption }; command.SetAction(parseResult => { var file = parseResult.GetValue(fileArg)!; var algorithm = parseResult.GetValue(algorithmOption)!; var function = parseResult.GetValue(functionOption); var format = parseResult.GetValue(formatOption)!; var verbose = parseResult.GetValue(verboseOption); return BinaryCommandHandlers.HandleFingerprintAsync( services, file, algorithm, function, format, verbose, cancellationToken); }); return command; } // CALLGRAPH-01: stella binary callgraph private static Command BuildCallGraphCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var fileArg = new Argument("file") { Description = "Path to binary file to analyze." }; var formatOption = new Option("--format", ["-f"]) { Description = "Output format: digest (default), json, summary." }.SetDefaultValue("digest").FromAmong("digest", "json", "summary"); var outputOption = new Option("--output", ["-o"]) { Description = "Output file path (default: stdout)." }; var emitSbomOption = new Option("--emit-sbom") { Description = "Path to SBOM file to inject callgraph digest as property." }; var scanIdOption = new Option("--scan-id") { Description = "Scan ID for graph metadata (default: auto-generated)." }; var command = new Command("callgraph", "Extract call graph and compute deterministic digest.") { fileArg, formatOption, outputOption, emitSbomOption, scanIdOption, verboseOption }; command.SetAction(parseResult => { var file = parseResult.GetValue(fileArg)!; var format = parseResult.GetValue(formatOption)!; var output = parseResult.GetValue(outputOption); var emitSbom = parseResult.GetValue(emitSbomOption); var scanId = parseResult.GetValue(scanIdOption); var verbose = parseResult.GetValue(verboseOption); return BinaryCommandHandlers.HandleCallGraphAsync( services, file, format, output, emitSbom, scanId, verbose, cancellationToken); }); return command; } private static Command BuildSubmitCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var graphOption = new Option("--graph", new[] { "-g" }) { Description = "Path to pre-generated rich graph JSON." }; var binaryOption = new Option("--binary", new[] { "-b" }) { Description = "Path to binary for analysis." }; var analyzeOption = new Option("--analyze") { Description = "Generate graph from binary (requires --binary)." }; var signOption = new Option("--sign") { Description = "Sign the graph with DSSE attestation." }; var registryOption = new Option("--registry", new[] { "-r" }) { Description = "OCI registry to push graph (e.g., ghcr.io/myorg/graphs)." }; var command = new Command("submit", "Submit binary graph for reachability analysis.") { graphOption, binaryOption, analyzeOption, signOption, registryOption, verboseOption }; command.SetAction(parseResult => { var graphPath = parseResult.GetValue(graphOption); var binaryPath = parseResult.GetValue(binaryOption); var analyze = parseResult.GetValue(analyzeOption); var sign = parseResult.GetValue(signOption); var registry = parseResult.GetValue(registryOption); var verbose = parseResult.GetValue(verboseOption); return BinaryCommandHandlers.HandleSubmitAsync( services, graphPath, binaryPath, analyze, sign, registry, verbose, cancellationToken); }); return command; } private static Command BuildInfoCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var hashArg = new Argument("hash") { Description = "Graph digest (e.g., blake3:abc123...)." }; var formatOption = new Option("--format", new[] { "-f" }) { Description = "Output format: text (default), json." }.SetDefaultValue("text").FromAmong("text", "json"); var command = new Command("info", "Display binary graph information.") { hashArg, formatOption, verboseOption }; command.SetAction(parseResult => { var hash = parseResult.GetValue(hashArg)!; var format = parseResult.GetValue(formatOption)!; var verbose = parseResult.GetValue(verboseOption); return BinaryCommandHandlers.HandleInfoAsync( services, hash, format, verbose, cancellationToken); }); return command; } private static Command BuildSymbolsCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var hashArg = new Argument("hash") { Description = "Graph digest (e.g., blake3:abc123...)." }; var strippedOnlyOption = new Option("--stripped-only") { Description = "Show only stripped (heuristic) symbols." }; var exportedOnlyOption = new Option("--exported-only") { Description = "Show only exported symbols." }; var entrypointsOnlyOption = new Option("--entrypoints-only") { Description = "Show only entrypoint symbols." }; var searchOption = new Option("--search", new[] { "-s" }) { Description = "Search pattern (supports wildcards, e.g., ssl_*)." }; var formatOption = new Option("--format", new[] { "-f" }) { Description = "Output format: text (default), json." }.SetDefaultValue("text").FromAmong("text", "json"); var limitOption = new Option("--limit", new[] { "-n" }) { Description = "Limit number of results." }.SetDefaultValue(100); var command = new Command("symbols", "List symbols from binary graph.") { hashArg, strippedOnlyOption, exportedOnlyOption, entrypointsOnlyOption, searchOption, formatOption, limitOption, verboseOption }; command.SetAction(parseResult => { var hash = parseResult.GetValue(hashArg)!; var strippedOnly = parseResult.GetValue(strippedOnlyOption); var exportedOnly = parseResult.GetValue(exportedOnlyOption); var entrypointsOnly = parseResult.GetValue(entrypointsOnlyOption); var search = parseResult.GetValue(searchOption); var format = parseResult.GetValue(formatOption)!; var limit = parseResult.GetValue(limitOption); var verbose = parseResult.GetValue(verboseOption); return BinaryCommandHandlers.HandleSymbolsAsync( services, hash, strippedOnly, exportedOnly, entrypointsOnly, search, format, limit, verbose, cancellationToken); }); return command; } private static Command BuildVerifyCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var graphOption = new Option("--graph", new[] { "-g" }) { Description = "Path to graph file.", Required = true }; var dsseOption = new Option("--dsse", new[] { "-d" }) { Description = "Path to DSSE envelope.", Required = true }; var publicKeyOption = new Option("--public-key", new[] { "-k" }) { Description = "Path to public key for signature verification." }; var rekorUrlOption = new Option("--rekor-url") { Description = "Rekor transparency log URL." }; var command = new Command("verify", "Verify binary graph attestation.") { graphOption, dsseOption, publicKeyOption, rekorUrlOption, verboseOption }; command.SetAction(parseResult => { var graphPath = parseResult.GetValue(graphOption)!; var dssePath = parseResult.GetValue(dsseOption)!; var publicKey = parseResult.GetValue(publicKeyOption); var rekorUrl = parseResult.GetValue(rekorUrlOption); var verbose = parseResult.GetValue(verboseOption); return BinaryCommandHandlers.HandleVerifyAsync( services, graphPath, dssePath, publicKey, rekorUrl, verbose, cancellationToken); }); return command; } }