// ============================================================================= // SecurityMisconfigurationTests.cs // Sprint: SPRINT_0352_0001_0001_security_testing_framework // Task: SEC-0352-007 // OWASP A05:2021 - Security Misconfiguration // ============================================================================= using FluentAssertions; using StellaOps.Security.Tests.Infrastructure; namespace StellaOps.Security.Tests.A05_SecurityMisconfiguration; /// /// Tests for OWASP A05:2021 - Security Misconfiguration. /// Ensures proper security configuration across all modules. /// [Trait("Category", "Security")] [Trait("OWASP", "A05")] public sealed class SecurityMisconfigurationTests : SecurityTestBase { [Fact(DisplayName = "A05-001: Debug mode should be disabled in production config")] public void DebugMode_ShouldBeDisabledInProduction() { // Arrange var productionConfig = LoadConfiguration("production"); // Assert productionConfig.Should().NotContainKey("Debug"); productionConfig.GetValueOrDefault("ASPNETCORE_ENVIRONMENT").Should().NotBe("Development"); } [Fact(DisplayName = "A05-002: Error details should not leak in production")] public void ErrorDetails_ShouldNotLeakInProduction() { // Arrange var productionConfig = LoadConfiguration("production"); // Assert productionConfig.GetValueOrDefault("DetailedErrors")?.Should().NotBe("true"); productionConfig.GetValueOrDefault("UseDeveloperExceptionPage")?.Should().NotBe("true"); } [Fact(DisplayName = "A05-003: Security headers should be configured")] public void SecurityHeaders_ShouldBeConfigured() { // Arrange var requiredHeaders = new[] { "X-Content-Type-Options", "X-Frame-Options", "X-XSS-Protection", "Strict-Transport-Security", "Content-Security-Policy" }; // Act var configuredHeaders = GetSecurityHeaders(); // Assert foreach (var header in requiredHeaders) { configuredHeaders.Should().ContainKey(header, $"Security header {header} should be configured"); } } [Fact(DisplayName = "A05-004: CORS should be restrictive")] public void Cors_ShouldBeRestrictive() { // Arrange var corsConfig = GetCorsConfiguration(); // Assert corsConfig.AllowedOrigins.Should().NotContain("*", "CORS should not allow all origins"); corsConfig.AllowCredentials.Should().BeTrue(); corsConfig.AllowedMethods.Should().NotContain("*", "CORS should specify explicit methods"); } [Fact(DisplayName = "A05-005: Default ports should not be used")] public void DefaultPorts_ShouldBeConfigurable() { // Arrange var portConfig = GetPortConfiguration(); // Assert portConfig.HttpsPort.Should().NotBe(443, "Default HTTPS port should be configurable"); portConfig.HttpPort.Should().BeNull("HTTP should be disabled or redirected"); } [Fact(DisplayName = "A05-006: Unnecessary features should be disabled")] public void UnnecessaryFeatures_ShouldBeDisabled() { // Arrange var disabledFeatures = new[] { "Swagger", // in production "GraphQLPlayground", // in production "TRACE", // HTTP method "OPTIONS" // unless needed for CORS }; // Act var enabledFeatures = GetEnabledFeatures("production"); // Assert foreach (var feature in disabledFeatures) { enabledFeatures.Should().NotContain(feature, $"Feature {feature} should be disabled in production"); } } [Fact(DisplayName = "A05-007: Directory listing should be disabled")] public void DirectoryListing_ShouldBeDisabled() { // Arrange var staticFileConfig = GetStaticFileConfiguration(); // Assert staticFileConfig.EnableDirectoryBrowsing.Should().BeFalse( "Directory listing should be disabled"); } [Fact(DisplayName = "A05-008: Admin endpoints should require authentication")] public void AdminEndpoints_ShouldRequireAuth() { // Arrange var adminEndpoints = new[] { "/admin", "/api/admin", "/api/v1/admin", "/manage", "/actuator" }; // Act & Assert foreach (var endpoint in adminEndpoints) { var requiresAuth = EndpointRequiresAuthentication(endpoint); requiresAuth.Should().BeTrue( $"Admin endpoint {endpoint} should require authentication"); } } [Fact(DisplayName = "A05-009: Cookie security flags should be set")] public void CookieSecurityFlags_ShouldBeSet() { // Arrange var cookieConfig = GetCookieConfiguration(); // Assert cookieConfig.Secure.Should().BeTrue("Cookies should be secure"); cookieConfig.HttpOnly.Should().BeTrue("Cookies should be HttpOnly"); cookieConfig.SameSite.Should().Be("Strict", "SameSite should be Strict"); } [Fact(DisplayName = "A05-010: Cloud metadata endpoints should be blocked")] public void CloudMetadataEndpoints_ShouldBeBlocked() { // Arrange var metadataEndpoints = new[] { "http://169.254.169.254/", // AWS, Azure, GCP "http://metadata.google.internal/", "http://100.100.100.200/" // Alibaba Cloud }; // Act & Assert foreach (var endpoint in metadataEndpoints) { var isBlocked = IsOutboundUrlBlocked(endpoint); isBlocked.Should().BeTrue( $"Cloud metadata endpoint {endpoint} should be blocked"); } } // Helper methods private static Dictionary LoadConfiguration(string environment) { // Simulated production configuration return new Dictionary { ["ASPNETCORE_ENVIRONMENT"] = "Production", ["DetailedErrors"] = "false", ["UseDeveloperExceptionPage"] = "false" }; } private static Dictionary GetSecurityHeaders() { return new Dictionary { ["X-Content-Type-Options"] = "nosniff", ["X-Frame-Options"] = "DENY", ["X-XSS-Protection"] = "1; mode=block", ["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains", ["Content-Security-Policy"] = "default-src 'self'" }; } private static CorsConfig GetCorsConfiguration() { return new CorsConfig( AllowedOrigins: new[] { "https://app.stella-ops.org" }, AllowCredentials: true, AllowedMethods: new[] { "GET", "POST", "PUT", "DELETE" } ); } private static PortConfig GetPortConfiguration() { return new PortConfig(HttpsPort: 8443, HttpPort: null); } private static string[] GetEnabledFeatures(string environment) { if (environment == "production") { return new[] { "HealthChecks", "Metrics", "API" }; } return new[] { "Swagger", "HealthChecks", "Metrics", "API", "GraphQLPlayground" }; } private static StaticFileConfig GetStaticFileConfiguration() { return new StaticFileConfig(EnableDirectoryBrowsing: false); } private static bool EndpointRequiresAuthentication(string endpoint) { // All admin endpoints require authentication return endpoint.Contains("admin", StringComparison.OrdinalIgnoreCase) || endpoint.Contains("manage", StringComparison.OrdinalIgnoreCase) || endpoint.Contains("actuator", StringComparison.OrdinalIgnoreCase); } private static CookieConfig GetCookieConfiguration() { return new CookieConfig(Secure: true, HttpOnly: true, SameSite: "Strict"); } private static bool IsOutboundUrlBlocked(string url) { var blockedPrefixes = new[] { "http://169.254.", "http://metadata.", "http://100.100.100.200" }; return blockedPrefixes.Any(p => url.StartsWith(p, StringComparison.OrdinalIgnoreCase)); } private record CorsConfig(string[] AllowedOrigins, bool AllowCredentials, string[] AllowedMethods); private record PortConfig(int HttpsPort, int? HttpPort); private record StaticFileConfig(bool EnableDirectoryBrowsing); private record CookieConfig(bool Secure, bool HttpOnly, string SameSite); }