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
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:
@@ -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,
|
||||
|
||||
@@ -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) { }
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user