From 6e273c2676ce29cef057d117e4427e031886e743 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Fri, 17 Jul 2026 10:39:17 -0500 Subject: v4.7.0: Add domain reputation/blocklist data source - New DomainReputationResult model (status: clean/listed/unknown, listed sources, checked-at) and a `reputation(domain:)` method on ExternalDataService, mirroring the existing pluggable-URL enrichment pattern (ownership history, DNS history, extended subdomains, pricing). With no endpoint configured (the default; DomainDig ships no bundled third-party reputation dependency) it resolves to unavailable rather than "clean". - New .reputation FeatureCapability/DataCapability, gated Pro+ like domainPricing. - Threaded reputation/reputationError through LookupSnapshot and HistoryEntry (backward-compatible decode) so results persist with history entries. - Auto-fetched in performLookup alongside pricing; surfaced as a "Reputation" info row, folded into DomainInsightEngine's risk score/factors and top-level insights (a listed domain raises risk score and adds a factor/insight), and exported in text, CSV, and JSON report output. - Reputation-driven risk changes ride the existing change-severity pipeline, so a listed status flip is visible to monitoring the same way any other risk delta is, without bespoke monitoring wiring. --- DomainDig/DomainInsightEngine.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'DomainDig/DomainInsightEngine.swift') diff --git a/DomainDig/DomainInsightEngine.swift b/DomainDig/DomainInsightEngine.swift index 2547727..f1de9c0 100644 --- a/DomainDig/DomainInsightEngine.swift +++ b/DomainDig/DomainInsightEngine.swift @@ -303,6 +303,19 @@ enum DomainInsightEngine { } } + if let reputation = snapshot.reputation { + switch reputation.status { + case .listed: + score += 25 + let sourceList = reputation.listedSources.isEmpty ? "" : " (\(reputation.listedSources.joined(separator: ", ")))" + factors.append(.init(description: "Domain is flagged by a configured reputation source\(sourceList)", impact: .negative)) + case .clean: + factors.append(.init(description: "Domain is clean against the configured reputation source", impact: .positive)) + case .unknown: + break + } + } + let clampedScore = min(max(score, 0), 100) let level: RiskLevel switch clampedScore { @@ -327,6 +340,9 @@ enum DomainInsightEngine { ) -> [String] { var items: [String] = [] + if snapshot.reputation?.status == .listed { + items.append("Domain is flagged by a configured reputation source") + } if let group = subdomainGroups.first(where: { $0.label == "staging" || $0.label == "dev" }) { items.append("Multiple \(group.label) subdomains suggest non-production environments are exposed") } -- cgit v1.2.3