Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -7,21 +7,12 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.OpenApi;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using StellaOps.VulnExplorer.Api.Data;
|
||||
using StellaOps.VulnExplorer.Api.Models;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "StellaOps Vuln Explorer API",
|
||||
Version = "v1",
|
||||
Description = "Deterministic vulnerability listing/detail and VEX decision endpoints"
|
||||
});
|
||||
});
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
// Configure JSON serialization with enum string converter
|
||||
builder.Services.ConfigureHttpJsonOptions(options =>
|
||||
@@ -57,8 +48,7 @@ app.MapGet("/v1/vulns", ([AsParameters] VulnFilter filter) =>
|
||||
|
||||
var response = new VulnListResponse(page, next);
|
||||
return Results.Ok(response);
|
||||
})
|
||||
.WithOpenApi();
|
||||
});
|
||||
|
||||
app.MapGet("/v1/vulns/{id}", ([FromHeader(Name = "x-stella-tenant")] string? tenant, string id) =>
|
||||
{
|
||||
@@ -70,8 +60,7 @@ app.MapGet("/v1/vulns/{id}", ([FromHeader(Name = "x-stella-tenant")] string? ten
|
||||
return SampleData.TryGetDetail(id, out var detail) && detail is not null
|
||||
? Results.Ok(detail)
|
||||
: Results.NotFound();
|
||||
})
|
||||
.WithOpenApi();
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// VEX Decision Endpoints (API-VEX-06-001, API-VEX-06-002, API-VEX-06-003)
|
||||
@@ -105,8 +94,7 @@ app.MapPost("/v1/vex-decisions", (
|
||||
var decision = store.Create(request, effectiveUserId, effectiveUserName);
|
||||
return Results.Created($"/v1/vex-decisions/{decision.Id}", decision);
|
||||
})
|
||||
.WithName("CreateVexDecision")
|
||||
.WithOpenApi();
|
||||
.WithName("CreateVexDecision");
|
||||
|
||||
app.MapPatch("/v1/vex-decisions/{id:guid}", (
|
||||
[FromHeader(Name = "x-stella-tenant")] string? tenant,
|
||||
@@ -124,8 +112,7 @@ app.MapPatch("/v1/vex-decisions/{id:guid}", (
|
||||
? Results.Ok(updated)
|
||||
: Results.NotFound(new { error = $"VEX decision {id} not found" });
|
||||
})
|
||||
.WithName("UpdateVexDecision")
|
||||
.WithOpenApi();
|
||||
.WithName("UpdateVexDecision");
|
||||
|
||||
app.MapGet("/v1/vex-decisions", ([AsParameters] VexDecisionFilter filter, VexDecisionStore store) =>
|
||||
{
|
||||
@@ -149,8 +136,7 @@ app.MapGet("/v1/vex-decisions", ([AsParameters] VexDecisionFilter filter, VexDec
|
||||
|
||||
return Results.Ok(new VexDecisionListResponse(decisions, next));
|
||||
})
|
||||
.WithName("ListVexDecisions")
|
||||
.WithOpenApi();
|
||||
.WithName("ListVexDecisions");
|
||||
|
||||
app.MapGet("/v1/vex-decisions/{id:guid}", (
|
||||
[FromHeader(Name = "x-stella-tenant")] string? tenant,
|
||||
@@ -167,8 +153,7 @@ app.MapGet("/v1/vex-decisions/{id:guid}", (
|
||||
? Results.Ok(decision)
|
||||
: Results.NotFound(new { error = $"VEX decision {id} not found" });
|
||||
})
|
||||
.WithName("GetVexDecision")
|
||||
.WithOpenApi();
|
||||
.WithName("GetVexDecision");
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -234,4 +219,5 @@ public record VexDecisionFilter(
|
||||
[FromQuery(Name = "pageSize")] int? PageSize,
|
||||
[FromQuery(Name = "pageToken")] string? PageToken);
|
||||
|
||||
public partial class Program { }
|
||||
// Program class public for WebApplicationFactory<Program>
|
||||
public partial class Program;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"StellaOps.VulnExplorer.Api": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:62547;http://localhost:62548"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
<RootNamespace>StellaOps.VulnExplorer.Api</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
// <copyright file="EvidenceSubgraphContracts.cs" company="StellaOps">
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// </copyright>
|
||||
|
||||
namespace StellaOps.VulnExplorer.WebService.Contracts;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Response containing the evidence subgraph for a finding.
|
||||
/// </summary>
|
||||
public sealed record EvidenceSubgraphResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Finding identifier this subgraph explains.
|
||||
/// </summary>
|
||||
public required string FindingId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Vulnerability identifier (CVE ID or similar).
|
||||
/// </summary>
|
||||
public required string VulnId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Root node of the evidence graph (typically the artifact).
|
||||
/// </summary>
|
||||
public required EvidenceNode Root { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// All edges in the evidence graph.
|
||||
/// </summary>
|
||||
public required IReadOnlyList<EvidenceEdge> Edges { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Summary verdict for this finding.
|
||||
/// </summary>
|
||||
public required VerdictSummary Verdict { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Available triage actions for this finding.
|
||||
/// </summary>
|
||||
public required IReadOnlyList<TriageAction> AvailableActions { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional metadata about the evidence collection.
|
||||
/// </summary>
|
||||
public EvidenceMetadata? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Node in the evidence graph.
|
||||
/// </summary>
|
||||
public sealed record EvidenceNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for this node.
|
||||
/// </summary>
|
||||
public required string Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of evidence this node represents.
|
||||
/// </summary>
|
||||
public required EvidenceNodeType Type { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable label for display.
|
||||
/// </summary>
|
||||
public required string Label { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional longer description.
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Type-specific metadata.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object>? Metadata { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Child nodes (for expandable tree view).
|
||||
/// </summary>
|
||||
public IReadOnlyList<EvidenceNode>? Children { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this node is expandable (has or can load children).
|
||||
/// </summary>
|
||||
public bool IsExpandable { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Status indicator for this node (pass/fail/info).
|
||||
/// </summary>
|
||||
public EvidenceNodeStatus Status { get; init; } = EvidenceNodeStatus.Info;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of evidence node.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum EvidenceNodeType
|
||||
{
|
||||
/// <summary>Container image or artifact.</summary>
|
||||
Artifact,
|
||||
|
||||
/// <summary>Software package (PURL).</summary>
|
||||
Package,
|
||||
|
||||
/// <summary>Code symbol (function, class, method).</summary>
|
||||
Symbol,
|
||||
|
||||
/// <summary>Call path from entry point to vulnerable code.</summary>
|
||||
CallPath,
|
||||
|
||||
/// <summary>VEX claim from vendor or internal team.</summary>
|
||||
VexClaim,
|
||||
|
||||
/// <summary>Policy rule that affected the verdict.</summary>
|
||||
PolicyRule,
|
||||
|
||||
/// <summary>External advisory source (NVD, vendor, etc.).</summary>
|
||||
AdvisorySource,
|
||||
|
||||
/// <summary>Scanner evidence (binary analysis, SBOM, etc.).</summary>
|
||||
ScannerEvidence,
|
||||
|
||||
/// <summary>Runtime observation (eBPF, traces).</summary>
|
||||
RuntimeObservation,
|
||||
|
||||
/// <summary>Configuration or environment context.</summary>
|
||||
Configuration,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status indicator for evidence nodes.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum EvidenceNodeStatus
|
||||
{
|
||||
/// <summary>Informational, no pass/fail.</summary>
|
||||
Info,
|
||||
|
||||
/// <summary>Positive indicator (mitigating factor).</summary>
|
||||
Pass,
|
||||
|
||||
/// <summary>Negative indicator (risk factor).</summary>
|
||||
Fail,
|
||||
|
||||
/// <summary>Needs review or additional information.</summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>Unknown or incomplete data.</summary>
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edge connecting two evidence nodes.
|
||||
/// </summary>
|
||||
public sealed record EvidenceEdge
|
||||
{
|
||||
/// <summary>
|
||||
/// Source node identifier.
|
||||
/// </summary>
|
||||
public required string SourceId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Target node identifier.
|
||||
/// </summary>
|
||||
public required string TargetId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of relationship (contains, calls, claims, references, etc.).
|
||||
/// </summary>
|
||||
public required string Relationship { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Citation linking to source evidence.
|
||||
/// </summary>
|
||||
public required EvidenceCitation Citation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this edge represents a reachable path.
|
||||
/// </summary>
|
||||
public bool IsReachable { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Strength of the relationship (for visualization).
|
||||
/// </summary>
|
||||
public double? Weight { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Citation linking to source evidence.
|
||||
/// </summary>
|
||||
public sealed record EvidenceCitation
|
||||
{
|
||||
/// <summary>
|
||||
/// Source type (scanner, vex:vendor, advisory:nvd, etc.).
|
||||
/// </summary>
|
||||
public required string Source { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// URL to the source evidence (if available).
|
||||
/// </summary>
|
||||
public required string SourceUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When this evidence was observed/collected.
|
||||
/// </summary>
|
||||
public required DateTimeOffset ObservedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Confidence score (0.0-1.0) if applicable.
|
||||
/// </summary>
|
||||
public double? Confidence { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Hash of the evidence (for verification).
|
||||
/// </summary>
|
||||
public string? EvidenceHash { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this citation is verified/signed.
|
||||
/// </summary>
|
||||
public bool IsVerified { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary verdict for a finding.
|
||||
/// </summary>
|
||||
public sealed record VerdictSummary
|
||||
{
|
||||
/// <summary>
|
||||
/// Decision outcome (allow, deny, review).
|
||||
/// </summary>
|
||||
public required string Decision { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable explanation paragraph.
|
||||
/// </summary>
|
||||
public required string Explanation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Key factors that influenced the decision.
|
||||
/// </summary>
|
||||
public required IReadOnlyList<string> KeyFactors { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Overall confidence score (0.0-1.0).
|
||||
/// </summary>
|
||||
public required double ConfidenceScore { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Policy rule IDs that affected this verdict.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string>? AppliedPolicies { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp when this verdict was computed.
|
||||
/// </summary>
|
||||
public DateTimeOffset? ComputedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Available triage action.
|
||||
/// </summary>
|
||||
public sealed record TriageAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique action identifier.
|
||||
/// </summary>
|
||||
public required string ActionId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of action.
|
||||
/// </summary>
|
||||
public required TriageActionType Type { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Display label.
|
||||
/// </summary>
|
||||
public required string Label { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional description of what this action does.
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this action requires confirmation dialog.
|
||||
/// </summary>
|
||||
public bool RequiresConfirmation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this action is currently available.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Reason if the action is disabled.
|
||||
/// </summary>
|
||||
public string? DisabledReason { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional parameters for the action.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object>? Parameters { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of triage action.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum TriageActionType
|
||||
{
|
||||
/// <summary>Accept vendor's VEX claim.</summary>
|
||||
AcceptVendorVex,
|
||||
|
||||
/// <summary>Request additional evidence.</summary>
|
||||
RequestEvidence,
|
||||
|
||||
/// <summary>Open diff view to previous version.</summary>
|
||||
OpenDiff,
|
||||
|
||||
/// <summary>Create time-boxed policy exception.</summary>
|
||||
CreateException,
|
||||
|
||||
/// <summary>Mark as false positive.</summary>
|
||||
MarkFalsePositive,
|
||||
|
||||
/// <summary>Escalate to security team.</summary>
|
||||
EscalateToSecurityTeam,
|
||||
|
||||
/// <summary>Apply internal VEX claim.</summary>
|
||||
ApplyInternalVex,
|
||||
|
||||
/// <summary>Schedule for patching.</summary>
|
||||
SchedulePatch,
|
||||
|
||||
/// <summary>Suppress finding.</summary>
|
||||
Suppress,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metadata about evidence collection.
|
||||
/// </summary>
|
||||
public sealed record EvidenceMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// When the evidence was collected.
|
||||
/// </summary>
|
||||
public DateTimeOffset CollectedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of nodes in the graph.
|
||||
/// </summary>
|
||||
public int NodeCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of edges in the graph.
|
||||
/// </summary>
|
||||
public int EdgeCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the graph is complete or truncated.
|
||||
/// </summary>
|
||||
public bool IsTruncated { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum depth of the tree.
|
||||
/// </summary>
|
||||
public int MaxDepth { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Sources of evidence included.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string>? Sources { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to execute a triage action.
|
||||
/// </summary>
|
||||
public sealed record ExecuteTriageActionRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Finding to apply action to.
|
||||
/// </summary>
|
||||
public required string FindingId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Action to execute.
|
||||
/// </summary>
|
||||
public required string ActionId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional parameters for the action.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object>? Parameters { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// User comment/justification.
|
||||
/// </summary>
|
||||
public string? Comment { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response from triage action execution.
|
||||
/// </summary>
|
||||
public sealed record ExecuteTriageActionResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the action succeeded.
|
||||
/// </summary>
|
||||
public required bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Result message.
|
||||
/// </summary>
|
||||
public required string Message { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Updated verdict after action (if changed).
|
||||
/// </summary>
|
||||
public VerdictSummary? UpdatedVerdict { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Next recommended action (if any).
|
||||
/// </summary>
|
||||
public TriageAction? NextAction { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters for finding triage.
|
||||
/// </summary>
|
||||
public sealed record TriageFilters
|
||||
{
|
||||
/// <summary>
|
||||
/// Reachability filter.
|
||||
/// </summary>
|
||||
public ReachabilityFilter Reachability { get; init; } = ReachabilityFilter.Reachable;
|
||||
|
||||
/// <summary>
|
||||
/// Patch status filter.
|
||||
/// </summary>
|
||||
public PatchStatusFilter PatchStatus { get; init; } = PatchStatusFilter.Unpatched;
|
||||
|
||||
/// <summary>
|
||||
/// VEX status filter.
|
||||
/// </summary>
|
||||
public VexStatusFilter VexStatus { get; init; } = VexStatusFilter.Unvexed;
|
||||
|
||||
/// <summary>
|
||||
/// Severity levels to include.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Severity { get; init; } = new[] { "critical", "high" };
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show suppressed findings.
|
||||
/// </summary>
|
||||
public bool ShowSuppressed { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reachability filter options.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum ReachabilityFilter
|
||||
{
|
||||
/// <summary>Show all findings.</summary>
|
||||
All,
|
||||
|
||||
/// <summary>Show only reachable findings.</summary>
|
||||
Reachable,
|
||||
|
||||
/// <summary>Show only unreachable findings.</summary>
|
||||
Unreachable,
|
||||
|
||||
/// <summary>Show findings with unknown reachability.</summary>
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch status filter options.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum PatchStatusFilter
|
||||
{
|
||||
/// <summary>Show all findings.</summary>
|
||||
All,
|
||||
|
||||
/// <summary>Show only findings with available patches.</summary>
|
||||
Patched,
|
||||
|
||||
/// <summary>Show only findings without available patches.</summary>
|
||||
Unpatched,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEX status filter options.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum VexStatusFilter
|
||||
{
|
||||
/// <summary>Show all findings.</summary>
|
||||
All,
|
||||
|
||||
/// <summary>Show only findings with VEX claims.</summary>
|
||||
Vexed,
|
||||
|
||||
/// <summary>Show only findings without VEX claims.</summary>
|
||||
Unvexed,
|
||||
|
||||
/// <summary>Show findings with conflicting VEX claims.</summary>
|
||||
Conflicting,
|
||||
}
|
||||
29
src/VulnExplorer/StellaOps.VulnExplorer.sln
Normal file
29
src/VulnExplorer/StellaOps.VulnExplorer.sln
Normal file
@@ -0,0 +1,29 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.VulnExplorer.Api", "StellaOps.VulnExplorer.Api", "{92C3A1D8-A193-9878-1FED-5EFEEF0CDA41}"
|
||||
|
||||
EndProject
|
||||
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.VulnExplorer.Api", "StellaOps.VulnExplorer.Api\StellaOps.VulnExplorer.Api.csproj", "{5F45C323-0BA3-BA55-32DA-7B193CBB8632}"
|
||||
|
||||
EndProject
|
||||
|
||||
Global
|
||||
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
||||
Release|Any CPU = Release|Any CPU
|
||||
|
||||
EndGlobalSection
|
||||
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
|
||||
{5F45C323-0BA3-BA55-32DA-7B193CBB8632}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
{5F45C323-0BA3-BA55-32DA-7B193CBB8632}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
||||
Reference in New Issue
Block a user