up
Some checks failed
Docs CI / lint-and-preview (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
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Reachability Corpus Validation / validate-corpus (push) Has been cancelled
Reachability Corpus Validation / validate-ground-truths (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Reachability Corpus Validation / determinism-check (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (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
Some checks failed
Docs CI / lint-and-preview (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
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Reachability Corpus Validation / validate-corpus (push) Has been cancelled
Reachability Corpus Validation / validate-ground-truths (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Reachability Corpus Validation / determinism-check (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (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
This commit is contained in:
@@ -4357,6 +4357,61 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
|
||||
?? new ReachabilityExplainResult();
|
||||
}
|
||||
|
||||
// UI-CLI-401-007: Graph explain with DSSE pointers, runtime hits, predicates, counterfactuals
|
||||
public async Task<GraphExplainResult> ExplainGraphAsync(GraphExplainRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
EnsureBackendConfigured();
|
||||
OfflineModeGuard.ThrowIfOffline("graph explain");
|
||||
|
||||
var queryParams = new List<string>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.VulnerabilityId))
|
||||
queryParams.Add($"vulnerabilityId={Uri.EscapeDataString(request.VulnerabilityId)}");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.PackagePurl))
|
||||
queryParams.Add($"packagePurl={Uri.EscapeDataString(request.PackagePurl)}");
|
||||
|
||||
if (request.IncludeCallPaths)
|
||||
queryParams.Add("includeCallPaths=true");
|
||||
|
||||
if (request.IncludeRuntimeHits)
|
||||
queryParams.Add("includeRuntimeHits=true");
|
||||
|
||||
if (request.IncludePredicates)
|
||||
queryParams.Add("includePredicates=true");
|
||||
|
||||
if (request.IncludeDsseEnvelopes)
|
||||
queryParams.Add("includeDsseEnvelopes=true");
|
||||
|
||||
if (request.IncludeCounterfactuals)
|
||||
queryParams.Add("includeCounterfactuals=true");
|
||||
|
||||
var query = queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : "";
|
||||
var relative = $"api/graphs/{Uri.EscapeDataString(request.GraphId)}/explain{query}";
|
||||
|
||||
using var httpRequest = CreateRequest(HttpMethod.Get, relative);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Tenant))
|
||||
{
|
||||
httpRequest.Headers.TryAddWithoutValidation("X-Tenant-Id", request.Tenant.Trim());
|
||||
}
|
||||
|
||||
await AuthorizeRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var (message, _) = await CreateFailureDetailsAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
throw new HttpRequestException($"Explain graph failed: {message}", null, response.StatusCode);
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<GraphExplainResult>(JsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? new GraphExplainResult();
|
||||
}
|
||||
|
||||
// CLI-SDK-63-001: API spec operations
|
||||
public async Task<ApiSpecListResponse> ListApiSpecsAsync(string? tenant, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -4660,4 +4715,121 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
|
||||
var result = await response.Content.ReadFromJsonAsync<SdkListResponse>(JsonOptions, cancellationToken).ConfigureAwait(false);
|
||||
return result ?? new SdkListResponse { Success = false, Error = "Empty response" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports VEX decisions as OpenVEX documents with optional DSSE signing.
|
||||
/// </summary>
|
||||
public async Task<DecisionExportResponse> ExportDecisionsAsync(
|
||||
DecisionExportRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
try
|
||||
{
|
||||
var queryParams = new List<string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(request.ScanId))
|
||||
{
|
||||
queryParams.Add($"scanId={Uri.EscapeDataString(request.ScanId)}");
|
||||
}
|
||||
|
||||
if (request.VulnIds is { Count: > 0 })
|
||||
{
|
||||
foreach (var vulnId in request.VulnIds)
|
||||
{
|
||||
queryParams.Add($"vulnId={Uri.EscapeDataString(vulnId)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (request.Purls is { Count: > 0 })
|
||||
{
|
||||
foreach (var purl in request.Purls)
|
||||
{
|
||||
queryParams.Add($"purl={Uri.EscapeDataString(purl)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (request.Statuses is { Count: > 0 })
|
||||
{
|
||||
foreach (var status in request.Statuses)
|
||||
{
|
||||
queryParams.Add($"status={Uri.EscapeDataString(status)}");
|
||||
}
|
||||
}
|
||||
|
||||
queryParams.Add($"format={Uri.EscapeDataString(request.Format)}");
|
||||
queryParams.Add($"sign={request.Sign.ToString().ToLowerInvariant()}");
|
||||
queryParams.Add($"rekor={request.SubmitToRekor.ToString().ToLowerInvariant()}");
|
||||
queryParams.Add($"includeEvidence={request.IncludeEvidence.ToString().ToLowerInvariant()}");
|
||||
|
||||
var queryString = queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : "";
|
||||
var url = $"{_options.BackendUrl}/api/v1/decisions/export{queryString}";
|
||||
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
httpRequest.Headers.TryAddWithoutValidation("X-Tenant-Id", request.TenantId);
|
||||
await AuthorizeRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var (message, _) = await CreateFailureDetailsAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
return new DecisionExportResponse
|
||||
{
|
||||
Success = false,
|
||||
Error = message
|
||||
};
|
||||
}
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Extract metadata from response headers
|
||||
response.Headers.TryGetValues("X-VEX-Digest", out var digestValues);
|
||||
response.Headers.TryGetValues("X-VEX-Rekor-Index", out var rekorIndexValues);
|
||||
response.Headers.TryGetValues("X-VEX-Rekor-UUID", out var rekorUuidValues);
|
||||
response.Headers.TryGetValues("X-VEX-Statement-Count", out var countValues);
|
||||
response.Headers.TryGetValues("X-VEX-Signed", out var signedValues);
|
||||
|
||||
var digest = digestValues?.FirstOrDefault();
|
||||
var rekorUuid = rekorUuidValues?.FirstOrDefault();
|
||||
long? rekorIndex = null;
|
||||
int statementCount = 0;
|
||||
bool signed = false;
|
||||
|
||||
if (rekorIndexValues?.FirstOrDefault() is { } indexStr && long.TryParse(indexStr, out var idx))
|
||||
{
|
||||
rekorIndex = idx;
|
||||
}
|
||||
|
||||
if (countValues?.FirstOrDefault() is { } countStr && int.TryParse(countStr, out var cnt))
|
||||
{
|
||||
statementCount = cnt;
|
||||
}
|
||||
|
||||
if (signedValues?.FirstOrDefault() is { } signedStr)
|
||||
{
|
||||
signed = signedStr.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return new DecisionExportResponse
|
||||
{
|
||||
Success = true,
|
||||
Content = content,
|
||||
Digest = digest,
|
||||
RekorLogIndex = rekorIndex,
|
||||
RekorUuid = rekorUuid,
|
||||
StatementCount = statementCount,
|
||||
Signed = signed
|
||||
};
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
return new DecisionExportResponse
|
||||
{
|
||||
Success = false,
|
||||
Error = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user