Add Canonical JSON serialization library with tests and documentation
- Implemented CanonJson class for deterministic JSON serialization and hashing. - Added unit tests for CanonJson functionality, covering various scenarios including key sorting, handling of nested objects, arrays, and special characters. - Created project files for the Canonical JSON library and its tests, including necessary package references. - Added README.md for library usage and API reference. - Introduced RabbitMqIntegrationFactAttribute for conditional RabbitMQ integration tests.
This commit is contained in:
497
src/Cli/StellaOps.Cli/Commands/CommandHandlers.Witness.cs
Normal file
497
src/Cli/StellaOps.Cli/Commands/CommandHandlers.Witness.cs
Normal file
@@ -0,0 +1,497 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// CommandHandlers.Witness.cs
|
||||
// Sprint: SPRINT_3700_0005_0001_witness_ui_cli
|
||||
// Tasks: CLI-001, CLI-002, CLI-003, CLI-004
|
||||
// Description: Command handlers for reachability witness CLI.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Text.Json;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace StellaOps.Cli.Commands;
|
||||
|
||||
internal static partial class CommandHandlers
|
||||
{
|
||||
private static readonly JsonSerializerOptions WitnessJsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handler for `witness show` command.
|
||||
/// </summary>
|
||||
internal static async Task HandleWitnessShowAsync(
|
||||
IServiceProvider services,
|
||||
string witnessId,
|
||||
string format,
|
||||
bool noColor,
|
||||
bool pathOnly,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var console = AnsiConsole.Console;
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
console.MarkupLine($"[dim]Fetching witness: {witnessId}[/]");
|
||||
}
|
||||
|
||||
// TODO: Replace with actual service call when witness API is available
|
||||
var witness = new WitnessDto
|
||||
{
|
||||
WitnessId = witnessId,
|
||||
WitnessSchema = "stellaops.witness.v1",
|
||||
CveId = "CVE-2024-12345",
|
||||
PackageName = "Newtonsoft.Json",
|
||||
PackageVersion = "12.0.3",
|
||||
ConfidenceTier = "confirmed",
|
||||
ObservedAt = DateTimeOffset.UtcNow.AddHours(-2).ToString("O"),
|
||||
Entrypoint = new WitnessEntrypointDto
|
||||
{
|
||||
Type = "http",
|
||||
Route = "GET /api/users/{id}",
|
||||
Symbol = "UserController.GetUser()",
|
||||
File = "src/Controllers/UserController.cs",
|
||||
Line = 42
|
||||
},
|
||||
Sink = new WitnessSinkDto
|
||||
{
|
||||
Symbol = "JsonConvert.DeserializeObject<User>()",
|
||||
Package = "Newtonsoft.Json",
|
||||
IsTrigger = true
|
||||
},
|
||||
Path = new[]
|
||||
{
|
||||
new PathStepDto { Symbol = "UserController.GetUser()", File = "src/Controllers/UserController.cs", Line = 42 },
|
||||
new PathStepDto { Symbol = "UserService.GetUserById()", File = "src/Services/UserService.cs", Line = 88 },
|
||||
new PathStepDto { Symbol = "JsonConvert.DeserializeObject<User>()", Package = "Newtonsoft.Json" }
|
||||
},
|
||||
Gates = new[]
|
||||
{
|
||||
new GateDto { Type = "authRequired", Detail = "[Authorize] attribute", Confidence = 0.95m }
|
||||
},
|
||||
Evidence = new WitnessEvidenceDto
|
||||
{
|
||||
CallgraphDigest = "blake3:a1b2c3d4e5f6...",
|
||||
SurfaceDigest = "sha256:9f8e7d6c5b4a...",
|
||||
SignedBy = "attestor-stellaops-ed25519"
|
||||
}
|
||||
};
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case "json":
|
||||
var json = JsonSerializer.Serialize(witness, WitnessJsonOptions);
|
||||
console.WriteLine(json);
|
||||
break;
|
||||
case "yaml":
|
||||
WriteWitnessYaml(console, witness);
|
||||
break;
|
||||
default:
|
||||
WriteWitnessText(console, witness, pathOnly, noColor);
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for `witness verify` command.
|
||||
/// </summary>
|
||||
internal static async Task HandleWitnessVerifyAsync(
|
||||
IServiceProvider services,
|
||||
string witnessId,
|
||||
string? publicKeyPath,
|
||||
bool offline,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var console = AnsiConsole.Console;
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
console.MarkupLine($"[dim]Verifying witness: {witnessId}[/]");
|
||||
if (publicKeyPath != null)
|
||||
{
|
||||
console.MarkupLine($"[dim]Using public key: {publicKeyPath}[/]");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Replace with actual verification when DSSE verification is wired up
|
||||
await Task.Delay(100, cancellationToken); // Simulate verification
|
||||
|
||||
// Placeholder result
|
||||
var valid = true;
|
||||
var keyId = "attestor-stellaops-ed25519";
|
||||
var algorithm = "Ed25519";
|
||||
|
||||
if (valid)
|
||||
{
|
||||
console.MarkupLine("[green]✓ Signature VALID[/]");
|
||||
console.MarkupLine($" Key ID: {keyId}");
|
||||
console.MarkupLine($" Algorithm: {algorithm}");
|
||||
}
|
||||
else
|
||||
{
|
||||
console.MarkupLine("[red]✗ Signature INVALID[/]");
|
||||
console.MarkupLine(" Error: Signature verification failed");
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for `witness list` command.
|
||||
/// </summary>
|
||||
internal static async Task HandleWitnessListAsync(
|
||||
IServiceProvider services,
|
||||
string scanId,
|
||||
string? cve,
|
||||
string? tier,
|
||||
string format,
|
||||
int limit,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var console = AnsiConsole.Console;
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
console.MarkupLine($"[dim]Listing witnesses for scan: {scanId}[/]");
|
||||
if (cve != null) console.MarkupLine($"[dim]Filtering by CVE: {cve}[/]");
|
||||
if (tier != null) console.MarkupLine($"[dim]Filtering by tier: {tier}[/]");
|
||||
}
|
||||
|
||||
// TODO: Replace with actual service call
|
||||
var witnesses = new[]
|
||||
{
|
||||
new WitnessListItemDto
|
||||
{
|
||||
WitnessId = "wit:sha256:abc123",
|
||||
CveId = "CVE-2024-12345",
|
||||
PackageName = "Newtonsoft.Json",
|
||||
ConfidenceTier = "confirmed",
|
||||
Entrypoint = "GET /api/users/{id}",
|
||||
Sink = "JsonConvert.DeserializeObject()"
|
||||
},
|
||||
new WitnessListItemDto
|
||||
{
|
||||
WitnessId = "wit:sha256:def456",
|
||||
CveId = "CVE-2024-12346",
|
||||
PackageName = "lodash",
|
||||
ConfidenceTier = "likely",
|
||||
Entrypoint = "POST /api/data",
|
||||
Sink = "_.template()"
|
||||
}
|
||||
};
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case "json":
|
||||
var json = JsonSerializer.Serialize(new { witnesses, total = witnesses.Length }, WitnessJsonOptions);
|
||||
console.WriteLine(json);
|
||||
break;
|
||||
default:
|
||||
WriteWitnessListTable(console, witnesses);
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for `witness export` command.
|
||||
/// </summary>
|
||||
internal static async Task HandleWitnessExportAsync(
|
||||
IServiceProvider services,
|
||||
string witnessId,
|
||||
string format,
|
||||
string? outputPath,
|
||||
bool includeDsse,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var console = AnsiConsole.Console;
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
console.MarkupLine($"[dim]Exporting witness: {witnessId} as {format}[/]");
|
||||
if (outputPath != null) console.MarkupLine($"[dim]Output: {outputPath}[/]");
|
||||
}
|
||||
|
||||
// TODO: Replace with actual witness fetch and export
|
||||
var exportContent = format switch
|
||||
{
|
||||
"sarif" => GenerateWitnessSarif(witnessId),
|
||||
_ => GenerateWitnessJson(witnessId, includeDsse)
|
||||
};
|
||||
|
||||
if (outputPath != null)
|
||||
{
|
||||
await File.WriteAllTextAsync(outputPath, exportContent, cancellationToken);
|
||||
console.MarkupLine($"[green]Exported to {outputPath}[/]");
|
||||
}
|
||||
else
|
||||
{
|
||||
console.WriteLine(exportContent);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteWitnessText(IAnsiConsole console, WitnessDto witness, bool pathOnly, bool noColor)
|
||||
{
|
||||
if (!pathOnly)
|
||||
{
|
||||
console.WriteLine();
|
||||
console.MarkupLine($"[bold]WITNESS:[/] {witness.WitnessId}");
|
||||
console.WriteLine(new string('═', 70));
|
||||
console.WriteLine();
|
||||
|
||||
var tierColor = witness.ConfidenceTier switch
|
||||
{
|
||||
"confirmed" => "red",
|
||||
"likely" => "yellow",
|
||||
"present" => "grey",
|
||||
"unreachable" => "green",
|
||||
_ => "white"
|
||||
};
|
||||
|
||||
console.MarkupLine($"Vulnerability: [bold]{witness.CveId}[/] ({witness.PackageName} <={witness.PackageVersion})");
|
||||
console.MarkupLine($"Confidence: [{tierColor}]{witness.ConfidenceTier.ToUpperInvariant()}[/]");
|
||||
console.MarkupLine($"Observed: {witness.ObservedAt}");
|
||||
console.WriteLine();
|
||||
}
|
||||
|
||||
console.MarkupLine("[bold]CALL PATH[/]");
|
||||
console.WriteLine(new string('─', 70));
|
||||
|
||||
// Entrypoint
|
||||
console.MarkupLine($"[green][ENTRYPOINT][/] {witness.Entrypoint.Route}");
|
||||
console.MarkupLine(" │");
|
||||
|
||||
// Path steps
|
||||
for (var i = 0; i < witness.Path.Length; i++)
|
||||
{
|
||||
var step = witness.Path[i];
|
||||
var isLast = i == witness.Path.Length - 1;
|
||||
var prefix = isLast ? "└──" : "├──";
|
||||
|
||||
if (isLast)
|
||||
{
|
||||
console.MarkupLine($" {prefix} [red][SINK][/] {step.Symbol}");
|
||||
if (step.Package != null)
|
||||
{
|
||||
console.MarkupLine($" └── {step.Package} (TRIGGER METHOD)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.MarkupLine($" {prefix} {step.Symbol}");
|
||||
if (step.File != null)
|
||||
{
|
||||
console.MarkupLine($" │ └── {step.File}:{step.Line}");
|
||||
}
|
||||
|
||||
// Check for gates after this step
|
||||
if (i < witness.Gates.Length)
|
||||
{
|
||||
var gate = witness.Gates[i];
|
||||
console.MarkupLine(" │");
|
||||
console.MarkupLine($" │ [yellow][GATE: {gate.Type}][/] {gate.Detail} ({gate.Confidence:P0})");
|
||||
}
|
||||
}
|
||||
|
||||
if (!isLast)
|
||||
{
|
||||
console.MarkupLine(" │");
|
||||
}
|
||||
}
|
||||
|
||||
if (!pathOnly)
|
||||
{
|
||||
console.WriteLine();
|
||||
console.MarkupLine("[bold]EVIDENCE[/]");
|
||||
console.WriteLine(new string('─', 70));
|
||||
console.MarkupLine($"Call Graph: {witness.Evidence.CallgraphDigest}");
|
||||
console.MarkupLine($"Surface: {witness.Evidence.SurfaceDigest}");
|
||||
console.MarkupLine($"Signed By: {witness.Evidence.SignedBy}");
|
||||
console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteWitnessYaml(IAnsiConsole console, WitnessDto witness)
|
||||
{
|
||||
console.WriteLine($"witnessId: {witness.WitnessId}");
|
||||
console.WriteLine($"witnessSchema: {witness.WitnessSchema}");
|
||||
console.WriteLine($"cveId: {witness.CveId}");
|
||||
console.WriteLine($"packageName: {witness.PackageName}");
|
||||
console.WriteLine($"packageVersion: {witness.PackageVersion}");
|
||||
console.WriteLine($"confidenceTier: {witness.ConfidenceTier}");
|
||||
console.WriteLine($"observedAt: {witness.ObservedAt}");
|
||||
console.WriteLine("entrypoint:");
|
||||
console.WriteLine($" type: {witness.Entrypoint.Type}");
|
||||
console.WriteLine($" route: {witness.Entrypoint.Route}");
|
||||
console.WriteLine($" symbol: {witness.Entrypoint.Symbol}");
|
||||
console.WriteLine("path:");
|
||||
foreach (var step in witness.Path)
|
||||
{
|
||||
console.WriteLine($" - symbol: {step.Symbol}");
|
||||
if (step.File != null) console.WriteLine($" file: {step.File}");
|
||||
if (step.Line > 0) console.WriteLine($" line: {step.Line}");
|
||||
}
|
||||
console.WriteLine("evidence:");
|
||||
console.WriteLine($" callgraphDigest: {witness.Evidence.CallgraphDigest}");
|
||||
console.WriteLine($" surfaceDigest: {witness.Evidence.SurfaceDigest}");
|
||||
console.WriteLine($" signedBy: {witness.Evidence.SignedBy}");
|
||||
}
|
||||
|
||||
private static void WriteWitnessListTable(IAnsiConsole console, WitnessListItemDto[] witnesses)
|
||||
{
|
||||
var table = new Table();
|
||||
table.AddColumn("Witness ID");
|
||||
table.AddColumn("CVE");
|
||||
table.AddColumn("Package");
|
||||
table.AddColumn("Tier");
|
||||
table.AddColumn("Entrypoint");
|
||||
table.AddColumn("Sink");
|
||||
|
||||
foreach (var w in witnesses)
|
||||
{
|
||||
var tierColor = w.ConfidenceTier switch
|
||||
{
|
||||
"confirmed" => "red",
|
||||
"likely" => "yellow",
|
||||
"present" => "grey",
|
||||
"unreachable" => "green",
|
||||
_ => "white"
|
||||
};
|
||||
|
||||
table.AddRow(
|
||||
w.WitnessId[..20] + "...",
|
||||
w.CveId,
|
||||
w.PackageName,
|
||||
$"[{tierColor}]{w.ConfidenceTier}[/]",
|
||||
w.Entrypoint.Length > 25 ? w.Entrypoint[..25] + "..." : w.Entrypoint,
|
||||
w.Sink.Length > 25 ? w.Sink[..25] + "..." : w.Sink
|
||||
);
|
||||
}
|
||||
|
||||
console.Write(table);
|
||||
}
|
||||
|
||||
private static string GenerateWitnessJson(string witnessId, bool includeDsse)
|
||||
{
|
||||
var witness = new
|
||||
{
|
||||
witness_schema = "stellaops.witness.v1",
|
||||
witness_id = witnessId,
|
||||
artifact = new { sbom_digest = "sha256:...", component_purl = "pkg:nuget/Newtonsoft.Json@12.0.3" },
|
||||
vuln = new { id = "CVE-2024-12345", source = "NVD" },
|
||||
entrypoint = new { type = "http", route = "GET /api/users/{id}" },
|
||||
path = new[] { new { symbol = "UserController.GetUser" }, new { symbol = "JsonConvert.DeserializeObject" } },
|
||||
evidence = new { callgraph_digest = "blake3:...", surface_digest = "sha256:..." }
|
||||
};
|
||||
|
||||
return JsonSerializer.Serialize(witness, WitnessJsonOptions);
|
||||
}
|
||||
|
||||
private static string GenerateWitnessSarif(string witnessId)
|
||||
{
|
||||
var sarif = new
|
||||
{
|
||||
version = "2.1.0",
|
||||
schema = "https://json.schemastore.org/sarif-2.1.0.json",
|
||||
runs = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
tool = new
|
||||
{
|
||||
driver = new
|
||||
{
|
||||
name = "StellaOps Reachability",
|
||||
version = "1.0.0",
|
||||
informationUri = "https://stellaops.dev"
|
||||
}
|
||||
},
|
||||
results = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
ruleId = "REACH001",
|
||||
level = "warning",
|
||||
message = new { text = "Reachable vulnerability: CVE-2024-12345" },
|
||||
properties = new { witnessId }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return JsonSerializer.Serialize(sarif, WitnessJsonOptions);
|
||||
}
|
||||
|
||||
// DTO classes for witness commands
|
||||
private sealed record WitnessDto
|
||||
{
|
||||
public required string WitnessId { get; init; }
|
||||
public required string WitnessSchema { get; init; }
|
||||
public required string CveId { get; init; }
|
||||
public required string PackageName { get; init; }
|
||||
public required string PackageVersion { get; init; }
|
||||
public required string ConfidenceTier { get; init; }
|
||||
public required string ObservedAt { get; init; }
|
||||
public required WitnessEntrypointDto Entrypoint { get; init; }
|
||||
public required WitnessSinkDto Sink { get; init; }
|
||||
public required PathStepDto[] Path { get; init; }
|
||||
public required GateDto[] Gates { get; init; }
|
||||
public required WitnessEvidenceDto Evidence { get; init; }
|
||||
}
|
||||
|
||||
private sealed record WitnessEntrypointDto
|
||||
{
|
||||
public required string Type { get; init; }
|
||||
public required string Route { get; init; }
|
||||
public required string Symbol { get; init; }
|
||||
public string? File { get; init; }
|
||||
public int Line { get; init; }
|
||||
}
|
||||
|
||||
private sealed record WitnessSinkDto
|
||||
{
|
||||
public required string Symbol { get; init; }
|
||||
public string? Package { get; init; }
|
||||
public bool IsTrigger { get; init; }
|
||||
}
|
||||
|
||||
private sealed record PathStepDto
|
||||
{
|
||||
public required string Symbol { get; init; }
|
||||
public string? File { get; init; }
|
||||
public int Line { get; init; }
|
||||
public string? Package { get; init; }
|
||||
}
|
||||
|
||||
private sealed record GateDto
|
||||
{
|
||||
public required string Type { get; init; }
|
||||
public required string Detail { get; init; }
|
||||
public decimal Confidence { get; init; }
|
||||
}
|
||||
|
||||
private sealed record WitnessEvidenceDto
|
||||
{
|
||||
public required string CallgraphDigest { get; init; }
|
||||
public required string SurfaceDigest { get; init; }
|
||||
public required string SignedBy { get; init; }
|
||||
}
|
||||
|
||||
private sealed record WitnessListItemDto
|
||||
{
|
||||
public required string WitnessId { get; init; }
|
||||
public required string CveId { get; init; }
|
||||
public required string PackageName { get; init; }
|
||||
public required string ConfidenceTier { get; init; }
|
||||
public required string Entrypoint { get; init; }
|
||||
public required string Sink { get; init; }
|
||||
}
|
||||
}
|
||||
255
src/Cli/StellaOps.Cli/Commands/WitnessCommandGroup.cs
Normal file
255
src/Cli/StellaOps.Cli/Commands/WitnessCommandGroup.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// WitnessCommandGroup.cs
|
||||
// Sprint: SPRINT_3700_0005_0001_witness_ui_cli
|
||||
// Tasks: CLI-001, CLI-002, CLI-003, CLI-004
|
||||
// Description: CLI command group for reachability witness operations.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.CommandLine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Cli.Extensions;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace StellaOps.Cli.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// CLI command group for reachability witness operations.
|
||||
/// </summary>
|
||||
internal static class WitnessCommandGroup
|
||||
{
|
||||
internal static Command BuildWitnessCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var witness = new Command("witness", "Reachability witness operations.");
|
||||
|
||||
witness.Add(BuildWitnessShowCommand(services, verboseOption, cancellationToken));
|
||||
witness.Add(BuildWitnessVerifyCommand(services, verboseOption, cancellationToken));
|
||||
witness.Add(BuildWitnessListCommand(services, verboseOption, cancellationToken));
|
||||
witness.Add(BuildWitnessExportCommand(services, verboseOption, cancellationToken));
|
||||
|
||||
return witness;
|
||||
}
|
||||
|
||||
private static Command BuildWitnessShowCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var witnessIdArg = new Argument<string>("witness-id")
|
||||
{
|
||||
Description = "The witness ID to display (e.g., wit:sha256:abc123)."
|
||||
};
|
||||
|
||||
var formatOption = new Option<string>("--format", new[] { "-f" })
|
||||
{
|
||||
Description = "Output format: text (default), json, yaml."
|
||||
}.SetDefaultValue("text").FromAmong("text", "json", "yaml");
|
||||
|
||||
var noColorOption = new Option<bool>("--no-color")
|
||||
{
|
||||
Description = "Disable colored output."
|
||||
};
|
||||
|
||||
var pathOnlyOption = new Option<bool>("--path-only")
|
||||
{
|
||||
Description = "Show only the call path, not full witness details."
|
||||
};
|
||||
|
||||
var command = new Command("show", "Display a witness with call path visualization.")
|
||||
{
|
||||
witnessIdArg,
|
||||
formatOption,
|
||||
noColorOption,
|
||||
pathOnlyOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var witnessId = parseResult.GetValue(witnessIdArg)!;
|
||||
var format = parseResult.GetValue(formatOption)!;
|
||||
var noColor = parseResult.GetValue(noColorOption);
|
||||
var pathOnly = parseResult.GetValue(pathOnlyOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleWitnessShowAsync(
|
||||
services,
|
||||
witnessId,
|
||||
format,
|
||||
noColor,
|
||||
pathOnly,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildWitnessVerifyCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var witnessIdArg = new Argument<string>("witness-id")
|
||||
{
|
||||
Description = "The witness ID to verify."
|
||||
};
|
||||
|
||||
var publicKeyOption = new Option<string?>("--public-key", new[] { "-k" })
|
||||
{
|
||||
Description = "Path to public key file (default: fetch from authority)."
|
||||
};
|
||||
|
||||
var offlineOption = new Option<bool>("--offline")
|
||||
{
|
||||
Description = "Verify using local key only, don't fetch from server."
|
||||
};
|
||||
|
||||
var command = new Command("verify", "Verify a witness signature.")
|
||||
{
|
||||
witnessIdArg,
|
||||
publicKeyOption,
|
||||
offlineOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var witnessId = parseResult.GetValue(witnessIdArg)!;
|
||||
var publicKeyPath = parseResult.GetValue(publicKeyOption);
|
||||
var offline = parseResult.GetValue(offlineOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleWitnessVerifyAsync(
|
||||
services,
|
||||
witnessId,
|
||||
publicKeyPath,
|
||||
offline,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildWitnessListCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var scanOption = new Option<string>("--scan", new[] { "-s" })
|
||||
{
|
||||
Description = "Scan ID to list witnesses for.",
|
||||
Required = true
|
||||
};
|
||||
|
||||
var cveOption = new Option<string?>("--cve")
|
||||
{
|
||||
Description = "Filter witnesses by CVE ID."
|
||||
};
|
||||
|
||||
var tierOption = new Option<string?>("--tier")
|
||||
{
|
||||
Description = "Filter by confidence tier: confirmed, likely, present, unreachable."
|
||||
}?.FromAmong("confirmed", "likely", "present", "unreachable");
|
||||
|
||||
var formatOption = new Option<string>("--format", new[] { "-f" })
|
||||
{
|
||||
Description = "Output format: table (default), json."
|
||||
}.SetDefaultValue("table").FromAmong("table", "json");
|
||||
|
||||
var limitOption = new Option<int>("--limit", new[] { "-l" })
|
||||
{
|
||||
Description = "Maximum number of witnesses to return."
|
||||
}.SetDefaultValue(50);
|
||||
|
||||
var command = new Command("list", "List witnesses for a scan.")
|
||||
{
|
||||
scanOption,
|
||||
cveOption,
|
||||
tierOption,
|
||||
formatOption,
|
||||
limitOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var scanId = parseResult.GetValue(scanOption)!;
|
||||
var cve = parseResult.GetValue(cveOption);
|
||||
var tier = parseResult.GetValue(tierOption);
|
||||
var format = parseResult.GetValue(formatOption)!;
|
||||
var limit = parseResult.GetValue(limitOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleWitnessListAsync(
|
||||
services,
|
||||
scanId,
|
||||
cve,
|
||||
tier,
|
||||
format,
|
||||
limit,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildWitnessExportCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var witnessIdArg = new Argument<string>("witness-id")
|
||||
{
|
||||
Description = "The witness ID to export."
|
||||
};
|
||||
|
||||
var formatOption = new Option<string>("--format", new[] { "-f" })
|
||||
{
|
||||
Description = "Export format: json (default), sarif."
|
||||
}.SetDefaultValue("json").FromAmong("json", "sarif");
|
||||
|
||||
var outputOption = new Option<string?>("--output", new[] { "-o" })
|
||||
{
|
||||
Description = "Output file path (default: stdout)."
|
||||
};
|
||||
|
||||
var includeDsseOption = new Option<bool>("--include-dsse")
|
||||
{
|
||||
Description = "Include DSSE envelope in export."
|
||||
};
|
||||
|
||||
var command = new Command("export", "Export a witness to file.")
|
||||
{
|
||||
witnessIdArg,
|
||||
formatOption,
|
||||
outputOption,
|
||||
includeDsseOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var witnessId = parseResult.GetValue(witnessIdArg)!;
|
||||
var format = parseResult.GetValue(formatOption)!;
|
||||
var outputPath = parseResult.GetValue(outputOption);
|
||||
var includeDsse = parseResult.GetValue(includeDsseOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return CommandHandlers.HandleWitnessExportAsync(
|
||||
services,
|
||||
witnessId,
|
||||
format,
|
||||
outputPath,
|
||||
includeDsse,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user