Add call graph fixtures for various languages and scenarios
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Policy Lint & Smoke / policy-lint (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
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (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
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

- Introduced `all-edge-reasons.json` to test edge resolution reasons in .NET.
- Added `all-visibility-levels.json` to validate method visibility levels in .NET.
- Created `dotnet-aspnetcore-minimal.json` for a minimal ASP.NET Core application.
- Included `go-gin-api.json` for a Go Gin API application structure.
- Added `java-spring-boot.json` for the Spring PetClinic application in Java.
- Introduced `legacy-no-schema.json` for legacy application structure without schema.
- Created `node-express-api.json` for an Express.js API application structure.
This commit is contained in:
master
2025-12-16 10:44:24 +02:00
parent 4391f35d8a
commit 5a480a3c2a
223 changed files with 19367 additions and 727 deletions

View File

@@ -0,0 +1,141 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stellaops.org/schemas/score-policy.v1.json",
"title": "StellaOps Score Policy v1",
"description": "Defines deterministic vulnerability scoring weights, buckets, and overrides",
"type": "object",
"required": ["policyVersion", "weightsBps"],
"properties": {
"policyVersion": {
"const": "score.v1",
"description": "Policy schema version"
},
"weightsBps": {
"type": "object",
"description": "Weight distribution in basis points (must sum to 10000)",
"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 }
},
"additionalProperties": false
},
"reachability": {
"$ref": "#/$defs/reachabilityConfig"
},
"evidence": {
"$ref": "#/$defs/evidenceConfig"
},
"provenance": {
"$ref": "#/$defs/provenanceConfig"
},
"overrides": {
"type": "array",
"items": { "$ref": "#/$defs/scoreOverride" }
}
},
"additionalProperties": false,
"$defs": {
"reachabilityConfig": {
"type": "object",
"properties": {
"hopBuckets": {
"type": "array",
"items": {
"type": "object",
"required": ["maxHops", "score"],
"properties": {
"maxHops": { "type": "integer", "minimum": 0 },
"score": { "type": "integer", "minimum": 0, "maximum": 100 }
},
"additionalProperties": false
}
},
"unreachableScore": { "type": "integer", "minimum": 0, "maximum": 100 },
"gateMultipliersBps": {
"type": "object",
"properties": {
"featureFlag": { "type": "integer", "minimum": 0, "maximum": 10000 },
"authRequired": { "type": "integer", "minimum": 0, "maximum": 10000 },
"adminOnly": { "type": "integer", "minimum": 0, "maximum": 10000 },
"nonDefaultConfig": { "type": "integer", "minimum": 0, "maximum": 10000 }
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"evidenceConfig": {
"type": "object",
"properties": {
"points": {
"type": "object",
"properties": {
"runtime": { "type": "integer", "minimum": 0, "maximum": 100 },
"dast": { "type": "integer", "minimum": 0, "maximum": 100 },
"sast": { "type": "integer", "minimum": 0, "maximum": 100 },
"sca": { "type": "integer", "minimum": 0, "maximum": 100 }
},
"additionalProperties": false
},
"freshnessBuckets": {
"type": "array",
"items": {
"type": "object",
"required": ["maxAgeDays", "multiplierBps"],
"properties": {
"maxAgeDays": { "type": "integer", "minimum": 0 },
"multiplierBps": { "type": "integer", "minimum": 0, "maximum": 10000 }
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"provenanceConfig": {
"type": "object",
"properties": {
"levels": {
"type": "object",
"properties": {
"unsigned": { "type": "integer", "minimum": 0, "maximum": 100 },
"signed": { "type": "integer", "minimum": 0, "maximum": 100 },
"signedWithSbom": { "type": "integer", "minimum": 0, "maximum": 100 },
"signedWithSbomAndAttestations": { "type": "integer", "minimum": 0, "maximum": 100 },
"reproducible": { "type": "integer", "minimum": 0, "maximum": 100 }
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"scoreOverride": {
"type": "object",
"required": ["name", "when"],
"properties": {
"name": { "type": "string", "minLength": 1 },
"when": {
"type": "object",
"properties": {
"flags": {
"type": "object",
"additionalProperties": { "type": "boolean" }
},
"minReachability": { "type": "integer", "minimum": 0, "maximum": 100 },
"maxReachability": { "type": "integer", "minimum": 0, "maximum": 100 },
"minEvidence": { "type": "integer", "minimum": 0, "maximum": 100 },
"maxEvidence": { "type": "integer", "minimum": 0, "maximum": 100 }
},
"additionalProperties": false
},
"setScore": { "type": "integer", "minimum": 0, "maximum": 100 },
"clampMaxScore": { "type": "integer", "minimum": 0, "maximum": 100 },
"clampMinScore": { "type": "integer", "minimum": 0, "maximum": 100 }
},
"additionalProperties": false
}
}
}

View File

@@ -0,0 +1,99 @@
using System.Text;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace StellaOps.Policy.Scoring;
/// <summary>
/// Loads score policies from YAML files.
/// </summary>
public sealed class ScorePolicyLoader
{
private static readonly IDeserializer Deserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();
/// <summary>
/// Loads a score policy from a YAML file.
/// </summary>
/// <param name="path">Path to the YAML file</param>
/// <returns>Parsed score policy</returns>
/// <exception cref="ScorePolicyLoadException">If parsing fails</exception>
public ScorePolicy LoadFromFile(string path)
{
if (string.IsNullOrWhiteSpace(path))
throw new ArgumentException("Path cannot be null or empty", nameof(path));
if (!File.Exists(path))
throw new ScorePolicyLoadException($"Score policy file not found: {path}");
var yaml = File.ReadAllText(path, Encoding.UTF8);
return LoadFromYaml(yaml, path);
}
/// <summary>
/// Loads a score policy from YAML content.
/// </summary>
/// <param name="yaml">YAML content</param>
/// <param name="source">Source identifier for error messages</param>
/// <returns>Parsed score policy</returns>
public ScorePolicy LoadFromYaml(string yaml, string source = "<inline>")
{
if (string.IsNullOrWhiteSpace(yaml))
throw new ScorePolicyLoadException($"Empty YAML content from {source}");
try
{
var policy = Deserializer.Deserialize<ScorePolicy>(yaml);
if (policy is null)
throw new ScorePolicyLoadException($"Failed to parse score policy from {source}: empty document");
// Validate policy version
if (policy.PolicyVersion != "score.v1")
throw new ScorePolicyLoadException(
$"Unsupported policy version '{policy.PolicyVersion}' in {source}. Expected 'score.v1'");
// Validate weight sum
if (!policy.ValidateWeights())
{
var sum = policy.WeightsBps.BaseSeverity + policy.WeightsBps.Reachability +
policy.WeightsBps.Evidence + policy.WeightsBps.Provenance;
throw new ScorePolicyLoadException(
$"Weight basis points must sum to 10000 in {source}. Got: {sum}");
}
return policy;
}
catch (YamlException ex)
{
throw new ScorePolicyLoadException($"YAML parse error in {source}: {ex.Message}", ex);
}
}
/// <summary>
/// Tries to load a score policy, returning null on failure.
/// </summary>
public ScorePolicy? TryLoadFromFile(string path)
{
try
{
return LoadFromFile(path);
}
catch (ScorePolicyLoadException)
{
return null;
}
}
}
/// <summary>
/// Exception thrown when score policy loading fails.
/// </summary>
public sealed class ScorePolicyLoadException : Exception
{
public ScorePolicyLoadException(string message) : base(message) { }
public ScorePolicyLoadException(string message, Exception inner) : base(message, inner) { }
}

View File

@@ -0,0 +1,173 @@
namespace StellaOps.Policy.Scoring;
/// <summary>
/// Root score policy configuration loaded from YAML.
/// </summary>
public sealed record ScorePolicy
{
public required string PolicyVersion { get; init; }
public required WeightsBps WeightsBps { get; init; }
public ReachabilityPolicyConfig? Reachability { get; init; }
public EvidencePolicyConfig? Evidence { get; init; }
public ProvenancePolicyConfig? Provenance { get; init; }
public IReadOnlyList<ScoreOverride>? Overrides { get; init; }
/// <summary>
/// Validates that weight basis points sum to 10000.
/// </summary>
public bool ValidateWeights()
{
var sum = WeightsBps.BaseSeverity + WeightsBps.Reachability +
WeightsBps.Evidence + WeightsBps.Provenance;
return sum == 10000;
}
/// <summary>
/// Creates a default score policy.
/// </summary>
public static ScorePolicy Default => new()
{
PolicyVersion = "score.v1",
WeightsBps = WeightsBps.Default,
Reachability = ReachabilityPolicyConfig.Default,
Evidence = EvidencePolicyConfig.Default,
Provenance = ProvenancePolicyConfig.Default,
Overrides = []
};
}
/// <summary>
/// Weight distribution in basis points. Must sum to 10000.
/// </summary>
public sealed record WeightsBps
{
public required int BaseSeverity { get; init; }
public required int Reachability { get; init; }
public required int Evidence { get; init; }
public required int Provenance { get; init; }
public static WeightsBps Default => new()
{
BaseSeverity = 1000, // 10%
Reachability = 4500, // 45%
Evidence = 3000, // 30%
Provenance = 1500 // 15%
};
}
/// <summary>
/// Reachability scoring configuration.
/// </summary>
public sealed record ReachabilityPolicyConfig
{
public IReadOnlyList<HopBucket>? HopBuckets { get; init; }
public int UnreachableScore { get; init; } = 0;
public GateMultipliersBps? GateMultipliersBps { get; init; }
public static ReachabilityPolicyConfig Default => new()
{
HopBuckets =
[
new HopBucket(0, 100), // Direct call
new HopBucket(1, 90), // 1 hop
new HopBucket(3, 70), // 2-3 hops
new HopBucket(5, 50), // 4-5 hops
new HopBucket(10, 30), // 6-10 hops
new HopBucket(int.MaxValue, 10) // > 10 hops
],
UnreachableScore = 0,
GateMultipliersBps = Scoring.GateMultipliersBps.Default
};
}
public sealed record HopBucket(int MaxHops, int Score);
public sealed record GateMultipliersBps
{
public int FeatureFlag { get; init; } = 7000;
public int AuthRequired { get; init; } = 8000;
public int AdminOnly { get; init; } = 8500;
public int NonDefaultConfig { get; init; } = 7500;
public static GateMultipliersBps Default => new();
}
/// <summary>
/// Evidence scoring configuration.
/// </summary>
public sealed record EvidencePolicyConfig
{
public EvidencePoints? Points { get; init; }
public IReadOnlyList<FreshnessBucket>? FreshnessBuckets { get; init; }
public static EvidencePolicyConfig Default => new()
{
Points = EvidencePoints.Default,
FreshnessBuckets =
[
new FreshnessBucket(7, 10000), // 0-7 days: 100%
new FreshnessBucket(30, 9000), // 8-30 days: 90%
new FreshnessBucket(90, 7000), // 31-90 days: 70%
new FreshnessBucket(180, 5000), // 91-180 days: 50%
new FreshnessBucket(365, 3000), // 181-365 days: 30%
new FreshnessBucket(int.MaxValue, 1000) // > 1 year: 10%
]
};
}
public sealed record EvidencePoints
{
public int Runtime { get; init; } = 60;
public int Dast { get; init; } = 30;
public int Sast { get; init; } = 20;
public int Sca { get; init; } = 10;
public static EvidencePoints Default => new();
}
public sealed record FreshnessBucket(int MaxAgeDays, int MultiplierBps);
/// <summary>
/// Provenance scoring configuration.
/// </summary>
public sealed record ProvenancePolicyConfig
{
public ProvenanceLevels? Levels { get; init; }
public static ProvenancePolicyConfig Default => new()
{
Levels = ProvenanceLevels.Default
};
}
public sealed record ProvenanceLevels
{
public int Unsigned { get; init; } = 0;
public int Signed { get; init; } = 30;
public int SignedWithSbom { get; init; } = 60;
public int SignedWithSbomAndAttestations { get; init; } = 80;
public int Reproducible { get; init; } = 100;
public static ProvenanceLevels Default => new();
}
/// <summary>
/// Score override rule for special conditions.
/// </summary>
public sealed record ScoreOverride
{
public required string Name { get; init; }
public required ScoreOverrideCondition When { get; init; }
public int? SetScore { get; init; }
public int? ClampMaxScore { get; init; }
public int? ClampMinScore { get; init; }
}
public sealed record ScoreOverrideCondition
{
public IReadOnlyDictionary<string, bool>? Flags { get; init; }
public int? MinReachability { get; init; }
public int? MaxReachability { get; init; }
public int? MinEvidence { get; init; }
public int? MaxEvidence { get; init; }
}