up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 18:08:55 +02:00
parent 6e45066e37
commit f1a39c4ce3
234 changed files with 24038 additions and 6910 deletions

View File

@@ -268,16 +268,22 @@ internal static class CommandFactory
{
Description = "Include raw NDJSON output."
};
var includeSemanticOption = new Option<bool>("--semantic")
{
Description = "Include semantic entrypoint analysis (intent, capabilities, threats)."
};
entryTrace.Add(scanIdOption);
entryTrace.Add(includeNdjsonOption);
entryTrace.Add(includeSemanticOption);
entryTrace.SetAction((parseResult, _) =>
{
var id = parseResult.GetValue(scanIdOption) ?? string.Empty;
var includeNdjson = parseResult.GetValue(includeNdjsonOption);
var includeSemantic = parseResult.GetValue(includeSemanticOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleScanEntryTraceAsync(services, id, includeNdjson, verbose, cancellationToken);
return CommandHandlers.HandleScanEntryTraceAsync(services, id, includeNdjson, includeSemantic, verbose, cancellationToken);
});
scan.Add(entryTrace);
@@ -8845,7 +8851,7 @@ internal static class CommandFactory
var runOutputOption = new Option<string>("--output", new[] { "-o" })
{
Description = "Path to write the export bundle.",
IsRequired = true
Required = true
};
var runOverwriteOption = new Option<bool>("--overwrite")
{
@@ -8895,7 +8901,7 @@ internal static class CommandFactory
var startProfileOption = new Option<string>("--profile-id")
{
Description = "Export profile identifier.",
IsRequired = true
Required = true
};
var startSelectorOption = new Option<string[]?>("--selector", new[] { "-s" })
{

View File

@@ -509,7 +509,7 @@ internal static class CommandHandlers
}
}
private static void RenderEntryTrace(EntryTraceResponseModel result, bool includeNdjson)
private static void RenderEntryTrace(EntryTraceResponseModel result, bool includeNdjson, bool includeSemantic)
{
var console = AnsiConsole.Console;
@@ -570,6 +570,69 @@ internal static class CommandHandlers
console.Write(diagTable);
}
// Semantic entrypoint analysis
if (includeSemantic && result.Semantic is not null)
{
console.WriteLine();
console.MarkupLine("[bold]Semantic Entrypoint Analysis[/]");
console.MarkupLine($"Intent: [green]{Markup.Escape(result.Semantic.Intent)}[/]");
console.MarkupLine($"Language: {Markup.Escape(result.Semantic.Language ?? "unknown")}");
console.MarkupLine($"Framework: {Markup.Escape(result.Semantic.Framework ?? "none")}");
console.MarkupLine($"Confidence: {result.Semantic.ConfidenceScore:P0} ({Markup.Escape(result.Semantic.ConfidenceTier)})");
if (result.Semantic.Capabilities.Count > 0)
{
console.MarkupLine($"Capabilities: [cyan]{Markup.Escape(string.Join(", ", result.Semantic.Capabilities))}[/]");
}
if (result.Semantic.Threats.Count > 0)
{
console.WriteLine();
console.MarkupLine("[bold]Threat Vectors[/]");
var threatTable = new Table()
.AddColumn("Threat")
.AddColumn("CWE")
.AddColumn("OWASP")
.AddColumn("Confidence");
foreach (var threat in result.Semantic.Threats)
{
threatTable.AddRow(
threat.Type,
threat.CweId ?? "-",
threat.OwaspCategory ?? "-",
threat.Confidence.ToString("P0", CultureInfo.InvariantCulture));
}
console.Write(threatTable);
}
if (result.Semantic.DataBoundaries.Count > 0)
{
console.WriteLine();
console.MarkupLine("[bold]Data Flow Boundaries[/]");
var boundaryTable = new Table()
.AddColumn("Type")
.AddColumn("Direction")
.AddColumn("Sensitivity");
foreach (var boundary in result.Semantic.DataBoundaries)
{
boundaryTable.AddRow(
boundary.Type,
boundary.Direction,
boundary.Sensitivity);
}
console.Write(boundaryTable);
}
}
else if (includeSemantic && result.Semantic is null)
{
console.WriteLine();
console.MarkupLine("[italic yellow]Semantic analysis not available for this scan.[/]");
}
if (includeNdjson && result.Ndjson.Count > 0)
{
console.MarkupLine("[bold]NDJSON Output[/]");
@@ -685,6 +748,7 @@ internal static class CommandHandlers
IServiceProvider services,
string scanId,
bool includeNdjson,
bool includeSemantic,
bool verbose,
CancellationToken cancellationToken)
{
@@ -697,6 +761,7 @@ internal static class CommandHandlers
using var activity = CliActivitySource.Instance.StartActivity("cli.scan.entrytrace", ActivityKind.Client);
activity?.SetTag("stellaops.cli.command", "scan entrytrace");
activity?.SetTag("stellaops.cli.scan_id", scanId);
activity?.SetTag("stellaops.cli.include_semantic", includeSemantic);
using var duration = CliMetrics.MeasureCommandDuration("scan entrytrace");
try
@@ -713,7 +778,7 @@ internal static class CommandHandlers
return;
}
RenderEntryTrace(result, includeNdjson);
RenderEntryTrace(result, includeNdjson, includeSemantic);
Environment.ExitCode = 0;
}
catch (Exception ex)
@@ -6362,6 +6427,8 @@ internal static class CommandHandlers
table.AddColumn("Status");
table.AddColumn("Severity");
table.AddColumn("Score");
table.AddColumn("Tier");
table.AddColumn("Risk");
table.AddColumn("SBOM");
table.AddColumn("Advisories");
table.AddColumn("Updated (UTC)");
@@ -6373,6 +6440,8 @@ internal static class CommandHandlers
Markup.Escape(item.Status),
Markup.Escape(item.Severity.Normalized),
Markup.Escape(FormatScore(item.Severity.Score)),
FormatUncertaintyTier(item.Uncertainty?.AggregateTier),
Markup.Escape(FormatScore(item.Uncertainty?.RiskScore)),
Markup.Escape(item.SbomId),
Markup.Escape(FormatListPreview(item.AdvisoryIds)),
Markup.Escape(FormatUpdatedAt(item.UpdatedAt)));
@@ -6385,11 +6454,13 @@ internal static class CommandHandlers
foreach (var item in items)
{
logger.LogInformation(
"{Finding} — Status {Status}, Severity {Severity} ({Score}), SBOM {Sbom}, Updated {Updated}",
"{Finding} — Status {Status}, Severity {Severity} ({Score}), Tier {Tier} (Risk {Risk}), SBOM {Sbom}, Updated {Updated}",
item.FindingId,
item.Status,
item.Severity.Normalized,
item.Severity.Score?.ToString("0.00", CultureInfo.InvariantCulture) ?? "n/a",
FormatUncertaintyTierPlain(item.Uncertainty?.AggregateTier),
item.Uncertainty?.RiskScore?.ToString("0.00", CultureInfo.InvariantCulture) ?? "n/a",
item.SbomId,
FormatUpdatedAt(item.UpdatedAt));
}
@@ -6420,6 +6491,8 @@ internal static class CommandHandlers
table.AddRow("Finding", Markup.Escape(finding.FindingId));
table.AddRow("Status", Markup.Escape(finding.Status));
table.AddRow("Severity", Markup.Escape(FormatSeverity(finding.Severity)));
table.AddRow("Uncertainty Tier", FormatUncertaintyTier(finding.Uncertainty?.AggregateTier));
table.AddRow("Risk Score", Markup.Escape(FormatScore(finding.Uncertainty?.RiskScore)));
table.AddRow("SBOM", Markup.Escape(finding.SbomId));
table.AddRow("Policy Version", Markup.Escape(finding.PolicyVersion.ToString(CultureInfo.InvariantCulture)));
table.AddRow("Updated (UTC)", Markup.Escape(FormatUpdatedAt(finding.UpdatedAt)));
@@ -6427,6 +6500,11 @@ internal static class CommandHandlers
table.AddRow("Advisories", Markup.Escape(FormatListPreview(finding.AdvisoryIds)));
table.AddRow("VEX", Markup.Escape(FormatVexMetadata(finding.Vex)));
if (finding.Uncertainty?.States is { Count: > 0 })
{
table.AddRow("Uncertainty States", Markup.Escape(FormatUncertaintyStates(finding.Uncertainty.States)));
}
AnsiConsole.Write(table);
}
else
@@ -6434,6 +6512,9 @@ internal static class CommandHandlers
logger.LogInformation("Finding {Finding}", finding.FindingId);
logger.LogInformation(" Status: {Status}", finding.Status);
logger.LogInformation(" Severity: {Severity}", FormatSeverity(finding.Severity));
logger.LogInformation(" Uncertainty: {Tier} (Risk {Risk})",
FormatUncertaintyTierPlain(finding.Uncertainty?.AggregateTier),
finding.Uncertainty?.RiskScore?.ToString("0.00", CultureInfo.InvariantCulture) ?? "n/a");
logger.LogInformation(" SBOM: {Sbom}", finding.SbomId);
logger.LogInformation(" Policy version: {Version}", finding.PolicyVersion);
logger.LogInformation(" Updated (UTC): {Updated}", FormatUpdatedAt(finding.UpdatedAt));
@@ -6449,6 +6530,10 @@ internal static class CommandHandlers
{
logger.LogInformation(" VEX: {Vex}", FormatVexMetadata(finding.Vex));
}
if (finding.Uncertainty?.States is { Count: > 0 })
{
logger.LogInformation(" Uncertainty States: {States}", FormatUncertaintyStates(finding.Uncertainty.States));
}
}
}
@@ -6569,6 +6654,54 @@ internal static class CommandHandlers
private static string FormatScore(double? score)
=> score.HasValue ? score.Value.ToString("0.00", CultureInfo.InvariantCulture) : "-";
private static string FormatUncertaintyTier(string? tier)
{
if (string.IsNullOrWhiteSpace(tier))
{
return "[grey]-[/]";
}
var (color, display) = tier.ToUpperInvariant() switch
{
"T1" => ("red", "T1 (High)"),
"T2" => ("yellow", "T2 (Medium)"),
"T3" => ("blue", "T3 (Low)"),
"T4" => ("green", "T4 (Negligible)"),
_ => ("grey", tier)
};
return $"[{color}]{Markup.Escape(display)}[/]";
}
private static string FormatUncertaintyTierPlain(string? tier)
{
if (string.IsNullOrWhiteSpace(tier))
{
return "-";
}
return tier.ToUpperInvariant() switch
{
"T1" => "T1 (High)",
"T2" => "T2 (Medium)",
"T3" => "T3 (Low)",
"T4" => "T4 (Negligible)",
_ => tier
};
}
private static string FormatUncertaintyStates(IReadOnlyList<PolicyFindingUncertaintyState>? states)
{
if (states is null || states.Count == 0)
{
return "-";
}
return string.Join(", ", states
.Where(s => !string.IsNullOrWhiteSpace(s.Code))
.Select(s => $"{s.Code}={s.Entropy?.ToString("0.00", CultureInfo.InvariantCulture) ?? "?"}"));
}
private static string FormatKeyValuePairs(IReadOnlyDictionary<string, string>? values)
{
if (values is null || values.Count == 0)

View File

@@ -2443,6 +2443,29 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
var updatedAt = document.UpdatedAt ?? DateTimeOffset.MinValue;
PolicyFindingUncertainty? uncertainty = null;
if (document.Uncertainty is not null)
{
IReadOnlyList<PolicyFindingUncertaintyState>? states = null;
if (document.Uncertainty.States is not null)
{
states = document.Uncertainty.States
.Where(s => s is not null)
.Select(s => new PolicyFindingUncertaintyState(
string.IsNullOrWhiteSpace(s!.Code) ? null : s.Code,
string.IsNullOrWhiteSpace(s.Name) ? null : s.Name,
s.Entropy,
string.IsNullOrWhiteSpace(s.Tier) ? null : s.Tier))
.ToList();
}
uncertainty = new PolicyFindingUncertainty(
string.IsNullOrWhiteSpace(document.Uncertainty.AggregateTier) ? null : document.Uncertainty.AggregateTier,
document.Uncertainty.RiskScore,
states,
document.Uncertainty.ComputedAt);
}
return new PolicyFindingDocument(
findingId,
status,
@@ -2450,6 +2473,7 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
sbomId,
advisoryIds,
vex,
uncertainty,
document.PolicyVersion ?? 0,
updatedAt,
string.IsNullOrWhiteSpace(document.RunId) ? null : document.RunId);

View File

@@ -10,4 +10,36 @@ internal sealed record EntryTraceResponseModel(
DateTimeOffset GeneratedAt,
EntryTraceGraph Graph,
IReadOnlyList<string> Ndjson,
EntryTracePlan? BestPlan);
EntryTracePlan? BestPlan,
SemanticEntrypointSummary? Semantic = null);
/// <summary>
/// Summary of semantic entrypoint analysis for CLI display.
/// </summary>
internal sealed record SemanticEntrypointSummary
{
public string Intent { get; init; } = "Unknown";
public IReadOnlyList<string> Capabilities { get; init; } = Array.Empty<string>();
public IReadOnlyList<ThreatVectorSummary> Threats { get; init; } = Array.Empty<ThreatVectorSummary>();
public IReadOnlyList<DataBoundarySummary> DataBoundaries { get; init; } = Array.Empty<DataBoundarySummary>();
public string? Framework { get; init; }
public string? Language { get; init; }
public double ConfidenceScore { get; init; }
public string ConfidenceTier { get; init; } = "Unknown";
public string AnalyzedAt { get; init; } = string.Empty;
}
internal sealed record ThreatVectorSummary
{
public string Type { get; init; } = string.Empty;
public double Confidence { get; init; }
public string? CweId { get; init; }
public string? OwaspCategory { get; init; }
}
internal sealed record DataBoundarySummary
{
public string Type { get; init; } = string.Empty;
public string Direction { get; init; } = string.Empty;
public string Sensitivity { get; init; } = string.Empty;
}

