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:
@@ -0,0 +1,312 @@
|
||||
// =============================================================================
|
||||
// SmartDiffSchemaValidationTests.cs
|
||||
// Sprint: SPRINT_3500_0002_0001
|
||||
// Task: SDIFF-FND-016 - JSON Schema validation tests
|
||||
// =============================================================================
|
||||
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using Json.Schema;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.SmartDiff.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests to validate Smart-Diff predicates against JSON Schema.
|
||||
/// </summary>
|
||||
[Trait("Category", "Schema")]
|
||||
[Trait("Sprint", "3500")]
|
||||
public sealed class SmartDiffSchemaValidationTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
[Fact(DisplayName = "Valid SmartDiffPredicate passes schema validation")]
|
||||
public void ValidPredicate_PassesValidation()
|
||||
{
|
||||
// Arrange
|
||||
var schema = GetSmartDiffSchema();
|
||||
var predicate = CreateValidPredicate();
|
||||
var json = JsonSerializer.Serialize(predicate, JsonOptions);
|
||||
var jsonNode = JsonDocument.Parse(json).RootElement;
|
||||
|
||||
// Act
|
||||
var result = schema.Evaluate(jsonNode);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue("Valid predicate should pass schema validation");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Predicate missing required field fails validation")]
|
||||
public void MissingRequiredField_FailsValidation()
|
||||
{
|
||||
// Arrange
|
||||
var schema = GetSmartDiffSchema();
|
||||
var json = """
|
||||
{
|
||||
"schemaVersion": "1.0.0",
|
||||
"baseImage": { "digest": "sha256:abc123" }
|
||||
}
|
||||
""";
|
||||
var jsonNode = JsonDocument.Parse(json).RootElement;
|
||||
|
||||
// Act
|
||||
var result = schema.Evaluate(jsonNode);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse("Missing required fields should fail validation");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Predicate with invalid schema version fails validation")]
|
||||
public void InvalidSchemaVersion_FailsValidation()
|
||||
{
|
||||
// Arrange
|
||||
var schema = GetSmartDiffSchema();
|
||||
var json = """
|
||||
{
|
||||
"schemaVersion": "invalid",
|
||||
"baseImage": { "digest": "sha256:abc123" },
|
||||
"targetImage": { "digest": "sha256:def456" },
|
||||
"diff": { "added": [], "removed": [], "modified": [] },
|
||||
"reachabilityGate": { "class": 0, "isSinkReachable": false, "isEntryReachable": false },
|
||||
"scanner": { "name": "test", "version": "1.0.0" }
|
||||
}
|
||||
""";
|
||||
var jsonNode = JsonDocument.Parse(json).RootElement;
|
||||
|
||||
// Act
|
||||
var result = schema.Evaluate(jsonNode);
|
||||
|
||||
// Assert
|
||||
// Schema version must match semver pattern
|
||||
result.IsValid.Should().BeFalse("Invalid schema version should fail validation");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "ReachabilityGate class must be 0-7")]
|
||||
public void ReachabilityGateClass_MustBe0To7()
|
||||
{
|
||||
// Arrange
|
||||
var schema = GetSmartDiffSchema();
|
||||
var json = """
|
||||
{
|
||||
"schemaVersion": "1.0.0",
|
||||
"baseImage": { "digest": "sha256:abc123" },
|
||||
"targetImage": { "digest": "sha256:def456" },
|
||||
"diff": { "added": [], "removed": [], "modified": [] },
|
||||
"reachabilityGate": { "class": 10, "isSinkReachable": false, "isEntryReachable": false },
|
||||
"scanner": { "name": "test", "version": "1.0.0" }
|
||||
}
|
||||
""";
|
||||
var jsonNode = JsonDocument.Parse(json).RootElement;
|
||||
|
||||
// Act
|
||||
var result = schema.Evaluate(jsonNode);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse("Reachability class > 7 should fail validation");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Valid reachability gate class 0 passes")]
|
||||
public void ReachabilityGateClass0_Passes()
|
||||
{
|
||||
// Arrange
|
||||
var schema = GetSmartDiffSchema();
|
||||
var json = CreatePredicateJson(gateClass: 0);
|
||||
var jsonNode = JsonDocument.Parse(json).RootElement;
|
||||
|
||||
// Act
|
||||
var result = schema.Evaluate(jsonNode);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Valid reachability gate class 7 passes")]
|
||||
public void ReachabilityGateClass7_Passes()
|
||||
{
|
||||
// Arrange
|
||||
var schema = GetSmartDiffSchema();
|
||||
var json = CreatePredicateJson(gateClass: 7);
|
||||
var jsonNode = JsonDocument.Parse(json).RootElement;
|
||||
|
||||
// Act
|
||||
var result = schema.Evaluate(jsonNode);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Suppressed count must be non-negative")]
|
||||
public void SuppressedCount_MustBeNonNegative()
|
||||
{
|
||||
// Arrange
|
||||
var schema = GetSmartDiffSchema();
|
||||
var json = """
|
||||
{
|
||||
"schemaVersion": "1.0.0",
|
||||
"baseImage": { "digest": "sha256:abc123" },
|
||||
"targetImage": { "digest": "sha256:def456" },
|
||||
"diff": { "added": [], "removed": [], "modified": [] },
|
||||
"reachabilityGate": { "class": 0, "isSinkReachable": false, "isEntryReachable": false },
|
||||
"scanner": { "name": "test", "version": "1.0.0" },
|
||||
"suppressedCount": -1
|
||||
}
|
||||
""";
|
||||
var jsonNode = JsonDocument.Parse(json).RootElement;
|
||||
|
||||
// Act
|
||||
var result = schema.Evaluate(jsonNode);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse("Negative suppressed count should fail");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Optional context field is valid when present")]
|
||||
public void OptionalContext_ValidWhenPresent()
|
||||
{
|
||||
// Arrange
|
||||
var schema = GetSmartDiffSchema();
|
||||
var json = """
|
||||
{
|
||||
"schemaVersion": "1.0.0",
|
||||
"baseImage": { "digest": "sha256:abc123" },
|
||||
"targetImage": { "digest": "sha256:def456" },
|
||||
"diff": { "added": [], "removed": [], "modified": [] },
|
||||
"reachabilityGate": { "class": 0, "isSinkReachable": false, "isEntryReachable": false },
|
||||
"scanner": { "name": "test", "version": "1.0.0" },
|
||||
"context": { "env": "production", "namespace": "default" }
|
||||
}
|
||||
""";
|
||||
var jsonNode = JsonDocument.Parse(json).RootElement;
|
||||
|
||||
// Act
|
||||
var result = schema.Evaluate(jsonNode);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
private static JsonSchema GetSmartDiffSchema()
|
||||
{
|
||||
// Define schema inline for testing
|
||||
var schemaJson = """
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://stellaops.dev/schemas/smart-diff.v1.json",
|
||||
"type": "object",
|
||||
"required": ["schemaVersion", "baseImage", "targetImage", "diff", "reachabilityGate", "scanner"],
|
||||
"properties": {
|
||||
"schemaVersion": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
|
||||
},
|
||||
"baseImage": {
|
||||
"type": "object",
|
||||
"required": ["digest"],
|
||||
"properties": {
|
||||
"digest": { "type": "string" },
|
||||
"repository": { "type": "string" },
|
||||
"tag": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"targetImage": {
|
||||
"type": "object",
|
||||
"required": ["digest"],
|
||||
"properties": {
|
||||
"digest": { "type": "string" },
|
||||
"repository": { "type": "string" },
|
||||
"tag": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"type": "object",
|
||||
"required": ["added", "removed", "modified"],
|
||||
"properties": {
|
||||
"added": { "type": "array" },
|
||||
"removed": { "type": "array" },
|
||||
"modified": { "type": "array" }
|
||||
}
|
||||
},
|
||||
"reachabilityGate": {
|
||||
"type": "object",
|
||||
"required": ["class", "isSinkReachable", "isEntryReachable"],
|
||||
"properties": {
|
||||
"class": { "type": "integer", "minimum": 0, "maximum": 7 },
|
||||
"isSinkReachable": { "type": "boolean" },
|
||||
"isEntryReachable": { "type": "boolean" },
|
||||
"sinkCategory": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"scanner": {
|
||||
"type": "object",
|
||||
"required": ["name", "version"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"version": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"context": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"suppressedCount": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"materialChanges": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
return JsonSchema.FromText(schemaJson);
|
||||
}
|
||||
|
||||
private static object CreateValidPredicate()
|
||||
{
|
||||
return new
|
||||
{
|
||||
schemaVersion = "1.0.0",
|
||||
baseImage = new { digest = "sha256:abc123" },
|
||||
targetImage = new { digest = "sha256:def456" },
|
||||
diff = new
|
||||
{
|
||||
added = Array.Empty<object>(),
|
||||
removed = Array.Empty<object>(),
|
||||
modified = Array.Empty<object>()
|
||||
},
|
||||
reachabilityGate = new
|
||||
{
|
||||
@class = 0,
|
||||
isSinkReachable = false,
|
||||
isEntryReachable = false
|
||||
},
|
||||
scanner = new
|
||||
{
|
||||
name = "stellaops-scanner",
|
||||
version = "1.5.0"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static string CreatePredicateJson(int gateClass)
|
||||
{
|
||||
return $$"""
|
||||
{
|
||||
"schemaVersion": "1.0.0",
|
||||
"baseImage": { "digest": "sha256:abc123" },
|
||||
"targetImage": { "digest": "sha256:def456" },
|
||||
"diff": { "added": [], "removed": [], "modified": [] },
|
||||
"reachabilityGate": { "class": {{gateClass}}, "isSinkReachable": false, "isEntryReachable": false },
|
||||
"scanner": { "name": "test", "version": "1.0.0" }
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user