new advisories work and features gaps work
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
// <copyright file="IVexOverrideAttestorClient.cs" company="StellaOps">
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Sprint: SPRINT_20260112_004_VULN_vex_override_workflow (VEX-OVR-002)
|
||||
// </copyright>
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.VulnExplorer.Api.Models;
|
||||
|
||||
namespace StellaOps.VulnExplorer.Api.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Client for creating signed VEX override attestations via Attestor.
|
||||
/// </summary>
|
||||
public interface IVexOverrideAttestorClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a signed DSSE attestation for a VEX override decision.
|
||||
/// </summary>
|
||||
Task<VexOverrideAttestationResult> CreateAttestationAsync(
|
||||
VexOverrideAttestationRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies an existing VEX override attestation.
|
||||
/// </summary>
|
||||
Task<AttestationVerificationStatusDto> VerifyAttestationAsync(
|
||||
string envelopeDigest,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to create a VEX override attestation.
|
||||
/// </summary>
|
||||
public sealed record VexOverrideAttestationRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Vulnerability ID being overridden.
|
||||
/// </summary>
|
||||
[JsonPropertyName("vulnerabilityId")]
|
||||
public required string VulnerabilityId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Subject the override applies to.
|
||||
/// </summary>
|
||||
[JsonPropertyName("subject")]
|
||||
public required SubjectRefDto Subject { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// VEX status being set.
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public required VexStatus Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Justification type.
|
||||
/// </summary>
|
||||
[JsonPropertyName("justificationType")]
|
||||
public required VexJustificationType JustificationType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Justification text.
|
||||
/// </summary>
|
||||
[JsonPropertyName("justificationText")]
|
||||
public string? JustificationText { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Evidence references supporting the decision.
|
||||
/// </summary>
|
||||
[JsonPropertyName("evidenceRefs")]
|
||||
public IReadOnlyList<EvidenceRefDto>? EvidenceRefs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Scope of the override.
|
||||
/// </summary>
|
||||
[JsonPropertyName("scope")]
|
||||
public VexScopeDto? Scope { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Validity period.
|
||||
/// </summary>
|
||||
[JsonPropertyName("validFor")]
|
||||
public ValidForDto? ValidFor { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Actor creating the override.
|
||||
/// </summary>
|
||||
[JsonPropertyName("createdBy")]
|
||||
public required ActorRefDto CreatedBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to anchor to Rekor.
|
||||
/// </summary>
|
||||
[JsonPropertyName("anchorToRekor")]
|
||||
public bool AnchorToRekor { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Signing key ID (null = default).
|
||||
/// </summary>
|
||||
[JsonPropertyName("signingKeyId")]
|
||||
public string? SigningKeyId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Storage destination for the attestation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("storageDestination")]
|
||||
public string? StorageDestination { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional metadata.
|
||||
/// </summary>
|
||||
[JsonPropertyName("additionalMetadata")]
|
||||
public IReadOnlyDictionary<string, string>? AdditionalMetadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of creating a VEX override attestation.
|
||||
/// </summary>
|
||||
public sealed record VexOverrideAttestationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the attestation was successfully created.
|
||||
/// </summary>
|
||||
[JsonPropertyName("success")]
|
||||
public required bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Created attestation details (if successful).
|
||||
/// </summary>
|
||||
[JsonPropertyName("attestation")]
|
||||
public VexOverrideAttestationDto? Attestation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message (if failed).
|
||||
/// </summary>
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error code (if failed).
|
||||
/// </summary>
|
||||
[JsonPropertyName("errorCode")]
|
||||
public string? ErrorCode { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful result.
|
||||
/// </summary>
|
||||
public static VexOverrideAttestationResult Ok(VexOverrideAttestationDto attestation) => new()
|
||||
{
|
||||
Success = true,
|
||||
Attestation = attestation
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a failed result.
|
||||
/// </summary>
|
||||
public static VexOverrideAttestationResult Fail(string error, string? errorCode = null) => new()
|
||||
{
|
||||
Success = false,
|
||||
Error = error,
|
||||
ErrorCode = errorCode
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTTP client implementation for VEX override attestations.
|
||||
/// </summary>
|
||||
public sealed class HttpVexOverrideAttestorClient : IVexOverrideAttestorClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<HttpVexOverrideAttestorClient> _logger;
|
||||
|
||||
public HttpVexOverrideAttestorClient(
|
||||
HttpClient httpClient,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<HttpVexOverrideAttestorClient> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_timeProvider = timeProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<VexOverrideAttestationResult> CreateAttestationAsync(
|
||||
VexOverrideAttestationRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync(
|
||||
"/api/v1/attestations/vex-override",
|
||||
request,
|
||||
cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
_logger.LogWarning(
|
||||
"Failed to create VEX override attestation: {StatusCode} - {Error}",
|
||||
response.StatusCode, errorBody);
|
||||
|
||||
return VexOverrideAttestationResult.Fail(
|
||||
$"Attestor returned {response.StatusCode}: {errorBody}",
|
||||
response.StatusCode.ToString());
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<VexOverrideAttestationDto>(
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
return VexOverrideAttestationResult.Fail("Empty response from Attestor");
|
||||
}
|
||||
|
||||
return VexOverrideAttestationResult.Ok(result);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "HTTP error creating VEX override attestation");
|
||||
return VexOverrideAttestationResult.Fail($"HTTP error: {ex.Message}", "HTTP_ERROR");
|
||||
}
|
||||
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Timeout creating VEX override attestation");
|
||||
return VexOverrideAttestationResult.Fail("Request timed out", "TIMEOUT");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AttestationVerificationStatusDto> VerifyAttestationAsync(
|
||||
string envelopeDigest,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(
|
||||
$"/api/v1/attestations/{envelopeDigest}/verify",
|
||||
cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return new AttestationVerificationStatusDto(
|
||||
SignatureValid: false,
|
||||
RekorVerified: null,
|
||||
VerifiedAt: _timeProvider.GetUtcNow(),
|
||||
ErrorMessage: $"Attestor returned {response.StatusCode}");
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<AttestationVerificationStatusDto>(
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
return result ?? new AttestationVerificationStatusDto(
|
||||
SignatureValid: false,
|
||||
RekorVerified: null,
|
||||
VerifiedAt: _timeProvider.GetUtcNow(),
|
||||
ErrorMessage: "Empty response from Attestor");
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogError(ex, "Error verifying attestation {Digest}", envelopeDigest);
|
||||
return new AttestationVerificationStatusDto(
|
||||
SignatureValid: false,
|
||||
RekorVerified: null,
|
||||
VerifiedAt: _timeProvider.GetUtcNow(),
|
||||
ErrorMessage: ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stub implementation for offline/testing scenarios.
|
||||
/// </summary>
|
||||
public sealed class StubVexOverrideAttestorClient : IVexOverrideAttestorClient
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public StubVexOverrideAttestorClient(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<VexOverrideAttestationResult> CreateAttestationAsync(
|
||||
VexOverrideAttestationRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// In offline mode, return an unsigned placeholder
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
var attestation = new VexOverrideAttestationDto(
|
||||
EnvelopeDigest: $"sha256:offline-stub-{Guid.NewGuid():N}",
|
||||
PredicateType: "https://stellaops.dev/predicates/vex-override@v1",
|
||||
RekorLogIndex: null,
|
||||
RekorEntryId: null,
|
||||
StorageRef: "offline-queue",
|
||||
AttestationCreatedAt: now,
|
||||
Verified: false,
|
||||
VerificationStatus: null);
|
||||
|
||||
return Task.FromResult(VexOverrideAttestationResult.Ok(attestation));
|
||||
}
|
||||
|
||||
public Task<AttestationVerificationStatusDto> VerifyAttestationAsync(
|
||||
string envelopeDigest,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(new AttestationVerificationStatusDto(
|
||||
SignatureValid: false,
|
||||
RekorVerified: null,
|
||||
VerifiedAt: _timeProvider.GetUtcNow(),
|
||||
ErrorMessage: "Offline mode - verification unavailable"));
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,16 @@ public sealed class VexDecisionStore
|
||||
private readonly ConcurrentDictionary<Guid, VexDecisionDto> _decisions = new();
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly IVexOverrideAttestorClient? _attestorClient;
|
||||
|
||||
public VexDecisionStore(TimeProvider? timeProvider = null, IGuidProvider? guidProvider = null)
|
||||
public VexDecisionStore(
|
||||
TimeProvider? timeProvider = null,
|
||||
IGuidProvider? guidProvider = null,
|
||||
IVexOverrideAttestorClient? attestorClient = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
_attestorClient = attestorClient;
|
||||
}
|
||||
|
||||
public VexDecisionDto Create(CreateVexDecisionRequest request, string userId, string userDisplayName)
|
||||
@@ -36,6 +41,7 @@ public sealed class VexDecisionStore
|
||||
Scope: request.Scope,
|
||||
ValidFor: request.ValidFor,
|
||||
AttestationRef: null, // Will be set when attestation is generated
|
||||
SignedOverride: null, // Will be set when attestation is generated (VEX-OVR-002)
|
||||
SupersedesDecisionId: request.SupersedesDecisionId,
|
||||
CreatedBy: new ActorRefDto(userId, userDisplayName),
|
||||
CreatedAt: now,
|
||||
@@ -105,4 +111,133 @@ public sealed class VexDecisionStore
|
||||
}
|
||||
|
||||
public int Count() => _decisions.Count;
|
||||
|
||||
// Sprint: SPRINT_20260112_004_VULN_vex_override_workflow (VEX-OVR-002)
|
||||
|
||||
/// <summary>
|
||||
/// Creates a VEX decision with a signed attestation.
|
||||
/// </summary>
|
||||
public async Task<(VexDecisionDto Decision, VexOverrideAttestationResult? AttestationResult)> CreateWithAttestationAsync(
|
||||
CreateVexDecisionRequest request,
|
||||
string userId,
|
||||
string userDisplayName,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var id = _guidProvider.NewGuid();
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
VexOverrideAttestationDto? signedOverride = null;
|
||||
VexOverrideAttestationResult? attestationResult = null;
|
||||
|
||||
// Create attestation if requested and client is available
|
||||
if (request.AttestationOptions?.CreateAttestation == true && _attestorClient is not null)
|
||||
{
|
||||
var attestationRequest = new VexOverrideAttestationRequest
|
||||
{
|
||||
VulnerabilityId = request.VulnerabilityId,
|
||||
Subject = request.Subject,
|
||||
Status = request.Status,
|
||||
JustificationType = request.JustificationType,
|
||||
JustificationText = request.JustificationText,
|
||||
EvidenceRefs = request.EvidenceRefs,
|
||||
Scope = request.Scope,
|
||||
ValidFor = request.ValidFor,
|
||||
CreatedBy = new ActorRefDto(userId, userDisplayName),
|
||||
AnchorToRekor = request.AttestationOptions.AnchorToRekor,
|
||||
SigningKeyId = request.AttestationOptions.SigningKeyId,
|
||||
StorageDestination = request.AttestationOptions.StorageDestination,
|
||||
AdditionalMetadata = request.AttestationOptions.AdditionalMetadata
|
||||
};
|
||||
|
||||
attestationResult = await _attestorClient.CreateAttestationAsync(attestationRequest, cancellationToken);
|
||||
|
||||
if (attestationResult.Success && attestationResult.Attestation is not null)
|
||||
{
|
||||
signedOverride = attestationResult.Attestation;
|
||||
}
|
||||
}
|
||||
|
||||
var decision = new VexDecisionDto(
|
||||
Id: id,
|
||||
VulnerabilityId: request.VulnerabilityId,
|
||||
Subject: request.Subject,
|
||||
Status: request.Status,
|
||||
JustificationType: request.JustificationType,
|
||||
JustificationText: request.JustificationText,
|
||||
EvidenceRefs: request.EvidenceRefs,
|
||||
Scope: request.Scope,
|
||||
ValidFor: request.ValidFor,
|
||||
AttestationRef: null,
|
||||
SignedOverride: signedOverride,
|
||||
SupersedesDecisionId: request.SupersedesDecisionId,
|
||||
CreatedBy: new ActorRefDto(userId, userDisplayName),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: null);
|
||||
|
||||
_decisions[id] = decision;
|
||||
return (decision, attestationResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a VEX decision and optionally creates a new attestation.
|
||||
/// </summary>
|
||||
public async Task<(VexDecisionDto? Decision, VexOverrideAttestationResult? AttestationResult)> UpdateWithAttestationAsync(
|
||||
Guid id,
|
||||
UpdateVexDecisionRequest request,
|
||||
string userId,
|
||||
string userDisplayName,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_decisions.TryGetValue(id, out var existing))
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
VexOverrideAttestationDto? signedOverride = existing.SignedOverride;
|
||||
VexOverrideAttestationResult? attestationResult = null;
|
||||
|
||||
// Create new attestation if requested
|
||||
if (request.AttestationOptions?.CreateAttestation == true && _attestorClient is not null)
|
||||
{
|
||||
var attestationRequest = new VexOverrideAttestationRequest
|
||||
{
|
||||
VulnerabilityId = existing.VulnerabilityId,
|
||||
Subject = existing.Subject,
|
||||
Status = request.Status ?? existing.Status,
|
||||
JustificationType = request.JustificationType ?? existing.JustificationType,
|
||||
JustificationText = request.JustificationText ?? existing.JustificationText,
|
||||
EvidenceRefs = request.EvidenceRefs ?? existing.EvidenceRefs,
|
||||
Scope = request.Scope ?? existing.Scope,
|
||||
ValidFor = request.ValidFor ?? existing.ValidFor,
|
||||
CreatedBy = new ActorRefDto(userId, userDisplayName),
|
||||
AnchorToRekor = request.AttestationOptions.AnchorToRekor,
|
||||
SigningKeyId = request.AttestationOptions.SigningKeyId,
|
||||
StorageDestination = request.AttestationOptions.StorageDestination,
|
||||
AdditionalMetadata = request.AttestationOptions.AdditionalMetadata
|
||||
};
|
||||
|
||||
attestationResult = await _attestorClient.CreateAttestationAsync(attestationRequest, cancellationToken);
|
||||
|
||||
if (attestationResult.Success && attestationResult.Attestation is not null)
|
||||
{
|
||||
signedOverride = attestationResult.Attestation;
|
||||
}
|
||||
}
|
||||
|
||||
var updated = existing with
|
||||
{
|
||||
Status = request.Status ?? existing.Status,
|
||||
JustificationType = request.JustificationType ?? existing.JustificationType,
|
||||
JustificationText = request.JustificationText ?? existing.JustificationText,
|
||||
EvidenceRefs = request.EvidenceRefs ?? existing.EvidenceRefs,
|
||||
Scope = request.Scope ?? existing.Scope,
|
||||
ValidFor = request.ValidFor ?? existing.ValidFor,
|
||||
SignedOverride = signedOverride,
|
||||
SupersedesDecisionId = request.SupersedesDecisionId ?? existing.SupersedesDecisionId,
|
||||
UpdatedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
_decisions[id] = updated;
|
||||
return (updated, attestationResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,57 @@ public sealed record VexDecisionDto(
|
||||
VexScopeDto? Scope,
|
||||
ValidForDto? ValidFor,
|
||||
AttestationRefDto? AttestationRef,
|
||||
VexOverrideAttestationDto? SignedOverride,
|
||||
Guid? SupersedesDecisionId,
|
||||
ActorRefDto CreatedBy,
|
||||
DateTimeOffset CreatedAt,
|
||||
DateTimeOffset? UpdatedAt);
|
||||
|
||||
/// <summary>
|
||||
/// Signed VEX override attestation details.
|
||||
/// Sprint: SPRINT_20260112_004_VULN_vex_override_workflow (VEX-OVR-001)
|
||||
/// </summary>
|
||||
public sealed record VexOverrideAttestationDto(
|
||||
/// <summary>DSSE envelope digest (sha256:hex).</summary>
|
||||
string EnvelopeDigest,
|
||||
|
||||
/// <summary>Predicate type for the attestation.</summary>
|
||||
string PredicateType,
|
||||
|
||||
/// <summary>Rekor transparency log index (null if not anchored).</summary>
|
||||
long? RekorLogIndex,
|
||||
|
||||
/// <summary>Rekor entry ID (null if not anchored).</summary>
|
||||
string? RekorEntryId,
|
||||
|
||||
/// <summary>Attestation storage location/reference.</summary>
|
||||
string? StorageRef,
|
||||
|
||||
/// <summary>Timestamp when attestation was created.</summary>
|
||||
DateTimeOffset AttestationCreatedAt,
|
||||
|
||||
/// <summary>Whether the attestation has been verified.</summary>
|
||||
bool Verified,
|
||||
|
||||
/// <summary>Verification status details.</summary>
|
||||
AttestationVerificationStatusDto? VerificationStatus);
|
||||
|
||||
/// <summary>
|
||||
/// Attestation verification status details.
|
||||
/// </summary>
|
||||
public sealed record AttestationVerificationStatusDto(
|
||||
/// <summary>Whether signature was valid.</summary>
|
||||
bool SignatureValid,
|
||||
|
||||
/// <summary>Whether Rekor inclusion was verified.</summary>
|
||||
bool? RekorVerified,
|
||||
|
||||
/// <summary>Timestamp when verification was performed.</summary>
|
||||
DateTimeOffset? VerifiedAt,
|
||||
|
||||
/// <summary>Error message if verification failed.</summary>
|
||||
string? ErrorMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Reference to an artifact or SBOM component that a VEX decision applies to.
|
||||
/// </summary>
|
||||
@@ -128,7 +174,29 @@ public sealed record CreateVexDecisionRequest(
|
||||
IReadOnlyList<EvidenceRefDto>? EvidenceRefs,
|
||||
VexScopeDto? Scope,
|
||||
ValidForDto? ValidFor,
|
||||
Guid? SupersedesDecisionId);
|
||||
Guid? SupersedesDecisionId,
|
||||
/// <summary>Attestation options for signed override.</summary>
|
||||
AttestationRequestOptions? AttestationOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Options for creating a signed attestation with the VEX decision.
|
||||
/// Sprint: SPRINT_20260112_004_VULN_vex_override_workflow (VEX-OVR-001)
|
||||
/// </summary>
|
||||
public sealed record AttestationRequestOptions(
|
||||
/// <summary>Whether to create a signed attestation (required in strict mode).</summary>
|
||||
bool CreateAttestation,
|
||||
|
||||
/// <summary>Whether to anchor the attestation to Rekor transparency log.</summary>
|
||||
bool AnchorToRekor = false,
|
||||
|
||||
/// <summary>Key ID to use for signing (null = default).</summary>
|
||||
string? SigningKeyId = null,
|
||||
|
||||
/// <summary>Storage destination for the attestation.</summary>
|
||||
string? StorageDestination = null,
|
||||
|
||||
/// <summary>Additional metadata to include in the attestation.</summary>
|
||||
IReadOnlyDictionary<string, string>? AdditionalMetadata = null);
|
||||
|
||||
/// <summary>
|
||||
/// Request to update an existing VEX decision.
|
||||
@@ -140,7 +208,9 @@ public sealed record UpdateVexDecisionRequest(
|
||||
IReadOnlyList<EvidenceRefDto>? EvidenceRefs,
|
||||
VexScopeDto? Scope,
|
||||
ValidForDto? ValidFor,
|
||||
Guid? SupersedesDecisionId);
|
||||
Guid? SupersedesDecisionId,
|
||||
/// <summary>Attestation options for signed override update.</summary>
|
||||
AttestationRequestOptions? AttestationOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Response for listing VEX decisions.
|
||||
|
||||
Reference in New Issue
Block a user