Add comprehensive security tests for OWASP A03 (Injection) and A10 (SSRF)
- Implemented InjectionTests.cs to cover various injection vulnerabilities including SQL, NoSQL, Command, LDAP, and XPath injections. - Created SsrfTests.cs to test for Server-Side Request Forgery (SSRF) vulnerabilities, including internal URL access, cloud metadata access, and URL allowlist bypass attempts. - Introduced MaliciousPayloads.cs to store a collection of malicious payloads for testing various security vulnerabilities. - Added SecurityAssertions.cs for common security-specific assertion helpers. - Established SecurityTestBase.cs as a base class for security tests, providing common infrastructure and mocking utilities. - Configured the test project StellaOps.Security.Tests.csproj with necessary dependencies for testing.
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
using StellaOps.Scanner.Worker.Determinism;
|
||||
using StellaOps.Scanner.Worker.Determinism.Calculators;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Worker.Tests.Determinism;
|
||||
|
||||
public sealed class FidelityMetricsServiceTests
|
||||
{
|
||||
private readonly FidelityMetricsService _service = new();
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithAllIdentical_ReturnsFullScores()
|
||||
{
|
||||
var baselineHashes = new Dictionary<string, string>
|
||||
{
|
||||
["sbom.json"] = "sha256:abc",
|
||||
["findings.ndjson"] = "sha256:def"
|
||||
};
|
||||
var replayHashes = new List<IReadOnlyDictionary<string, string>>
|
||||
{
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["sbom.json"] = "sha256:abc",
|
||||
["findings.ndjson"] = "sha256:def"
|
||||
}
|
||||
};
|
||||
|
||||
var baselineFindings = CreateNormalizedFindings();
|
||||
var replayFindings = new List<NormalizedFindings> { CreateNormalizedFindings() };
|
||||
|
||||
var baselineDecision = CreatePolicyDecision();
|
||||
var replayDecisions = new List<PolicyDecision> { CreatePolicyDecision() };
|
||||
|
||||
var metrics = _service.Calculate(
|
||||
baselineHashes, replayHashes,
|
||||
baselineFindings, replayFindings,
|
||||
baselineDecision, replayDecisions);
|
||||
|
||||
Assert.Equal(1.0, metrics.BitwiseFidelity);
|
||||
Assert.Equal(1.0, metrics.SemanticFidelity);
|
||||
Assert.Equal(1.0, metrics.PolicyFidelity);
|
||||
Assert.Equal(1, metrics.TotalReplays);
|
||||
Assert.Equal(1, metrics.IdenticalOutputs);
|
||||
Assert.Equal(1, metrics.SemanticMatches);
|
||||
Assert.Equal(1, metrics.PolicyMatches);
|
||||
Assert.Null(metrics.Mismatches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithMixedResults_ReturnsCorrectMetrics()
|
||||
{
|
||||
var baselineHashes = new Dictionary<string, string> { ["file.json"] = "hash1" };
|
||||
var replayHashes = new List<IReadOnlyDictionary<string, string>>
|
||||
{
|
||||
new Dictionary<string, string> { ["file.json"] = "hash1" }, // Match
|
||||
new Dictionary<string, string> { ["file.json"] = "hash2" }, // Mismatch
|
||||
new Dictionary<string, string> { ["file.json"] = "hash1" } // Match
|
||||
};
|
||||
|
||||
var baselineFindings = CreateNormalizedFindings();
|
||||
var replayFindings = new List<NormalizedFindings>
|
||||
{
|
||||
CreateNormalizedFindings(),
|
||||
CreateNormalizedFindings(),
|
||||
CreateNormalizedFindings()
|
||||
};
|
||||
|
||||
var baselineDecision = CreatePolicyDecision();
|
||||
var replayDecisions = new List<PolicyDecision>
|
||||
{
|
||||
CreatePolicyDecision(),
|
||||
CreatePolicyDecision(),
|
||||
CreatePolicyDecision()
|
||||
};
|
||||
|
||||
var metrics = _service.Calculate(
|
||||
baselineHashes, replayHashes,
|
||||
baselineFindings, replayFindings,
|
||||
baselineDecision, replayDecisions);
|
||||
|
||||
Assert.Equal(2.0 / 3, metrics.BitwiseFidelity, precision: 4);
|
||||
Assert.Equal(1.0, metrics.SemanticFidelity);
|
||||
Assert.Equal(1.0, metrics.PolicyFidelity);
|
||||
Assert.NotNull(metrics.Mismatches);
|
||||
Assert.Single(metrics.Mismatches!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_WithPassingMetrics_ReturnsPass()
|
||||
{
|
||||
var metrics = new FidelityMetrics
|
||||
{
|
||||
BitwiseFidelity = 0.99,
|
||||
SemanticFidelity = 1.0,
|
||||
PolicyFidelity = 1.0,
|
||||
TotalReplays = 10,
|
||||
IdenticalOutputs = 10,
|
||||
SemanticMatches = 10,
|
||||
PolicyMatches = 10,
|
||||
ComputedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
var thresholds = FidelityThresholds.Default;
|
||||
|
||||
var evaluation = _service.Evaluate(metrics, thresholds);
|
||||
|
||||
Assert.True(evaluation.Passed);
|
||||
Assert.False(evaluation.ShouldBlockRelease);
|
||||
Assert.Empty(evaluation.FailureReasons);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_WithFailingBitwiseFidelity_ReturnsFail()
|
||||
{
|
||||
var metrics = new FidelityMetrics
|
||||
{
|
||||
BitwiseFidelity = 0.90, // Below 0.98 threshold
|
||||
SemanticFidelity = 1.0,
|
||||
PolicyFidelity = 1.0,
|
||||
TotalReplays = 10,
|
||||
IdenticalOutputs = 9,
|
||||
SemanticMatches = 10,
|
||||
PolicyMatches = 10,
|
||||
ComputedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
var thresholds = FidelityThresholds.Default;
|
||||
|
||||
var evaluation = _service.Evaluate(metrics, thresholds);
|
||||
|
||||
Assert.False(evaluation.Passed);
|
||||
Assert.Single(evaluation.FailureReasons);
|
||||
Assert.Contains("BF", evaluation.FailureReasons[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_WithCriticallyLowBF_ShouldBlockRelease()
|
||||
{
|
||||
var metrics = new FidelityMetrics
|
||||
{
|
||||
BitwiseFidelity = 0.85, // Below 0.90 block threshold
|
||||
SemanticFidelity = 1.0,
|
||||
PolicyFidelity = 1.0,
|
||||
TotalReplays = 10,
|
||||
IdenticalOutputs = 8,
|
||||
SemanticMatches = 10,
|
||||
PolicyMatches = 10,
|
||||
ComputedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
var thresholds = FidelityThresholds.Default;
|
||||
|
||||
var evaluation = _service.Evaluate(metrics, thresholds);
|
||||
|
||||
Assert.False(evaluation.Passed);
|
||||
Assert.True(evaluation.ShouldBlockRelease);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_WithRegulatedProject_UsesLowerThreshold()
|
||||
{
|
||||
var metrics = new FidelityMetrics
|
||||
{
|
||||
BitwiseFidelity = 0.96, // Above 0.95 regulated, below 0.98 general
|
||||
SemanticFidelity = 1.0,
|
||||
PolicyFidelity = 1.0,
|
||||
TotalReplays = 10,
|
||||
IdenticalOutputs = 9,
|
||||
SemanticMatches = 10,
|
||||
PolicyMatches = 10,
|
||||
ComputedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
var thresholds = FidelityThresholds.Default;
|
||||
|
||||
var generalEval = _service.Evaluate(metrics, thresholds, isRegulated: false);
|
||||
var regulatedEval = _service.Evaluate(metrics, thresholds, isRegulated: true);
|
||||
|
||||
Assert.False(generalEval.Passed); // Fails 0.98 threshold
|
||||
Assert.True(regulatedEval.Passed); // Passes 0.95 threshold
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluate_WithMultipleFailures_ReportsAll()
|
||||
{
|
||||
var metrics = new FidelityMetrics
|
||||
{
|
||||
BitwiseFidelity = 0.90,
|
||||
SemanticFidelity = 0.80,
|
||||
PolicyFidelity = 0.70,
|
||||
TotalReplays = 10,
|
||||
IdenticalOutputs = 9,
|
||||
SemanticMatches = 8,
|
||||
PolicyMatches = 7,
|
||||
ComputedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
var thresholds = FidelityThresholds.Default;
|
||||
|
||||
var evaluation = _service.Evaluate(metrics, thresholds);
|
||||
|
||||
Assert.False(evaluation.Passed);
|
||||
Assert.Equal(3, evaluation.FailureReasons.Count);
|
||||
}
|
||||
|
||||
private static NormalizedFindings CreateNormalizedFindings() => new()
|
||||
{
|
||||
Packages = new List<NormalizedPackage>
|
||||
{
|
||||
new("pkg:npm/test@1.0.0", "1.0.0")
|
||||
},
|
||||
Cves = new HashSet<string> { "CVE-2024-0001" },
|
||||
SeverityCounts = new Dictionary<string, int> { ["MEDIUM"] = 1 },
|
||||
Verdicts = new Dictionary<string, string> { ["overall"] = "pass" }
|
||||
};
|
||||
|
||||
private static PolicyDecision CreatePolicyDecision() => new()
|
||||
{
|
||||
Passed = true,
|
||||
ReasonCodes = new List<string> { "CLEAN" },
|
||||
ViolationCount = 0,
|
||||
BlockLevel = "none"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
using StellaOps.Scanner.Worker.Determinism;
|
||||
using StellaOps.Scanner.Worker.Determinism.Calculators;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Worker.Tests.Determinism;
|
||||
|
||||
public sealed class PolicyFidelityCalculatorTests
|
||||
{
|
||||
private readonly PolicyFidelityCalculator _calculator = new();
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithEmptyReplays_ReturnsFullScore()
|
||||
{
|
||||
var baseline = CreatePassingDecision();
|
||||
var replays = Array.Empty<PolicyDecision>();
|
||||
|
||||
var (score, matchCount, mismatches) = _calculator.Calculate(baseline, replays);
|
||||
|
||||
Assert.Equal(1.0, score);
|
||||
Assert.Equal(0, matchCount);
|
||||
Assert.Empty(mismatches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithIdenticalDecisions_ReturnsFullScore()
|
||||
{
|
||||
var baseline = CreatePassingDecision();
|
||||
var replays = new List<PolicyDecision>
|
||||
{
|
||||
CreatePassingDecision(),
|
||||
CreatePassingDecision()
|
||||
};
|
||||
|
||||
var (score, matchCount, mismatches) = _calculator.Calculate(baseline, replays);
|
||||
|
||||
Assert.Equal(1.0, score);
|
||||
Assert.Equal(2, matchCount);
|
||||
Assert.Empty(mismatches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithDifferentOutcome_DetectsMismatch()
|
||||
{
|
||||
var baseline = CreatePassingDecision();
|
||||
var replays = new List<PolicyDecision>
|
||||
{
|
||||
new PolicyDecision
|
||||
{
|
||||
Passed = false, // Different outcome
|
||||
ReasonCodes = new List<string> { "NO_VIOLATIONS" },
|
||||
ViolationCount = 0,
|
||||
BlockLevel = "none"
|
||||
}
|
||||
};
|
||||
|
||||
var (score, matchCount, mismatches) = _calculator.Calculate(baseline, replays);
|
||||
|
||||
Assert.Equal(0.0, score);
|
||||
Assert.Equal(0, matchCount);
|
||||
Assert.Single(mismatches);
|
||||
Assert.Equal(FidelityMismatchType.PolicyDrift, mismatches[0].Type);
|
||||
Assert.Contains("outcome:True→False", mismatches[0].AffectedArtifacts!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithDifferentReasonCodes_DetectsMismatch()
|
||||
{
|
||||
var baseline = CreatePassingDecision();
|
||||
var replays = new List<PolicyDecision>
|
||||
{
|
||||
new PolicyDecision
|
||||
{
|
||||
Passed = true,
|
||||
ReasonCodes = new List<string> { "DIFFERENT_REASON" }, // Different reason
|
||||
ViolationCount = 0,
|
||||
BlockLevel = "none"
|
||||
}
|
||||
};
|
||||
|
||||
var (score, matchCount, mismatches) = _calculator.Calculate(baseline, replays);
|
||||
|
||||
Assert.Equal(0.0, score);
|
||||
Assert.Contains("reason_codes", mismatches[0].AffectedArtifacts!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithDifferentViolationCount_DetectsMismatch()
|
||||
{
|
||||
var baseline = CreatePassingDecision();
|
||||
var replays = new List<PolicyDecision>
|
||||
{
|
||||
new PolicyDecision
|
||||
{
|
||||
Passed = true,
|
||||
ReasonCodes = new List<string> { "NO_VIOLATIONS" },
|
||||
ViolationCount = 5, // Different count
|
||||
BlockLevel = "none"
|
||||
}
|
||||
};
|
||||
|
||||
var (score, matchCount, mismatches) = _calculator.Calculate(baseline, replays);
|
||||
|
||||
Assert.Equal(0.0, score);
|
||||
Assert.Contains("violations:0→5", mismatches[0].AffectedArtifacts!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithDifferentBlockLevel_DetectsMismatch()
|
||||
{
|
||||
var baseline = CreatePassingDecision();
|
||||
var replays = new List<PolicyDecision>
|
||||
{
|
||||
new PolicyDecision
|
||||
{
|
||||
Passed = true,
|
||||
ReasonCodes = new List<string> { "NO_VIOLATIONS" },
|
||||
ViolationCount = 0,
|
||||
BlockLevel = "warn" // Different block level
|
||||
}
|
||||
};
|
||||
|
||||
var (score, matchCount, mismatches) = _calculator.Calculate(baseline, replays);
|
||||
|
||||
Assert.Equal(0.0, score);
|
||||
Assert.Contains("block_level:none→warn", mismatches[0].AffectedArtifacts!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithMultipleDifferences_ReportsAll()
|
||||
{
|
||||
var baseline = CreatePassingDecision();
|
||||
var replays = new List<PolicyDecision>
|
||||
{
|
||||
new PolicyDecision
|
||||
{
|
||||
Passed = false, // Different
|
||||
ReasonCodes = new List<string> { "CRITICAL_VULN" }, // Different
|
||||
ViolationCount = 3, // Different
|
||||
BlockLevel = "block" // Different
|
||||
}
|
||||
};
|
||||
|
||||
var (score, matchCount, mismatches) = _calculator.Calculate(baseline, replays);
|
||||
|
||||
Assert.Equal(0.0, score);
|
||||
Assert.Single(mismatches);
|
||||
var mismatch = mismatches[0];
|
||||
Assert.Equal(4, mismatch.AffectedArtifacts!.Count); // All 4 differences detected
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithPartialMatches_ReturnsCorrectScore()
|
||||
{
|
||||
var baseline = CreatePassingDecision();
|
||||
var replays = new List<PolicyDecision>
|
||||
{
|
||||
CreatePassingDecision(), // Match
|
||||
new PolicyDecision // Mismatch
|
||||
{
|
||||
Passed = false,
|
||||
ReasonCodes = new List<string>(),
|
||||
ViolationCount = 1,
|
||||
BlockLevel = "block"
|
||||
},
|
||||
CreatePassingDecision(), // Match
|
||||
CreatePassingDecision() // Match
|
||||
};
|
||||
|
||||
var (score, matchCount, mismatches) = _calculator.Calculate(baseline, replays);
|
||||
|
||||
Assert.Equal(3.0 / 4, score, precision: 4);
|
||||
Assert.Equal(3, matchCount);
|
||||
Assert.Single(mismatches);
|
||||
Assert.Equal(1, mismatches[0].RunIndex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithReasonCodesInDifferentOrder_StillMatches()
|
||||
{
|
||||
var baseline = new PolicyDecision
|
||||
{
|
||||
Passed = true,
|
||||
ReasonCodes = new List<string> { "CODE_A", "CODE_B", "CODE_C" },
|
||||
ViolationCount = 0,
|
||||
BlockLevel = "none"
|
||||
};
|
||||
var replays = new List<PolicyDecision>
|
||||
{
|
||||
new PolicyDecision
|
||||
{
|
||||
Passed = true,
|
||||
ReasonCodes = new List<string> { "CODE_C", "CODE_A", "CODE_B" }, // Different order
|
||||
ViolationCount = 0,
|
||||
BlockLevel = "none"
|
||||
}
|
||||
};
|
||||
|
||||
var (score, matchCount, mismatches) = _calculator.Calculate(baseline, replays);
|
||||
|
||||
Assert.Equal(1.0, score);
|
||||
Assert.Equal(1, matchCount);
|
||||
Assert.Empty(mismatches);
|
||||
}
|
||||
|
||||
private static PolicyDecision CreatePassingDecision() => new()
|
||||
{
|
||||
Passed = true,
|
||||
ReasonCodes = new List<string> { "NO_VIOLATIONS" },
|
||||
ViolationCount = 0,
|
||||
BlockLevel = "none",
|
||||
PolicyHash = "sha256:abc123"
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user