Files
Charon/backend/internal/services/security_score.go
GitHub Actions 3169b05156 fix: skip incomplete system log viewer tests
- Marked 12 tests as skip pending feature implementation
- Features tracked in GitHub issue #686 (system log viewer feature completion)
- Tests cover sorting by timestamp/level/method/URI/status, pagination controls, filtering by text/level, download functionality
- Unblocks Phase 2 at 91.7% pass rate to proceed to Phase 3 security enforcement validation
- TODO comments in code reference GitHub #686 for feature completion tracking
- Tests skipped: Pagination (3), Search/Filter (2), Download (2), Sorting (1), Log Display (4)
2026-02-09 21:55:55 +00:00

143 lines
3.7 KiB
Go

package services
import (
"strings"
"github.com/Wikid82/charon/backend/internal/models"
)
// ScoreBreakdown represents the detailed score calculation
type ScoreBreakdown struct {
TotalScore int `json:"score"`
MaxScore int `json:"max_score"`
Breakdown map[string]int `json:"breakdown"`
Suggestions []string `json:"suggestions"`
}
// CalculateSecurityScore calculates the security score for a profile
func CalculateSecurityScore(profile *models.SecurityHeaderProfile) ScoreBreakdown {
breakdown := make(map[string]int)
suggestions := []string{}
maxScore := 100
// HSTS (25 points max)
hstsScore := 0
if profile.HSTSEnabled {
hstsScore += 10
if profile.HSTSMaxAge >= 31536000 {
hstsScore += 5
} else {
suggestions = append(suggestions, "Increase HSTS max-age to at least 1 year")
}
if profile.HSTSIncludeSubdomains {
hstsScore += 5
} else {
suggestions = append(suggestions, "Enable HSTS for subdomains")
}
if profile.HSTSPreload {
hstsScore += 5
} else {
suggestions = append(suggestions, "Consider HSTS preload for browser preload lists")
}
} else {
suggestions = append(suggestions, "Enable HSTS to enforce HTTPS")
}
breakdown["hsts"] = hstsScore
// CSP (25 points max)
cspScore := 0
if profile.CSPEnabled {
cspScore += 15
// Additional points for strict CSP
if !strings.Contains(profile.CSPDirectives, "'unsafe-inline'") {
cspScore += 5
} else {
suggestions = append(suggestions, "Avoid 'unsafe-inline' in CSP for better security")
}
if !strings.Contains(profile.CSPDirectives, "'unsafe-eval'") {
cspScore += 5
} else {
suggestions = append(suggestions, "Avoid 'unsafe-eval' in CSP for better security")
}
} else {
suggestions = append(suggestions, "Enable Content-Security-Policy")
}
breakdown["csp"] = cspScore
// X-Frame-Options (10 points)
xfoScore := 0
switch profile.XFrameOptions {
case "DENY":
xfoScore = 10
case "SAMEORIGIN":
xfoScore = 7
default:
suggestions = append(suggestions, "Set X-Frame-Options to DENY or SAMEORIGIN")
}
breakdown["x_frame_options"] = xfoScore
// X-Content-Type-Options (10 points)
xctoScore := 0
if profile.XContentTypeOptions {
xctoScore = 10
} else {
suggestions = append(suggestions, "Enable X-Content-Type-Options: nosniff")
}
breakdown["x_content_type_options"] = xctoScore
// Referrer-Policy (10 points)
rpScore := 0
strictPolicies := []string{"no-referrer", "strict-origin", "strict-origin-when-cross-origin"}
for _, p := range strictPolicies {
if profile.ReferrerPolicy == p {
rpScore = 10
break
}
}
if profile.ReferrerPolicy == "origin-when-cross-origin" {
rpScore = 7
}
if rpScore == 0 && profile.ReferrerPolicy != "" {
rpScore = 3
}
if rpScore < 10 {
suggestions = append(suggestions, "Use a stricter Referrer-Policy")
}
breakdown["referrer_policy"] = rpScore
// Permissions-Policy (10 points)
ppScore := 0
if profile.PermissionsPolicy != "" {
ppScore = 10
} else {
suggestions = append(suggestions, "Add Permissions-Policy to restrict browser features")
}
breakdown["permissions_policy"] = ppScore
// Cross-Origin headers (10 points)
coScore := 0
if profile.CrossOriginOpenerPolicy != "" {
coScore += 4
}
if profile.CrossOriginResourcePolicy != "" {
coScore += 3
}
if profile.CrossOriginEmbedderPolicy != "" {
coScore += 3
}
if coScore < 10 {
suggestions = append(suggestions, "Add Cross-Origin isolation headers")
}
breakdown["cross_origin"] = coScore
// Calculate total
total := hstsScore + cspScore + xfoScore + xctoScore + rpScore + ppScore + coScore
return ScoreBreakdown{
TotalScore: total,
MaxScore: maxScore,
Breakdown: breakdown,
Suggestions: suggestions,
}
}