View File

@@ -25,6 +25,7 @@ internal sealed record PolicyFindingDocument(
string SbomId,
IReadOnlyList<string> AdvisoryIds,
PolicyFindingVexMetadata? Vex,
PolicyFindingUncertainty? Uncertainty,
int PolicyVersion,
DateTimeOffset UpdatedAt,
string? RunId);
@@ -33,6 +34,18 @@ internal sealed record PolicyFindingSeverity(string Normalized, double? Score);
internal sealed record PolicyFindingVexMetadata(string? WinningStatementId, string? Source, string? Status);
internal sealed record PolicyFindingUncertainty(
string? AggregateTier,
double? RiskScore,
IReadOnlyList<PolicyFindingUncertaintyState>? States,
DateTimeOffset? ComputedAt);
internal sealed record PolicyFindingUncertaintyState(
string? Code,
string? Name,
double? Entropy,
string? Tier);
internal sealed record PolicyFindingExplainResult(
string FindingId,
int PolicyVersion,

View File

@@ -27,6 +27,8 @@ internal sealed class PolicyFindingDocumentDocument
public PolicyFindingVexDocument? Vex { get; set; }
public PolicyFindingUncertaintyDocument? Uncertainty { get; set; }
public int? PolicyVersion { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
@@ -34,6 +36,28 @@ internal sealed class PolicyFindingDocumentDocument
public string? RunId { get; set; }
}
internal sealed class PolicyFindingUncertaintyDocument
{
public string? AggregateTier { get; set; }
public double? RiskScore { get; set; }
public List<PolicyFindingUncertaintyStateDocument>? States { get; set; }
public DateTimeOffset? ComputedAt { get; set; }
}
internal sealed class PolicyFindingUncertaintyStateDocument
{
public string? Code { get; set; }
public string? Name { get; set; }
public double? Entropy { get; set; }
public string? Tier { get; set; }
}
internal sealed class PolicyFindingSeverityDocument
{
public string? Normalized { get; set; }

View File

@@ -17,7 +17,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.1.0" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
<PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
</ItemGroup>