Add comprehensive security tests for OWASP A02, A05, A07, and A08 categories
Some checks failed
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
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled

- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management.
- Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management.
- Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support.
- Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
This commit is contained in:
master
2025-12-16 16:40:19 +02:00
parent 415eff1207
commit 2170a58734
206 changed files with 30547 additions and 534 deletions

View File

@@ -6,6 +6,13 @@ namespace StellaOps.Policy.Scoring;
public sealed record ScorePolicy
{
public required string PolicyVersion { get; init; }
/// <summary>
/// Scoring profile to use. Defaults to "advanced".
/// Options: "simple", "advanced", "custom"
/// </summary>
public string ScoringProfile { get; init; } = "advanced";
public required WeightsBps WeightsBps { get; init; }
public ReachabilityPolicyConfig? Reachability { get; init; }
public EvidencePolicyConfig? Evidence { get; init; }
@@ -28,6 +35,7 @@ public sealed record ScorePolicy
public static ScorePolicy Default => new()
{
PolicyVersion = "score.v1",
ScoringProfile = "advanced",
WeightsBps = WeightsBps.Default,
Reachability = ReachabilityPolicyConfig.Default,
Evidence = EvidencePolicyConfig.Default,

View File

@@ -0,0 +1,318 @@
// =============================================================================
// ScorePolicyValidator.cs
// Sprint: SPRINT_3402_0001_0001
// Task: YAML-3402-003 - Implement ScorePolicyValidator with JSON Schema validation
// =============================================================================
using System.Text.Json;
using Json.Schema;
namespace StellaOps.Policy.Scoring;
/// <summary>
/// Validates score policies against JSON Schema.
/// </summary>
public sealed class ScorePolicyValidator
{
private readonly JsonSchema _schema;
/// <summary>
/// Creates a validator with the embedded score.v1 schema.
/// </summary>
public ScorePolicyValidator()
{
_schema = JsonSchema.FromText(ScorePolicySchemaJson);
}
/// <summary>
/// Creates a validator with a custom schema.
/// </summary>
public ScorePolicyValidator(string schemaJson)
{
_schema = JsonSchema.FromText(schemaJson);
}
/// <summary>
/// Validates a score policy.
/// </summary>
/// <param name="policy">The policy to validate</param>
/// <returns>Validation result with errors if any</returns>
public ScorePolicyValidationResult Validate(ScorePolicy policy)
{
ArgumentNullException.ThrowIfNull(policy);
var json = JsonSerializer.Serialize(policy, JsonOptions);
var jsonDoc = JsonDocument.Parse(json);
var result = _schema.Evaluate(jsonDoc.RootElement);
if (result.IsValid)
{
return new ScorePolicyValidationResult(true, []);
}
var errors = CollectErrors(result);
return new ScorePolicyValidationResult(false, errors);
}
/// <summary>
/// Validates JSON content against the schema.
/// </summary>
public ScorePolicyValidationResult ValidateJson(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
return new ScorePolicyValidationResult(false, ["JSON content is empty"]);
}
try
{
var jsonDoc = JsonDocument.Parse(json);
var result = _schema.Evaluate(jsonDoc.RootElement);
if (result.IsValid)
{
return new ScorePolicyValidationResult(true, []);
}
var errors = CollectErrors(result);
return new ScorePolicyValidationResult(false, errors);
}
catch (JsonException ex)
{
return new ScorePolicyValidationResult(false, [$"Invalid JSON: {ex.Message}"]);
}
}
private static List<string> CollectErrors(EvaluationResults result)
{
var errors = new List<string>();
CollectErrorsRecursive(result, errors);
return errors;
}
private static void CollectErrorsRecursive(EvaluationResults result, List<string> errors)
{
if (!result.IsValid && result.Errors is { Count: > 0 })
{
foreach (var error in result.Errors)
{
errors.Add($"{result.InstanceLocation}: {error.Key} - {error.Value}");
}
}
if (result.Details is null) return;
foreach (var detail in result.Details)
{
CollectErrorsRecursive(detail, errors);
}
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
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>
/// Result of score policy validation.
/// </summary>
/// <param name="IsValid">Whether the policy is valid</param>
/// <param name="Errors">List of validation errors (empty if valid)</param>
public readonly record struct ScorePolicyValidationResult(bool IsValid, IReadOnlyList<string> Errors)
{
/// <summary>
/// Throws if validation failed.
/// </summary>
public void ThrowIfInvalid(string context = "")
{
if (!IsValid)
{
var prefix = string.IsNullOrEmpty(context) ? "" : $"{context}: ";
throw new ScorePolicyValidationException(
$"{prefix}Score policy validation failed: {string.Join("; ", Errors)}");
}
}
}
/// <summary>
/// Exception thrown when score policy validation fails.
/// </summary>
public sealed class ScorePolicyValidationException : Exception
{
public ScorePolicyValidationException(string message) : base(message) { }
public ScorePolicyValidationException(string message, Exception inner) : base(message, inner) { }
}

View File

@@ -0,0 +1,71 @@
// -----------------------------------------------------------------------------
// ScoringProfile.cs
// Sprint: SPRINT_3407_0001_0001_configurable_scoring
// Task: PROF-3407-001
// Description: Defines scoring profiles for pluggable scoring engines
// -----------------------------------------------------------------------------
namespace StellaOps.Policy.Scoring;
/// <summary>
/// Available scoring profiles.
/// </summary>
public enum ScoringProfile
{
/// <summary>
/// Simple 4-factor basis-points weighted scoring.
/// Formula: riskScore = (wB*B + wR*R + wE*E + wP*P) / 10000
/// Transparent, customer-configurable via YAML.
/// </summary>
Simple,
/// <summary>
/// Advanced entropy-based + CVSS hybrid scoring.
/// Uses uncertainty tiers, entropy penalties, and CVSS v4.0 receipts.
/// Default for new deployments.
/// </summary>
Advanced,
/// <summary>
/// Custom scoring using fully user-defined rules.
/// Requires Rego policy configuration.
/// </summary>
Custom
}
/// <summary>
/// Scoring profile configuration.
/// </summary>
public sealed record ScoringProfileConfig
{
/// <summary>
/// Active scoring profile.
/// </summary>
public required ScoringProfile Profile { get; init; }
/// <summary>
/// Profile-specific settings.
/// </summary>
public IReadOnlyDictionary<string, string>? Settings { get; init; }
/// <summary>
/// For Custom profile: path to Rego policy.
/// </summary>
public string? CustomPolicyPath { get; init; }
/// <summary>
/// Creates default configuration for Advanced profile.
/// </summary>
public static ScoringProfileConfig DefaultAdvanced => new()
{
Profile = ScoringProfile.Advanced
};
/// <summary>
/// Creates default configuration for Simple profile.
/// </summary>
public static ScoringProfileConfig DefaultSimple => new()
{
Profile = ScoringProfile.Simple
};
}