From abfd1f0155e74a51a185c998f418acaa7c7433ed Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Wed, 22 Oct 2025 14:06:18 +0700 Subject: [PATCH] Add a score to DNS --- api/openapi.yaml | 7 ++ pkg/analyzer/dns.go | 100 +++++++++++++++++++ pkg/analyzer/report.go | 6 ++ web/src/lib/components/DnsRecordsCard.svelte | 16 ++- web/src/lib/components/ScoreCard.svelte | 14 +++ web/src/routes/test/[test]/+page.svelte | 5 +- 6 files changed, 143 insertions(+), 5 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index 0432da1..a3649ef 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -296,12 +296,19 @@ components: ScoreSummary: type: object required: + - dns_score - authentication_score - spam_score - blacklist_score - content_score - header_score properties: + dns_score: + type: integer + minimum: 0 + maximum: 100 + description: DNS records score (in percentage) + example: 42 authentication_score: type: integer minimum: 0 diff --git a/pkg/analyzer/dns.go b/pkg/analyzer/dns.go index 27566cf..42ce6b4 100644 --- a/pkg/analyzer/dns.go +++ b/pkg/analyzer/dns.go @@ -441,3 +441,103 @@ func (d *DNSAnalyzer) validateBIMI(record string) bool { return true } + +// CalculateDNSScore calculates the DNS score from records results +// Returns a score from 0-100 where higher is better +func (d *DNSAnalyzer) CalculateDNSScore(results *api.DNSResults) int { + if results == nil { + return 0 + } + + score := 0 + + // TODO: 20 points for correct PTR and A/AAAA + + // MX Records: 20 points + // Having valid MX records is critical for email deliverability + if results.MxRecords != nil && len(*results.MxRecords) > 0 { + hasValidMX := false + for _, mx := range *results.MxRecords { + if mx.Valid { + hasValidMX = true + break + } + } + if hasValidMX { + score += 20 + } + } + + // SPF Record: 20 points + // SPF is essential for email authentication + if results.SpfRecord != nil { + if results.SpfRecord.Valid { + score += 20 + } else if results.SpfRecord.Record != nil { + // Partial credit if SPF record exists but has issues + score += 5 + } + } + + // DKIM Records: 20 points + // DKIM provides strong email authentication + if results.DkimRecords != nil && len(*results.DkimRecords) > 0 { + hasValidDKIM := false + for _, dkim := range *results.DkimRecords { + if dkim.Valid { + hasValidDKIM = true + break + } + } + if hasValidDKIM { + score += 20 + } else { + // Partial credit if DKIM record exists but has issues + score += 5 + } + } + + // DMARC Record: 20 points + // DMARC ties SPF and DKIM together and provides policy + if results.DmarcRecord != nil { + if results.DmarcRecord.Valid { + score += 15 + // Bonus points for stricter policies + if results.DmarcRecord.Policy != nil { + switch *results.DmarcRecord.Policy { + case "reject": + // Strictest policy - full points already awarded + score += 5 + case "quarantine": + // Good policy - no deduction + case "none": + // Weakest policy - deduct 5 points + score -= 5 + } + } + } else if results.DmarcRecord.Record != nil { + // Partial credit if DMARC record exists but has issues + score += 5 + } + } + + // BIMI Record: 5 bonus points + // BIMI is optional but indicates advanced email branding + if results.BimiRecord != nil && results.BimiRecord.Valid { + if score >= 100 { + return 100 + } + } + + // Ensure score doesn't exceed maximum + if score > 100 { + score = 100 + } + + // Ensure score is non-negative + if score < 0 { + score = 0 + } + + return score +} diff --git a/pkg/analyzer/report.go b/pkg/analyzer/report.go index 9cefcbd..6e38bce 100644 --- a/pkg/analyzer/report.go +++ b/pkg/analyzer/report.go @@ -95,6 +95,11 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu } // Calculate scores directly from analyzers (no more checks array) + dnsScore := 0 + if results.DNS != nil { + dnsScore = r.dnsAnalyzer.CalculateDNSScore(results.DNS) + } + authScore := 0 if results.Authentication != nil { authScore = r.authAnalyzer.CalculateAuthenticationScore(results.Authentication) @@ -121,6 +126,7 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu } report.Summary = &api.ScoreSummary{ + DnsScore: dnsScore, AuthenticationScore: authScore, BlacklistScore: blacklistScore, ContentScore: contentScore, diff --git a/web/src/lib/components/DnsRecordsCard.svelte b/web/src/lib/components/DnsRecordsCard.svelte index 7c624ed..ac5e68f 100644 --- a/web/src/lib/components/DnsRecordsCard.svelte +++ b/web/src/lib/components/DnsRecordsCard.svelte @@ -3,16 +3,24 @@ interface Props { dnsResults?: DNSResults; + dnsScore?: number; } - let { dnsResults }: Props = $props(); + let { dnsResults, dnsScore }: Props = $props();
-

- - DNS Records +

+ + + DNS Records + + {#if dnsScore !== undefined} + + {dnsScore}% + + {/if}

diff --git a/web/src/lib/components/ScoreCard.svelte b/web/src/lib/components/ScoreCard.svelte index fb0912f..7555b8c 100644 --- a/web/src/lib/components/ScoreCard.svelte +++ b/web/src/lib/components/ScoreCard.svelte @@ -36,6 +36,20 @@ {#if summary}
+
+
+ = 100} + class:text-warning={summary.dns_score < 100 && + summary.dns_score >= 50} + class:text-danger={summary.dns_score < 50} + > + {summary.dns_score}% + + DNS +
+
- +
{/if}