consolidation of some of the modules, localization fixes, product advisories work, qa work

This commit is contained in:
master
2026-03-05 03:54:22 +02:00
parent 7bafcc3eef
commit 8e1cb9448d
3878 changed files with 72600 additions and 46861 deletions

View File

@@ -100,6 +100,12 @@ public static class GovernanceEndpoints
.WithName("ValidateRiskProfile")
.WithDescription("Validate a candidate risk profile configuration without persisting it. Checks for required fields (name, at least one signal) and emits warnings when signal weights do not sum to 1.0. Used by policy authoring tools to provide inline validation feedback before profile creation.");
// Risk Budget endpoints
governance.MapGet("/risk-budget/dashboard", GetRiskBudgetDashboardAsync)
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyRead))
.WithName("GetRiskBudgetDashboard")
.WithDescription("Retrieve the current risk budget dashboard including utilization, headroom, top contributors, active alerts, and KPIs for the tenant.");
// Audit endpoints
governance.MapGet("/audit/events", GetAuditEventsAsync)
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyAudit))
@@ -539,6 +545,117 @@ public static class GovernanceEndpoints
return Task.FromResult(Results.Ok(response));
}
// ========================================================================
// Risk Budget Handlers
// ========================================================================
private static Task<IResult> GetRiskBudgetDashboardAsync(
HttpContext httpContext,
[FromServices] TimeProvider timeProvider,
[FromQuery] string? tenantId,
[FromQuery] string? projectId)
{
var tenant = tenantId ?? GetTenantId(httpContext) ?? "default";
var now = timeProvider.GetUtcNow();
var traceId = httpContext.TraceIdentifier;
var periodStart = new DateTimeOffset(now.Year, ((now.Month - 1) / 3) * 3 + 1, 1, 0, 0, 0, TimeSpan.Zero);
var periodEnd = periodStart.AddMonths(3).AddSeconds(-1);
var response = new
{
config = new
{
id = "budget-001",
tenantId = tenant,
projectId = projectId ?? (string?)null,
name = $"Q{(now.Month - 1) / 3 + 1} {now.Year} Risk Budget",
totalBudget = 1000,
warningThreshold = 70,
criticalThreshold = 90,
period = "quarterly",
periodStart = periodStart.ToString("O", CultureInfo.InvariantCulture),
periodEnd = periodEnd.ToString("O", CultureInfo.InvariantCulture),
createdAt = periodStart.AddDays(-15).ToString("O", CultureInfo.InvariantCulture),
updatedAt = now.ToString("O", CultureInfo.InvariantCulture)
},
currentRiskPoints = 720,
headroom = 280,
utilizationPercent = 72.0,
status = "warning",
timeSeries = Enumerable.Range(0, 5).Select(i =>
{
var ts = now.AddDays(-28 + i * 7);
var actual = 600 + i * 30;
return new
{
timestamp = ts.ToString("O", CultureInfo.InvariantCulture),
actual,
budget = 1000,
headroom = 1000 - actual
};
}).ToList(),
updatedAt = now.ToString("O", CultureInfo.InvariantCulture),
traceId,
topContributors = new[]
{
new { identifier = "pkg:npm/lodash@4.17.20", type = "component", displayName = "lodash", riskPoints = 120, percentOfBudget = 12.0, trend = "stable", delta24h = 0 },
new { identifier = "CVE-2024-1234", type = "vulnerability", displayName = "CVE-2024-1234", riskPoints = 95, percentOfBudget = 9.5, trend = "increasing", delta24h = 10 },
new { identifier = "vulnerability", type = "category", displayName = "Vulnerabilities", riskPoints = 450, percentOfBudget = 45.0, trend = "stable", delta24h = 5 }
},
activeAlerts = new[]
{
new
{
id = "alert-001",
threshold = new { level = 70, severity = "medium", actions = new[] { new { type = "notify", channels = new[] { "slack" } } } },
currentUtilization = 72.0,
triggeredAt = now.AddDays(-1).ToString("O", CultureInfo.InvariantCulture),
acknowledged = false,
acknowledgedBy = (string?)null,
acknowledgedAt = (string?)null
}
},
governance = new
{
id = "budget-001",
tenantId = tenant,
name = $"Q{(now.Month - 1) / 3 + 1} {now.Year} Risk Budget",
totalBudget = 1000,
warningThreshold = 70,
criticalThreshold = 90,
period = "quarterly",
periodStart = periodStart.ToString("O", CultureInfo.InvariantCulture),
periodEnd = periodEnd.ToString("O", CultureInfo.InvariantCulture),
createdAt = periodStart.AddDays(-15).ToString("O", CultureInfo.InvariantCulture),
updatedAt = now.ToString("O", CultureInfo.InvariantCulture),
thresholds = new[]
{
new { level = 70, severity = "medium", actions = new object[] { new { type = "notify", channels = new[] { "slack", "email" } } } },
new { level = 90, severity = "high", actions = new object[] { new { type = "notify", channels = new[] { "slack", "email" } }, new { type = "require_approval" } } },
new { level = 100, severity = "critical", actions = new object[] { new { type = "block_deploys" }, new { type = "escalate" } } }
},
enforceHardLimits = true,
gracePeriodHours = 24,
autoReset = true,
carryoverPercent = 0
},
kpis = new
{
headroom = 280,
headroomDelta24h = -20,
unknownsDelta24h = 3,
riskRetired7d = 45,
exceptionsExpiring = 2,
burnRate = 8.5,
projectedDaysToExceeded = 33,
traceId
}
};
return Task.FromResult(Results.Ok(response));
}
// ========================================================================
// Audit Handlers
// ========================================================================

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@
<ItemGroup>
<ProjectReference Include="../StellaOps.Policy/StellaOps.Policy.csproj" />
<ProjectReference Include="../../../RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Core/StellaOps.RiskEngine.Core.csproj" />
<ProjectReference Include="../../../Findings/StellaOps.RiskEngine.Core/StellaOps.RiskEngine.Core.csproj" />
<ProjectReference Include="../../../BinaryIndex/__Libraries/StellaOps.BinaryIndex.GoldenSet/StellaOps.BinaryIndex.GoldenSet.csproj" />
</ItemGroup>
</Project>

