502 lines
16 KiB
C#
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;
|
|
}
|
|
}
|