Files
Charon/backend/internal/services/security_score_test.go
2026-03-04 18:34:49 +00:00

167 lines
5.9 KiB
Go

package services
import (
"testing"
"github.com/Wikid82/charon/backend/internal/models"
"github.com/stretchr/testify/assert"
)
func TestCalculateSecurityScore_AllEnabled(t *testing.T) {
profile := &models.SecurityHeaderProfile{
HSTSEnabled: true,
HSTSMaxAge: 63072000,
HSTSIncludeSubdomains: true,
HSTSPreload: true,
CSPEnabled: true,
CSPDirectives: `{"default-src":["'self'"],"script-src":["'self'"],"style-src":["'self'"]}`,
XFrameOptions: "DENY",
XContentTypeOptions: true,
ReferrerPolicy: "no-referrer",
PermissionsPolicy: `[{"feature":"camera","allowlist":[]}]`,
CrossOriginOpenerPolicy: "same-origin",
CrossOriginResourcePolicy: "same-origin",
CrossOriginEmbedderPolicy: "require-corp",
}
result := CalculateSecurityScore(profile)
assert.Equal(t, 100, result.TotalScore)
assert.Equal(t, 100, result.MaxScore)
assert.Equal(t, 25, result.Breakdown["hsts"])
assert.Equal(t, 25, result.Breakdown["csp"])
assert.Equal(t, 10, result.Breakdown["x_frame_options"])
assert.Equal(t, 10, result.Breakdown["x_content_type_options"])
assert.Equal(t, 10, result.Breakdown["referrer_policy"])
assert.Equal(t, 10, result.Breakdown["permissions_policy"])
assert.Equal(t, 10, result.Breakdown["cross_origin"])
assert.Empty(t, result.Suggestions)
}
func TestCalculateSecurityScore_HSTSOnly(t *testing.T) {
profile := &models.SecurityHeaderProfile{
HSTSEnabled: true,
HSTSMaxAge: 31536000,
HSTSIncludeSubdomains: true,
HSTSPreload: false,
CSPEnabled: false,
XFrameOptions: "SAMEORIGIN",
XContentTypeOptions: true,
ReferrerPolicy: "strict-origin-when-cross-origin",
}
result := CalculateSecurityScore(profile)
assert.Equal(t, 20, result.Breakdown["hsts"]) // 10 + 5 + 5, missing preload
assert.Equal(t, 0, result.Breakdown["csp"])
assert.Equal(t, 7, result.Breakdown["x_frame_options"]) // SAMEORIGIN = 7 points
assert.Equal(t, 10, result.Breakdown["x_content_type_options"])
assert.Equal(t, 10, result.Breakdown["referrer_policy"])
assert.Equal(t, 0, result.Breakdown["permissions_policy"])
assert.Equal(t, 0, result.Breakdown["cross_origin"])
assert.Contains(t, result.Suggestions, "Consider HSTS preload for browser preload lists")
assert.Contains(t, result.Suggestions, "Enable Content-Security-Policy")
}
func TestCalculateSecurityScore_NoHeaders(t *testing.T) {
profile := &models.SecurityHeaderProfile{
HSTSEnabled: false,
CSPEnabled: false,
XFrameOptions: "",
XContentTypeOptions: false,
ReferrerPolicy: "",
}
result := CalculateSecurityScore(profile)
assert.Equal(t, 0, result.TotalScore)
assert.Equal(t, 100, result.MaxScore)
assert.Contains(t, result.Suggestions, "Enable HSTS to enforce HTTPS")
assert.Contains(t, result.Suggestions, "Enable Content-Security-Policy")
assert.Contains(t, result.Suggestions, "Set X-Frame-Options to DENY or SAMEORIGIN")
assert.Contains(t, result.Suggestions, "Enable X-Content-Type-Options: nosniff")
}
func TestCalculateSecurityScore_UnsafeCSP(t *testing.T) {
profile := &models.SecurityHeaderProfile{
HSTSEnabled: true,
HSTSMaxAge: 31536000,
CSPEnabled: true,
CSPDirectives: `{"default-src":["'self'"],"script-src":["'self'","'unsafe-inline'","'unsafe-eval'"]}`,
XFrameOptions: "DENY",
XContentTypeOptions: true,
ReferrerPolicy: "strict-origin",
}
result := CalculateSecurityScore(profile)
assert.Equal(t, 15, result.Breakdown["csp"]) // Base 15, no bonus for unsafe directives
assert.Contains(t, result.Suggestions, "Avoid 'unsafe-inline' in CSP for better security")
assert.Contains(t, result.Suggestions, "Avoid 'unsafe-eval' in CSP for better security")
}
func TestCalculateSecurityScore_PartialCrossOrigin(t *testing.T) {
profile := &models.SecurityHeaderProfile{
HSTSEnabled: true,
HSTSMaxAge: 31536000,
CSPEnabled: false,
XFrameOptions: "DENY",
XContentTypeOptions: true,
ReferrerPolicy: "strict-origin",
CrossOriginOpenerPolicy: "same-origin",
CrossOriginResourcePolicy: "same-origin",
}
result := CalculateSecurityScore(profile)
assert.Equal(t, 7, result.Breakdown["cross_origin"]) // 4 + 3, missing embedder
assert.Contains(t, result.Suggestions, "Add Cross-Origin isolation headers")
}
func TestCalculateSecurityScore_WeakReferrerPolicy(t *testing.T) {
profile := &models.SecurityHeaderProfile{
HSTSEnabled: true,
HSTSMaxAge: 31536000,
XFrameOptions: "DENY",
XContentTypeOptions: true,
ReferrerPolicy: "origin-when-cross-origin",
}
result := CalculateSecurityScore(profile)
assert.Equal(t, 7, result.Breakdown["referrer_policy"])
assert.Contains(t, result.Suggestions, "Use a stricter Referrer-Policy")
}
func TestCalculateSecurityScore_UnknownReferrerPolicy(t *testing.T) {
profile := &models.SecurityHeaderProfile{
HSTSEnabled: true,
HSTSMaxAge: 31536000,
XFrameOptions: "DENY",
XContentTypeOptions: true,
ReferrerPolicy: "unsafe-url",
}
result := CalculateSecurityScore(profile)
assert.Equal(t, 3, result.Breakdown["referrer_policy"]) // Non-empty but not strict
assert.Contains(t, result.Suggestions, "Use a stricter Referrer-Policy")
}
func TestCalculateSecurityScore_ShortHSTSMaxAge(t *testing.T) {
profile := &models.SecurityHeaderProfile{
HSTSEnabled: true,
HSTSMaxAge: 86400, // 1 day - too short
HSTSIncludeSubdomains: false,
XFrameOptions: "DENY",
XContentTypeOptions: true,
ReferrerPolicy: "strict-origin",
}
result := CalculateSecurityScore(profile)
assert.Equal(t, 10, result.Breakdown["hsts"]) // Only base score, no bonus
assert.Contains(t, result.Suggestions, "Increase HSTS max-age to at least 1 year")
assert.Contains(t, result.Suggestions, "Enable HSTS for subdomains")
}