From 1002bcbde2c463151956273967b8e0ebb08e959e Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Thu, 26 Mar 2026 10:22:50 +0700 Subject: [PATCH 1/2] Fix RBL score: return A+ when not listed on any blocklist Move the ListedCount check before scoringListCount calculation so we return early with a perfect score when the IP/domain is not listed, regardless of how many informational-only lists exist. Co-Authored-By: Claude Opus 4.6 --- pkg/analyzer/rbl.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/analyzer/rbl.go b/pkg/analyzer/rbl.go index 08d3b8f..64db1f7 100644 --- a/pkg/analyzer/rbl.go +++ b/pkg/analyzer/rbl.go @@ -305,11 +305,11 @@ func (r *DNSListChecker) CalculateScore(results *DNSListResults) (int, string) { return 100, "" } - scoringListCount := len(r.Lists) - len(r.informationalSet) - if scoringListCount <= 0 { + if results.ListedCount <= 0 { return 100, "A+" } + scoringListCount := len(r.Lists) - len(r.informationalSet) percentage := 100 - results.RelevantListedCount*100/scoringListCount return percentage, ScoreToGrade(percentage) } From 297fcaef198516df441ece442340c7286115be28 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Thu, 26 Mar 2026 10:31:09 +0700 Subject: [PATCH 2/2] Incorporate DNSWL (whitelist) grade into blacklist scoring CalculateScore now accepts a forWhitelist flag to handle whitelist scoring logic separately. The final blacklist grade combines both RBL and DNSWL results using MinGrade for a more accurate reputation assessment. --- pkg/analyzer/analyzer.go | 2 +- pkg/analyzer/rbl.go | 15 +++++++++++++-- pkg/analyzer/report.go | 6 ++++-- pkg/analyzer/scoring.go | 2 ++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 54d9e42..f21d1f8 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -138,7 +138,7 @@ func (a *APIAdapter) CheckBlacklistIP(ip string) ([]api.BlacklistCheck, []api.Bl IPsChecked: []string{ip}, ListedCount: listedCount, } - score, grade := a.analyzer.generator.rblChecker.CalculateScore(results) + score, grade := a.analyzer.generator.rblChecker.CalculateScore(results, false) // Check the IP against all configured DNSWLs (informational only) whitelists, _, err := a.analyzer.generator.dnswlChecker.CheckIP(ip) diff --git a/pkg/analyzer/rbl.go b/pkg/analyzer/rbl.go index 64db1f7..47e74e0 100644 --- a/pkg/analyzer/rbl.go +++ b/pkg/analyzer/rbl.go @@ -300,7 +300,19 @@ func (r *DNSListChecker) reverseIP(ipStr string) string { // CalculateScore calculates the list contribution to deliverability. // Informational lists are not counted in the score. -func (r *DNSListChecker) CalculateScore(results *DNSListResults) (int, string) { +func (r *DNSListChecker) CalculateScore(results *DNSListResults, forWhitelist bool) (int, string) { + scoringListCount := len(r.Lists) - len(r.informationalSet) + + if forWhitelist { + if results.ListedCount >= scoringListCount { + return 100, "A++" + } else if results.ListedCount > 0 { + return 100, "A+" + } else { + return 95, "A" + } + } + if results == nil || len(results.IPsChecked) == 0 { return 100, "" } @@ -309,7 +321,6 @@ func (r *DNSListChecker) CalculateScore(results *DNSListResults) (int, string) { return 100, "A+" } - scoringListCount := len(r.Lists) - len(r.informationalSet) percentage := 100 - results.RelevantListedCount*100/scoringListCount return percentage, ScoreToGrade(percentage) } diff --git a/pkg/analyzer/report.go b/pkg/analyzer/report.go index 6dcf588..7332307 100644 --- a/pkg/analyzer/report.go +++ b/pkg/analyzer/report.go @@ -141,8 +141,10 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu blacklistScore := 0 var blacklistGrade string + var whitelistGrade string if results.RBL != nil { - blacklistScore, blacklistGrade = r.rblChecker.CalculateScore(results.RBL) + blacklistScore, blacklistGrade = r.rblChecker.CalculateScore(results.RBL, false) + _, whitelistGrade = r.dnswlChecker.CalculateScore(results.DNSWL, true) } saScore, saGrade := r.spamAnalyzer.CalculateSpamAssassinScore(results.SpamAssassin) @@ -173,7 +175,7 @@ func (r *ReportGenerator) GenerateReport(testID uuid.UUID, results *AnalysisResu AuthenticationScore: authScore, AuthenticationGrade: api.ScoreSummaryAuthenticationGrade(authGrade), BlacklistScore: blacklistScore, - BlacklistGrade: api.ScoreSummaryBlacklistGrade(blacklistGrade), + BlacklistGrade: api.ScoreSummaryBlacklistGrade(MinGrade(blacklistGrade, whitelistGrade)), ContentScore: contentScore, ContentGrade: api.ScoreSummaryContentGrade(contentGrade), HeaderScore: headerScore, diff --git a/pkg/analyzer/scoring.go b/pkg/analyzer/scoring.go index 798590f..5568c8e 100644 --- a/pkg/analyzer/scoring.go +++ b/pkg/analyzer/scoring.go @@ -73,6 +73,8 @@ func ScoreToReportGrade(score int) api.ReportGrade { // gradeRank returns a numeric rank for a grade (lower = worse) func gradeRank(grade string) int { switch grade { + case "A++": + return 7 case "A+": return 6 case "A":