Files
git.stella-ops.org/src/Cli/StellaOps.Cli/Commands/Binary/BinaryCommandGroup.cs
2026-01-16 18:39:36 +02:00

502 lines
16 KiB
C#

// -----------------------------------------------------------------------------
// 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;
/// <summary>
/// CLI command group for binary reachability operations.
/// </summary>
internal static class BinaryCommandGroup
{
internal static Command BuildBinaryCommand(
IServiceProvider services,
Option<bool> 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<bool> verboseOption,
CancellationToken cancellationToken)
{
var fileArg = new Argument<string>("file")
{
Description = "Path to binary file to inspect."
};
var formatOption = new Option<string>("--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<bool> verboseOption,
CancellationToken cancellationToken)
{
var buildIdArg = new Argument<string>("build-id")
{
Description = "GNU Build-ID to look up (hex string)."
};
var distroOption = new Option<string?>("--distro", new[] { "-d" })
{
Description = "Distribution (debian, ubuntu, alpine, rhel)."
};
var releaseOption = new Option<string?>("--release", new[] { "-r" })
{
Description = "Distribution release (bookworm, jammy, v3.19)."
};
var formatOption = new Option<string>("--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<bool> verboseOption,
CancellationToken cancellationToken)
{
var fileArg = new Argument<string>("file")
{
Description = "Path to binary file to fingerprint."
};
var algorithmOption = new Option<string>("--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<string?>("--function")
{
Description = "Specific function to fingerprint."
};
var formatOption = new Option<string>("--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<bool> verboseOption,
CancellationToken cancellationToken)
{
var fileArg = new Argument<string>("file")
{
Description = "Path to binary file to analyze."
};
var formatOption = new Option<string>("--format", ["-f"])
{
Description = "Output format: digest (default), json, summary."
}.SetDefaultValue("digest").FromAmong("digest", "json", "summary");
var outputOption = new Option<string?>("--output", ["-o"])
{
Description = "Output file path (default: stdout)."
};
var emitSbomOption = new Option<string?>("--emit-sbom")
{
Description = "Path to SBOM file to inject callgraph digest as property."
};
var scanIdOption = new Option<string?>("--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<bool> verboseOption,
CancellationToken cancellationToken)
{
var graphOption = new Option<string?>("--graph", new[] { "-g" })
{
Description = "Path to pre-generated rich graph JSON."
};
var binaryOption = new Option<string?>("--binary", new[] { "-b" })
{
Description = "Path to binary for analysis."
};
var analyzeOption = new Option<bool>("--analyze")
{
Description = "Generate graph from binary (requires --binary)."
};
var signOption = new Option<bool>("--sign")
{
Description = "Sign the graph with DSSE attestation."
};
var registryOption = new Option<string?>("--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<bool> verboseOption,
CancellationToken cancellationToken)
{
var hashArg = new Argument<string>("hash")
{
Description = "Graph digest (e.g., blake3:abc123...)."
};
var formatOption = new Option<string>("--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<bool> verboseOption,
CancellationToken cancellationToken)
{
var hashArg = new Argument<string>("hash")
{
Description = "Graph digest (e.g., blake3:abc123...)."
};
var strippedOnlyOption = new Option<bool>("--stripped-only")
{
Description = "Show only stripped (heuristic) symbols."
};
var exportedOnlyOption = new Option<bool>("--exported-only")
{
Description = "Show only exported symbols."
};
var entrypointsOnlyOption = new Option<bool>("--entrypoints-only")
{
Description = "Show only entrypoint symbols."
};
var searchOption = new Option<string?>("--search", new[] { "-s" })
{
Description = "Search pattern (supports wildcards, e.g., ssl_*)."
};
var formatOption = new Option<string>("--format", new[] { "-f" })
{
Description = "Output format: text (default), json."
}.SetDefaultValue("text").FromAmong("text", "json");
var limitOption = new Option<int>("--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<bool> verboseOption,
CancellationToken cancellationToken)
{
var graphOption = new Option<string>("--graph", new[] { "-g" })
{
Description = "Path to graph file.",
Required = true
};
var dsseOption = new Option<string>("--dsse", new[] { "-d" })
{
Description = "Path to DSSE envelope.",
Required = true
};
var publicKeyOption = new Option<string?>("--public-key", new[] { "-k" })
{
Description = "Path to public key for signature verification."
};
var rekorUrlOption = new Option<string?>("--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;
}
}