View File

@@ -4,12 +4,21 @@
"title": "StellaOps Score Policy v1",
"description": "Defines deterministic vulnerability scoring weights, buckets, and overrides",
"type": "object",
"required": ["policyVersion", "weightsBps"],
"required": ["policyVersion", "policyId", "weightsBps"],
"properties": {
"policyVersion": {
"const": "score.v1",
"description": "Policy schema version"
},
"policyId": {
"type": "string",
"minLength": 1,
"description": "Deterministic score policy identifier"
},
"scoringProfile": {
"type": "string",
"description": "Scoring profile selector"
},
"weightsBps": {
"type": "object",
"description": "Weight distribution in basis points (must sum to 10000)",

View File

@@ -14,6 +14,7 @@ public sealed class ScorePolicyLoader
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();
private static readonly ScorePolicyValidator Validator = new();
/// <summary>
/// Loads a score policy from a YAML file.
@@ -56,6 +57,21 @@ public sealed class ScorePolicyLoader
throw new ScorePolicyLoadException(
$"Unsupported policy version '{policy.PolicyVersion}' in {source}. Expected 'score.v1'");
if (string.IsNullOrWhiteSpace(policy.PolicyId))
{
throw new ScorePolicyLoadException($"Missing required field 'policyId' in {source}");
}
var validation = Validator.Validate(policy);
if (!validation.IsValid)
{
var details = validation.Errors.Count > 0
? string.Join("; ", validation.Errors)
: "schema validation returned no detailed errors";
throw new ScorePolicyLoadException(
$"Schema validation failed in {source}: {details}");
}
// Validate weight sum
if (!policy.ValidateWeights())
{

View File

@@ -6,6 +6,7 @@ namespace StellaOps.Policy.Scoring;
public sealed record ScorePolicy
{
public required string PolicyVersion { get; init; }
public required string PolicyId { get; init; }
/// <summary>
/// Scoring profile to use. Defaults to "advanced".
@@ -35,6 +36,7 @@ public sealed record ScorePolicy
public static ScorePolicy Default => new()
{
PolicyVersion = "score.v1",
PolicyId = "score-policy.default.v1",
ScoringProfile = "advanced",
WeightsBps = WeightsBps.Default,
Reachability = ReachabilityPolicyConfig.Default,

View File

@@ -0,0 +1,28 @@
using System.Reflection;
using System.Text;
namespace StellaOps.Policy.Scoring;
public static class ScorePolicySchemaResource
{
private const string SchemaResourceName = "StellaOps.Policy.Schemas.score-policy.v1.schema.json";
public static Stream OpenSchemaStream()
{
var assembly = Assembly.GetExecutingAssembly();
var stream = assembly.GetManifestResourceStream(SchemaResourceName);
if (stream is null)
{
throw new InvalidOperationException($"Unable to locate embedded schema resource '{SchemaResourceName}'.");
}
return stream;
}
public static string ReadSchemaJson()
{
using var stream = OpenSchemaStream();
using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
return reader.ReadToEnd();
}
}

View File

@@ -7,6 +7,7 @@
using Json.Schema;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace StellaOps.Policy.Scoring;
@@ -22,7 +23,7 @@ public sealed class ScorePolicyValidator
/// </summary>
public ScorePolicyValidator()
{
_schema = JsonSchema.FromText(ScorePolicySchemaJson);
_schema = JsonSchema.FromText(ScorePolicySchemaResource.ReadSchemaJson());
}
/// <summary>
@@ -113,179 +114,9 @@ public sealed class ScorePolicyValidator
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false
};
/// <summary>
/// Embedded JSON Schema for score.v1 policies.
/// </summary>
private const string ScorePolicySchemaJson = """
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stellaops.dev/schemas/score-policy.v1.json",
"title": "Score Policy",
"type": "object",
"required": ["policyVersion", "policyId", "weightsBps"],
"properties": {
"policyVersion": {
"type": "string",
"const": "score.v1"
},
"policyId": {
"type": "string",
"minLength": 1
},
"policyName": {
"type": "string"
},
"description": {
"type": "string"
},
"weightsBps": {
"$ref": "#/$defs/WeightsBps"
},
"reachabilityConfig": {
"$ref": "#/$defs/ReachabilityConfig"
},
"evidenceConfig": {
"$ref": "#/$defs/EvidenceConfig"
},
"provenanceConfig": {
"$ref": "#/$defs/ProvenanceConfig"
},
"overrides": {
"type": "array",
"items": {
"$ref": "#/$defs/ScoreOverride"
}
}
},
"$defs": {
"WeightsBps": {
"type": "object",
"required": ["baseSeverity", "reachability", "evidence", "provenance"],
"properties": {
"baseSeverity": {
"type": "integer",
"minimum": 0,
"maximum": 10000
},
"reachability": {
"type": "integer",
"minimum": 0,
"maximum": 10000
},
"evidence": {
"type": "integer",
"minimum": 0,
"maximum": 10000
},
"provenance": {
"type": "integer",
"minimum": 0,
"maximum": 10000
}
}
},
"ReachabilityConfig": {
"type": "object",
"properties": {
"reachableMultiplier": {
"type": "number",
"minimum": 0,
"maximum": 2
},
"unreachableMultiplier": {
"type": "number",
"minimum": 0,
"maximum": 2
},
"unknownMultiplier": {
"type": "number",
"minimum": 0,
"maximum": 2
}
}
},
"EvidenceConfig": {
"type": "object",
"properties": {
"kevWeight": {
"type": "number",
"minimum": 0
},
"epssThreshold": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"epssWeight": {
"type": "number",
"minimum": 0
}
}
},
"ProvenanceConfig": {
"type": "object",
"properties": {
"signedBonus": {
"type": "number"
},
"rekorVerifiedBonus": {
"type": "number"
},
"unsignedPenalty": {
"type": "number"
}
}
},
"ScoreOverride": {
"type": "object",
"required": ["id", "match"],
"properties": {
"id": {
"type": "string"
},
"match": {
"type": "object",
"properties": {
"cvePattern": {
"type": "string"
},
"purlPattern": {
"type": "string"
},
"severityEquals": {
"type": "string"
}
}
},
"action": {
"type": "object",
"properties": {
"setScore": {
"type": "number"
},
"addScore": {
"type": "number"
},
"multiplyScore": {
"type": "number"
}
}
},
"reason": {
"type": "string"
},
"expires": {
"type": "string",
"format": "date-time"
}
}
}
}
}
""";
}
/// <summary>

View File

@@ -19,6 +19,7 @@
<EmbeddedResource Include="Schemas\policy-schema@1.json" />
<EmbeddedResource Include="Schemas\policy-scoring-default.json" />
<EmbeddedResource Include="Schemas\policy-scoring-schema@1.json" />
<EmbeddedResource Include="Schemas\score-policy.v1.schema.json" />
<EmbeddedResource Include="Schemas\signals-schema@1.json" />
<EmbeddedResource Include="Schemas\spl-schema@1.json" />
<EmbeddedResource Include="Schemas\spl-sample@1.json" />

View File

@@ -2,6 +2,7 @@ using System.Net;
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -9,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Auth.Abstractions;
using StellaOps.Policy.Engine.Attestation;
using StellaOps.TestKit.Fixtures;
using Xunit;
@@ -88,6 +90,10 @@ public sealed class PolicyEngineWebServiceFixture : WebServiceFixture<StellaOps.
private static void ConfigureTestServices(IServiceCollection services)
{
services.RemoveAll<IHostedService>();
services.RemoveAll<IConfigureOptions<AuthenticationOptions>>();
services.RemoveAll<IPostConfigureOptions<AuthenticationOptions>>();
services.RemoveAll<IConfigureOptions<JwtBearerOptions>>();
services.RemoveAll<IPostConfigureOptions<JwtBearerOptions>>();
services.AddAuthentication(options =>
{
@@ -96,6 +102,9 @@ public sealed class PolicyEngineWebServiceFixture : WebServiceFixture<StellaOps.
})
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
TestAuthHandler.SchemeName,
_ => { })
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
StellaOpsAuthenticationDefaults.AuthenticationScheme,
_ => { });
}
@@ -105,6 +114,8 @@ public sealed class PolicyEngineWebServiceFixture : WebServiceFixture<StellaOps.
{
var settings = new Dictionary<string, string?>
{
["PolicyEngine:ResourceServer:Authority"] = "http://127.0.0.1:59999",
["PolicyEngine:ResourceServer:RequireHttpsMetadata"] = "false",
["VerdictAttestation:Enabled"] = "true",
["VerdictAttestation:FailOnError"] = "true",
["VerdictAttestation:RekorEnabled"] = "true",
@@ -142,6 +153,7 @@ internal sealed class TestAuthHandler : AuthenticationHandler<AuthenticationSche
var claims = new[]
{
new Claim("scope", "policy:read"),
new Claim(StellaOpsClaimTypes.Tenant, "test-tenant"),
new Claim("tenant_id", "test-tenant"),
new Claim(ClaimTypes.NameIdentifier, "test-user")
};
@@ -153,4 +165,3 @@ internal sealed class TestAuthHandler : AuthenticationHandler<AuthenticationSche
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}

View File

@@ -193,6 +193,7 @@ public sealed class ScorePolicyServiceCachingTests
var policy1 = new ScorePolicy
{
PolicyVersion = "score.v1",
PolicyId = "policy-1",
ScoringProfile = "advanced",
WeightsBps = new WeightsBps
{
@@ -206,6 +207,7 @@ public sealed class ScorePolicyServiceCachingTests
var policy2 = new ScorePolicy
{
PolicyVersion = "score.v1",
PolicyId = "policy-1",
ScoringProfile = "advanced",
WeightsBps = new WeightsBps
{
@@ -225,6 +227,7 @@ public sealed class ScorePolicyServiceCachingTests
private static ScorePolicy CreateTestPolicy(string id) => new()
{
PolicyVersion = "score.v1",
PolicyId = id,
ScoringProfile = "advanced",
WeightsBps = new WeightsBps
{

View File

@@ -0,0 +1,71 @@
{
"verdictId": "VERDICT-2025-005",
"policyId": "POL-COMPLEX-001",
"policyName": "Complex Multi-Rule Policy",
"policyVersion": "3.0.0",
"tenantId": "TENANT-003",
"evaluatedAt": "2025-12-24T12:00:00+00:00",
"digestEvaluated": "sha256:complex123",
"outcome": 1,
"rulesMatched": 5,
"rulesTotal": 8,
"violations": [],
"warnings": [
{
"ruleName": "warn_eol_runtime",
"severity": "medium",
"message": "Runtime marked as EOL; upgrade recommended",
"packagePurl": "pkg:deb/debian/python3.9@3.9.2"
},
{
"ruleName": "warn_ruby_git_sources",
"severity": "low",
"message": "Git-sourced Ruby gem present; review required",
"packagePurl": "pkg:gem/custom-gem@1.0.0"
}
],
"matchedRules": [
{
"ruleName": "block_critical",
"priority": 5,
"status": 0,
"reason": "No critical vulnerabilities"
},
{
"ruleName": "escalate_high_internet",
"priority": 4,
"status": 0,
"reason": "No high severity on internet-exposed assets"
},
{
"ruleName": "require_vex_justification",
"priority": 3,
"status": 1,
"reason": "VEX statement accepted"
},
{
"ruleName": "warn_eol_runtime",
"priority": 1,
"status": 3,
"reason": "EOL runtime detected"
},
{
"ruleName": "warn_ruby_git_sources",
"priority": 1,
"status": 3,
"reason": "Git-sourced gem detected"
}
],
"unknownsBudgetResult": null,
"vexMergeTrace": null,
"scoreResult": null,
"metadata": {
"evaluationDurationMs": 123,
"feedVersions": {
"nvd": "2025-12-24",
"ghsa": "2025-12-24",
"osv": "2025-12-24"
},
"policyChecksum": "sha256:policy789"
}
}

View File

@@ -0,0 +1,25 @@
{
"verdictId": "VERDICT-2025-006",
"policyId": "POL-PROD-001",
"policyName": "Production Baseline Policy",
"policyVersion": "1.2.3",
"tenantId": "TENANT-001",
"evaluatedAt": "2025-12-24T12:00:00+00:00",
"digestEvaluated": "sha256:empty123",
"outcome": 0,
"rulesMatched": 0,
"rulesTotal": 5,
"violations": [],
"warnings": [],
"matchedRules": [],
"unknownsBudgetResult": null,
"vexMergeTrace": null,
"scoreResult": null,
"metadata": {
"evaluationDurationMs": 12,
"feedVersions": {
"nvd": "2025-12-24"
},
"policyChecksum": "sha256:policy123"
}
}

View File

@@ -0,0 +1,50 @@
{
"verdictId": "VERDICT-2025-002",
"policyId": "POL-PROD-001",
"policyName": "Production Baseline Policy",
"policyVersion": "1.2.3",
"tenantId": "TENANT-001",
"evaluatedAt": "2025-12-24T12:00:00+00:00",
"digestEvaluated": "sha256:abc123def456",
"outcome": 2,
"rulesMatched": 3,
"rulesTotal": 5,
"violations": [
{
"ruleName": "block_critical",
"severity": "critical",
"message": "Critical vulnerability CVE-2024-0001 found",
"vulnerabilityId": "CVE-2024-0001",
"packagePurl": "pkg:npm/lodash@4.17.20",
"remediation": "Upgrade to lodash@4.17.21"
},
{
"ruleName": "block_critical",
"severity": "critical",
"message": "Critical vulnerability CVE-2024-0002 found",
"vulnerabilityId": "CVE-2024-0002",
"packagePurl": "pkg:npm/express@4.18.0",
"remediation": "Upgrade to express@4.19.0"
}
],
"warnings": [],
"matchedRules": [
{
"ruleName": "block_critical",
"priority": 5,
"status": 2,
"reason": "2 critical vulnerabilities found"
}
],
"unknownsBudgetResult": null,
"vexMergeTrace": null,
"scoreResult": null,
"metadata": {
"evaluationDurationMs": 67,
"feedVersions": {
"nvd": "2025-12-24",
"ghsa": "2025-12-24"
},
"policyChecksum": "sha256:policy123"
}
}

View File

@@ -0,0 +1,90 @@
{
"traceId": "TRACE-2025-002",
"policyId": "POL-COMPLEX-001",
"policyName": "Complex Multi-Rule Policy",
"evaluationContext": {
"digestEvaluated": "sha256:xyz789",
"tenantId": "TENANT-002",
"environment": "staging",
"exposure": "internal"
},
"startedAt": "2025-12-24T12:00:00+00:00",
"completedAt": "2025-12-24T12:00:00.089+00:00",
"durationMs": 89,
"outcome": "Warn",
"steps": [
{
"stepNumber": 1,
"ruleName": "block_critical",
"priority": 5,
"phase": 3,
"condition": "severity.normalized \u003E= \u0022Critical\u0022",
"conditionResult": false,
"action": null,
"explanation": "No critical vulnerabilities",
"durationMs": 10,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": null
},
{
"stepNumber": 2,
"ruleName": "escalate_high_internet",
"priority": 4,
"phase": 3,
"condition": "severity.normalized == \u0022High\u0022 and env.exposure == \u0022internet\u0022",
"conditionResult": false,
"action": null,
"explanation": "Not internet-exposed, skipping escalation",
"durationMs": 8,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": null
},
{
"stepNumber": 3,
"ruleName": "block_ruby_dev",
"priority": 4,
"phase": 3,
"condition": "sbom.any_component(ruby.group(\u0022development\u0022))",
"conditionResult": false,
"action": null,
"explanation": "No development-only Ruby gems",
"durationMs": 12,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": null
},
{
"stepNumber": 4,
"ruleName": "require_vex_justification",
"priority": 3,
"phase": 3,
"condition": "vex.any(status in [\u0022not_affected\u0022,\u0022fixed\u0022])",
"conditionResult": true,
"action": "status := vex.status",
"explanation": "VEX statement found: not_affected (component_not_present)",
"durationMs": 25,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": null
},
{
"stepNumber": 5,
"ruleName": "warn_eol_runtime",
"priority": 1,
"phase": 3,
"condition": "severity.normalized \u003C= \u0022Medium\u0022 and sbom.has_tag(\u0022runtime:eol\u0022)",
"conditionResult": true,
"action": "warn message \u0022Runtime marked as EOL; upgrade recommended.\u0022",
"explanation": "EOL runtime detected: python3.9",
"durationMs": 15,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": null
}
],
"finalStatus": "warning",
"matchedRuleCount": 2,
"totalRuleCount": 5
}

View File

@@ -0,0 +1,39 @@
{
"verdictId": "VERDICT-2025-001",
"policyId": "POL-PROD-001",
"policyName": "Production Baseline Policy",
"policyVersion": "1.2.3",
"tenantId": "TENANT-001",
"evaluatedAt": "2025-12-24T12:00:00+00:00",
"digestEvaluated": "sha256:abc123def456",
"outcome": 0,
"rulesMatched": 2,
"rulesTotal": 5,
"violations": [],
"warnings": [],
"matchedRules": [
{
"ruleName": "allow_low_severity",
"priority": 1,
"status": 1,
"reason": "Severity \u003C= Low, allowing"
},
{
"ruleName": "vex_not_affected",
"priority": 2,
"status": 1,
"reason": "VEX status is not_affected"
}
],
"unknownsBudgetResult": null,
"vexMergeTrace": null,
"scoreResult": null,
"metadata": {
"evaluationDurationMs": 42,
"feedVersions": {
"nvd": "2025-12-24",
"ghsa": "2025-12-24"
},
"policyChecksum": "sha256:policy123"
}
}

View File

@@ -15,6 +15,18 @@ public sealed class PolicyEvaluationTraceSnapshotTests
{
private static readonly DateTimeOffset FrozenTime = DateTimeOffset.Parse("2025-12-24T12:00:00Z");
[Fact]
public void SnapshotDirectory_ResolvesToSourceControlledSnapshotsFolder()
{
var snapshotsDirectory = SnapshotAssert.ResolveDefaultSnapshotsDirectory();
Directory.Exists(snapshotsDirectory).Should().BeTrue();
Path.GetFileName(snapshotsDirectory).Should().Be("Snapshots");
File.Exists(Path.Combine(snapshotsDirectory, "PolicyEvaluationTraceSnapshotTests.cs"))
.Should()
.BeTrue("snapshot root should resolve to the source-controlled Snapshots folder");
}
/// <summary>
/// Verifies that a simple evaluation trace produces stable structure.
/// </summary>

View File

@@ -0,0 +1,58 @@
{
"traceId": "TRACE-2025-004",
"policyId": "POL-PROFILE-001",
"policyName": "Profile-Based Policy",
"evaluationContext": {
"digestEvaluated": "sha256:profile123",
"tenantId": "TENANT-003",
"environment": "production",
"exposure": "internet"
},
"startedAt": "2025-12-24T12:00:00+00:00",
"completedAt": "2025-12-24T12:00:00.055+00:00",
"durationMs": 55,
"outcome": "Pass",
"steps": [
{
"stepNumber": 1,
"ruleName": "profile_severity",
"priority": 100,
"phase": 0,
"condition": "profile.severity.enabled",
"conditionResult": true,
"action": null,
"explanation": "Applying severity profile adjustments",
"durationMs": 18,
"vexMergeDetail": null,
"profileApplicationDetail": {
"profileName": "severity",
"adjustments": [
{
"finding": "CVE-2024-0001",
"originalSeverity": "High",
"adjustedSeverity": "Critical",
"reason": "GHSA source weight \u002B0.5, internet exposure \u002B0.5"
}
]
},
"escalationDetail": null
},
{
"stepNumber": 2,
"ruleName": "block_critical",
"priority": 5,
"phase": 3,
"condition": "severity.normalized \u003E= \u0022Critical\u0022",
"conditionResult": false,
"action": null,
"explanation": "Post-profile: no critical vulnerabilities (VEX override applied)",
"durationMs": 10,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": null
}
],
"finalStatus": "allowed",
"matchedRuleCount": 1,
"totalRuleCount": 2
}

View File

@@ -0,0 +1,53 @@
{
"traceId": "TRACE-2025-005",
"policyId": "POL-ESCALATE-001",
"policyName": "Escalation Policy",
"evaluationContext": {
"digestEvaluated": "sha256:escalate123",
"tenantId": "TENANT-001",
"environment": "production",
"exposure": "internet"
},
"startedAt": "2025-12-24T12:00:00+00:00",
"completedAt": "2025-12-24T12:00:00.045+00:00",
"durationMs": 45,
"outcome": "Fail",
"steps": [
{
"stepNumber": 1,
"ruleName": "escalate_high_internet",
"priority": 4,
"phase": 2,
"condition": "severity.normalized == \u0022High\u0022 and env.exposure == \u0022internet\u0022",
"conditionResult": true,
"action": "escalate to severity_band(\u0022Critical\u0022)",
"explanation": "High severity on internet-exposed asset escalated to Critical",
"durationMs": 15,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": {
"finding": "CVE-2024-0001",
"originalSeverity": "High",
"escalatedSeverity": "Critical",
"reason": "Internet exposure triggers escalation per policy rule"
}
},
{
"stepNumber": 2,
"ruleName": "block_critical",
"priority": 5,
"phase": 3,
"condition": "severity.normalized \u003E= \u0022Critical\u0022",
"conditionResult": true,
"action": "status := \u0022blocked\u0022",
"explanation": "Critical severity (post-escalation) triggers block",
"durationMs": 10,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": null
}
],
"finalStatus": "blocked",
"matchedRuleCount": 2,
"totalRuleCount": 2
}

View File

@@ -0,0 +1,48 @@
{
"traceId": "TRACE-2025-001",
"policyId": "POL-PROD-001",
"policyName": "Production Baseline Policy",
"evaluationContext": {
"digestEvaluated": "sha256:abc123def456",
"tenantId": "TENANT-001",
"environment": "production",
"exposure": "internet"
},
"startedAt": "2025-12-24T12:00:00+00:00",
"completedAt": "2025-12-24T12:00:00.042+00:00",
"durationMs": 42,
"outcome": "Pass",
"steps": [
{
"stepNumber": 1,
"ruleName": "block_critical",
"priority": 5,
"phase": 3,
"condition": "severity.normalized \u003E= \u0022Critical\u0022",
"conditionResult": false,
"action": null,
"explanation": "No critical vulnerabilities found in scan",
"durationMs": 15,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": null
},
{
"stepNumber": 2,
"ruleName": "allow_low_severity",
"priority": 1,
"phase": 3,
"condition": "severity.normalized \u003C= \u0022Low\u0022",
"conditionResult": true,
"action": "status := \u0022allowed\u0022",
"explanation": "All findings are Low severity or below",
"durationMs": 12,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": null
}
],
"finalStatus": "allowed",
"matchedRuleCount": 1,
"totalRuleCount": 2
}

View File

@@ -0,0 +1,71 @@
{
"verdictId": "VERDICT-2025-007",
"policyId": "POL-SCORE-001",
"policyName": "EWS Score-Based Policy",
"policyVersion": "1.0.0",
"tenantId": "TENANT-001",
"evaluatedAt": "2025-12-24T12:00:00+00:00",
"digestEvaluated": "sha256:score123",
"outcome": 2,
"rulesMatched": 2,
"rulesTotal": 5,
"violations": [
{
"ruleName": "block_act_now",
"severity": "critical",
"message": "Score 92 in ActNow bucket requires immediate action",
"vulnerabilityId": "CVE-2024-0010",
"packagePurl": "pkg:npm/critical-pkg@1.0.0",
"remediation": "Upgrade to patched version immediately"
}
],
"warnings": [],
"matchedRules": [
{
"ruleName": "block_act_now",
"priority": 10,
"status": 2,
"reason": "score.is_act_now evaluated true (score=92)"
},
{
"ruleName": "score_threshold_80",
"priority": 8,
"status": 1,
"reason": "score \u003E= 80 threshold exceeded"
}
],
"unknownsBudgetResult": null,
"vexMergeTrace": null,
"scoreResult": {
"findingId": "FINDING-CVE-2024-0010",
"score": 92,
"bucket": "ActNow",
"inputs": {
"reachability": 0.95,
"runtime": 0.8,
"backport": 0.1,
"exploit": 0.9,
"sourceTrust": 0.7,
"mitigation": 0.05
},
"flags": [
"live-signal",
"public-exploit"
],
"explanations": [
"High reachability (0.95): function is in hot code path",
"Active exploit in the wild detected",
"No mitigation available"
],
"calculatedAt": "2025-12-24T12:00:00+00:00",
"policyDigest": "sha256:ews-policy-v1"
},
"metadata": {
"evaluationDurationMs": 78,
"feedVersions": {
"nvd": "2025-12-24",
"ghsa": "2025-12-24"
},
"policyChecksum": "sha256:score-policy-001"
}
}

View File

@@ -0,0 +1,73 @@
{
"verdictId": "VERDICT-2025-009",
"policyId": "POL-SCORE-002",
"policyName": "KEV-Aware Score Policy",
"policyVersion": "1.0.0",
"tenantId": "TENANT-002",
"evaluatedAt": "2025-12-24T12:00:00+00:00",
"digestEvaluated": "sha256:kev-score",
"outcome": 2,
"rulesMatched": 2,
"rulesTotal": 4,
"violations": [
{
"ruleName": "block_kev_flagged",
"severity": "critical",
"message": "KEV-listed vulnerability must be remediated immediately",
"vulnerabilityId": "CVE-2024-0030",
"packagePurl": "pkg:npm/vulnerable-pkg@1.0.0",
"remediation": "CISA KEV deadline: 2025-01-15"
}
],
"warnings": [],
"matchedRules": [
{
"ruleName": "block_kev_flagged",
"priority": 15,
"status": 2,
"reason": "score.has_flag(\u0022kev\u0022) evaluated true"
},
{
"ruleName": "escalate_act_now",
"priority": 10,
"status": 1,
"reason": "score.is_act_now with KEV flag"
}
],
"unknownsBudgetResult": null,
"vexMergeTrace": null,
"scoreResult": {
"findingId": "FINDING-CVE-2024-0030",
"score": 98,
"bucket": "ActNow",
"inputs": {
"reachability": 0.7,
"runtime": 0.9,
"backport": 0,
"exploit": 1,
"sourceTrust": 0.85,
"mitigation": 0
},
"flags": [
"kev",
"public-exploit",
"weaponized"
],
"explanations": [
"CISA KEV listed: actively exploited in the wild",
"Exploit complexity: Low",
"No backport available",
"No mitigation factors apply"
],
"calculatedAt": "2025-12-24T12:00:00+00:00",
"policyDigest": "sha256:kev-policy-v1"
},
"metadata": {
"evaluationDurationMs": 56,
"feedVersions": {
"nvd": "2025-12-24",
"kev": "2025-12-24"
},
"policyChecksum": "sha256:kev-policy-001"
}
}

View File

@@ -0,0 +1,52 @@
{
"verdictId": "VERDICT-2025-010",
"policyId": "POL-SCORE-001",
"policyName": "EWS Score-Based Policy",
"policyVersion": "1.0.0",
"tenantId": "TENANT-001",
"evaluatedAt": "2025-12-24T12:00:00+00:00",
"digestEvaluated": "sha256:low-score",
"outcome": 0,
"rulesMatched": 1,
"rulesTotal": 5,
"violations": [],
"warnings": [],
"matchedRules": [
{
"ruleName": "allow_low_score",
"priority": 1,
"status": 1,
"reason": "score \u003C 40 - acceptable risk level"
}
],
"unknownsBudgetResult": null,
"vexMergeTrace": null,
"scoreResult": {
"findingId": "FINDING-CVE-2024-0040",
"score": 25,
"bucket": "Watchlist",
"inputs": {
"reachability": 0.1,
"runtime": 0.2,
"backport": 0.9,
"exploit": 0.15,
"sourceTrust": 0.95,
"mitigation": 0.8
},
"flags": [],
"explanations": [
"Low reachability (0.1): function not in execution path",
"Backport available (0.9)",
"Strong mitigation factors (0.8)"
],
"calculatedAt": "2025-12-24T12:00:00+00:00",
"policyDigest": "sha256:ews-policy-v1"
},
"metadata": {
"evaluationDurationMs": 32,
"feedVersions": {
"nvd": "2025-12-24"
},
"policyChecksum": "sha256:score-policy-001"
}
}

View File

@@ -0,0 +1,60 @@
{
"verdictId": "VERDICT-2025-008",
"policyId": "POL-SCORE-001",
"policyName": "EWS Score-Based Policy",
"policyVersion": "1.0.0",
"tenantId": "TENANT-001",
"evaluatedAt": "2025-12-24T12:00:00+00:00",
"digestEvaluated": "sha256:score-violation",
"outcome": 2,
"rulesMatched": 1,
"rulesTotal": 3,
"violations": [
{
"ruleName": "block_high_exploit_reachable",
"severity": "high",
"message": "Reachable vulnerability with high exploit score blocked",
"vulnerabilityId": "CVE-2024-0020",
"packagePurl": "pkg:maven/org.example/lib@2.0.0",
"remediation": "Apply patch or configure WAF rules"
}
],
"warnings": [],
"matchedRules": [
{
"ruleName": "block_high_exploit_reachable",
"priority": 7,
"status": 2,
"reason": "score.rch \u003E 0.8 and score.xpl \u003E 0.7 condition met"
}
],
"unknownsBudgetResult": null,
"vexMergeTrace": null,
"scoreResult": {
"findingId": "FINDING-CVE-2024-0020",
"score": 75,
"bucket": "ScheduleNext",
"inputs": {
"reachability": 0.85,
"runtime": 0.6,
"backport": 0.3,
"exploit": 0.75,
"sourceTrust": 0.8,
"mitigation": 0.2
},
"flags": [],
"explanations": [
"High reachability (0.85): code path confirmed reachable",
"Exploit code available (0.75)"
],
"calculatedAt": "2025-12-24T12:00:00+00:00",
"policyDigest": "sha256:ews-policy-v1"
},
"metadata": {
"evaluationDurationMs": 45,
"feedVersions": {
"nvd": "2025-12-24"
},
"policyChecksum": "sha256:score-policy-001"
}
}

View File

@@ -0,0 +1,49 @@
{
"verdictId": "VERDICT-2025-003",
"policyId": "POL-STRICT-001",
"policyName": "Strict Unknown Budget Policy",
"policyVersion": "2.0.0",
"tenantId": "TENANT-002",
"evaluatedAt": "2025-12-24T12:00:00+00:00",
"digestEvaluated": "sha256:xyz789",
"outcome": 2,
"rulesMatched": 1,
"rulesTotal": 3,
"violations": [
{
"ruleName": "unknowns_budget",
"severity": "high",
"message": "Unknowns budget exceeded: 5 critical unknowns (max: 0)",
"vulnerabilityId": null,
"packagePurl": null,
"remediation": "Resolve unknown packages or request VEX statements"
}
],
"warnings": [],
"matchedRules": [
{
"ruleName": "unknowns_budget",
"priority": 10,
"status": 2,
"reason": "Critical unknowns (5) exceeds budget (0)"
}
],
"unknownsBudgetResult": {
"withinBudget": false,
"criticalCount": 5,
"highCount": 12,
"mediumCount": 45,
"lowCount": 120,
"totalCount": 182,
"action": "Block"
},
"vexMergeTrace": null,
"scoreResult": null,
"metadata": {
"evaluationDurationMs": 89,
"feedVersions": {
"nvd": "2025-12-24"
},
"policyChecksum": "sha256:policy456"
}
}

View File

@@ -0,0 +1,41 @@
{
"verdictId": "VERDICT-2025-004",
"policyId": "POL-PROD-001",
"policyName": "Production Baseline Policy",
"policyVersion": "1.2.3",
"tenantId": "TENANT-001",
"evaluatedAt": "2025-12-24T12:00:00+00:00",
"digestEvaluated": "sha256:abc123def456",
"outcome": 0,
"rulesMatched": 2,
"rulesTotal": 5,
"violations": [],
"warnings": [],
"matchedRules": [
{
"ruleName": "require_vex_justification",
"priority": 3,
"status": 1,
"reason": "VEX statement accepted with strong justification"
}
],
"unknownsBudgetResult": null,
"vexMergeTrace": {
"vulnerabilityId": "CVE-2024-0001",
"winningSource": "vendor",
"winningStatus": "not_affected",
"winningJustification": "component_not_present",
"conflictsResolved": 1,
"sourcesConsidered": 3,
"resolutionReason": "TrustWeight"
},
"scoreResult": null,
"metadata": {
"evaluationDurationMs": 55,
"feedVersions": {
"nvd": "2025-12-24",
"ghsa": "2025-12-24"
},
"policyChecksum": "sha256:policy123"
}
}

View File

@@ -0,0 +1,72 @@
{
"traceId": "TRACE-2025-003",
"policyId": "POL-VEX-001",
"policyName": "VEX-Aware Policy",
"evaluationContext": {
"digestEvaluated": "sha256:vex123",
"tenantId": "TENANT-001",
"environment": "production",
"exposure": "internet"
},
"startedAt": "2025-12-24T12:00:00+00:00",
"completedAt": "2025-12-24T12:00:00.067+00:00",
"durationMs": 67,
"outcome": "Pass",
"steps": [
{
"stepNumber": 1,
"ruleName": "vex_merge_resolution",
"priority": 10,
"phase": 1,
"condition": "vex.statements.count \u003E 1",
"conditionResult": true,
"action": null,
"explanation": "Multiple VEX statements found; merging via K4 lattice",
"durationMs": 20,
"vexMergeDetail": {
"vulnerabilityId": "CVE-2024-0001",
"statementCount": 3,
"sources": [
{
"source": "vendor",
"status": "not_affected",
"trust": 1.0
},
{
"source": "maintainer",
"status": "affected",
"trust": 0.9
},
{
"source": "scanner",
"status": "unknown",
"trust": 0.5
}
],
"winningSource": "vendor",
"winningStatus": "not_affected",
"resolutionReason": "TrustWeight",
"conflictsResolved": 2
},
"profileApplicationDetail": null,
"escalationDetail": null
},
{
"stepNumber": 2,
"ruleName": "require_vex_justification",
"priority": 3,
"phase": 3,
"condition": "vex.justification in [\u0022component_not_present\u0022,\u0022vulnerable_code_not_present\u0022]",
"conditionResult": true,
"action": "status := vex.status",
"explanation": "VEX justification accepted: component_not_present",
"durationMs": 12,
"vexMergeDetail": null,
"profileApplicationDetail": null,
"escalationDetail": null
}
],
"finalStatus": "allowed",
"matchedRuleCount": 2,
"totalRuleCount": 2
}

View File

@@ -23,7 +23,7 @@
<ProjectReference Include="../../StellaOps.Policy.Engine/StellaOps.Policy.Engine.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.DeltaVerdict/StellaOps.DeltaVerdict.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Provcache/StellaOps.Provcache.csproj" />
<ProjectReference Include="../../../Excititor/__Libraries/StellaOps.Excititor.Core/StellaOps.Excititor.Core.csproj" />
<ProjectReference Include="../../../Concelier/__Libraries/StellaOps.Excititor.Core/StellaOps.Excititor.Core.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Policy.Unknowns/StellaOps.Policy.Unknowns.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
<ProjectReference Include="../../../Scanner/__Libraries/StellaOps.Scanner.Emit/StellaOps.Scanner.Emit.csproj" />

View File

@@ -149,6 +149,7 @@ public sealed class TenantIsolationTests
// Arrange
var filter = new TenantContextEndpointFilter();
var services = new ServiceCollection();
services.AddLogging();
services.AddSingleton<ITenantContextAccessor>(_accessor);
var sp = services.BuildServiceProvider();

View File

@@ -207,6 +207,7 @@ public sealed class EvidenceWeightedScoreModelTests
var policy = new ScorePolicy
{
PolicyVersion = "score.v1",
PolicyId = "test-policy.invalid-weights",
WeightsBps = new WeightsBps
{
BaseSeverity = 1000,

View File

@@ -0,0 +1,107 @@
using FluentAssertions;
using StellaOps.Policy.Scoring;
using System.Text.Json;
using Xunit;
namespace StellaOps.Policy.Tests.Scoring;
[Trait("Category", "Unit")]
public sealed class ScorePolicyLoaderContractTests
{
private readonly ScorePolicyLoader _loader = new();
[Fact]
public void LoadFromYaml_MissingPolicyId_FailsValidation()
{
var yaml = """
policyVersion: score.v1
weightsBps:
baseSeverity: 2500
reachability: 2500
evidence: 2500
provenance: 2500
""";
var act = () => _loader.LoadFromYaml(yaml, "missing-policy-id");
act.Should()
.Throw<ScorePolicyLoadException>()
.WithMessage("*Missing required field 'policyId'*");
}
[Fact]
public void LoadFromYaml_ValidPolicyWithPolicyId_LoadsSuccessfully()
{
var yaml = """
policyVersion: score.v1
policyId: tenant-default-score
scoringProfile: advanced
weightsBps:
baseSeverity: 2500
reachability: 2500
evidence: 2500
provenance: 2500
reachability:
unreachableScore: 0
evidence:
points:
runtime: 60
dast: 30
sast: 20
sca: 10
provenance:
levels:
unsigned: 0
signed: 30
signedWithSbom: 60
signedWithSbomAndAttestations: 80
reproducible: 100
""";
var policy = _loader.LoadFromYaml(yaml, "valid-policy");
policy.PolicyId.Should().Be("tenant-default-score");
policy.PolicyVersion.Should().Be("score.v1");
policy.ValidateWeights().Should().BeTrue();
policy.Reachability.Should().NotBeNull();
policy.Evidence.Should().NotBeNull();
policy.Provenance.Should().NotBeNull();
}
[Fact]
public void EmbeddedSchemaAndSourceSchema_RemainInParity()
{
var embeddedSchema = JsonDocument.Parse(ScorePolicySchemaResource.ReadSchemaJson());
var sourceSchema = JsonDocument.Parse(File.ReadAllText(FindSourceSchemaPath()));
var embeddedNormalized = JsonSerializer.Serialize(embeddedSchema.RootElement);
var sourceNormalized = JsonSerializer.Serialize(sourceSchema.RootElement);
embeddedNormalized.Should().Be(sourceNormalized,
"embedded schema and source schema must stay identical");
}
private static string FindSourceSchemaPath()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null)
{
var candidate = Path.Combine(
directory.FullName,
"src",
"Policy",
"__Libraries",
"StellaOps.Policy",
"Schemas",
"score-policy.v1.schema.json");
if (File.Exists(candidate))
{
return candidate;
}
directory = directory.Parent;
}
throw new InvalidOperationException("Unable to locate score-policy.v1.schema.json from test base directory.");
}
}