From 8e38dc7892efa59e4d77123352a74dc0f3ef78bb Mon Sep 17 00:00:00 2001 From: TJBK Date: Fri, 15 May 2026 19:35:30 +0100 Subject: [PATCH 01/15] Add copyable audit report --- entrypoints/popup/main.ts | 69 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/entrypoints/popup/main.ts b/entrypoints/popup/main.ts index 47ed131..9becd62 100644 --- a/entrypoints/popup/main.ts +++ b/entrypoints/popup/main.ts @@ -100,6 +100,11 @@ function statusLabel(status: HealthStatus): string { } } +function gradeStatusLabel(status: GradeLine['status']): string { + if (status === 'info') return 'Info'; + return statusLabel(status); +} + function truncate(s: string, max: number): string { const t = s.trim(); if (t.length <= max) return t; @@ -142,6 +147,7 @@ function renderResultFooterActions(result: CheckResult): string { return ` `; } +function renderFixGuidance(title: 'SPF' | 'DMARC' | 'DKIM', result: CheckResult): string { + const score = title === 'SPF' + ? result.full.spf + : title === 'DMARC' + ? result.full.dmarc + : result.full.dkim; + if (score.status === 'pass') return ''; + + const host = title === 'DMARC' + ? `_dmarc.${result.dmarcLookupHost}` + : result.queryHostname; + let guidance = ''; + if (title === 'SPF') { + const providerInclude = result.spfMailProviderHint?.expectedInclude; + const example = providerInclude + ? `v=spf1 include:${providerInclude} -all` + : 'v=spf1 -all'; + guidance = `Publish one TXT record at ${host}. Example for a domain that sends no mail, or after adding approved senders: ${example}`; + } else if (title === 'DMARC') { + guidance = `Publish one TXT record at ${host}. Start with reporting, then move toward enforcement: v=DMARC1; p=none; rua=mailto:dmarc@example.com`; + } else { + guidance = 'Confirm the selector your mail platform signs with, then publish that selector under selector._domainkey. Custom selectors can be added in settings.'; + } + + return ` +
+ Fix guidance +

${escapeHtml(guidance)}

+
+ `; +} + function renderProtocolCard( title: string, score: FullScore['spf'], @@ -578,6 +610,7 @@ function renderResult(result: CheckResult): void { (detailedBreakdown || result.spfMailProviderHint.status !== 'pass') ? renderSpfMailProviderHint(result.spfMailProviderHint) : ''; + const spfFooter = `${spfSupplement}${renderFixGuidance('SPF', result)}`; root.innerHTML = `
@@ -601,7 +634,7 @@ function renderResult(result: CheckResult): void { result.spfBreakdown, detailedBreakdown, undefined, - spfSupplement || undefined, + spfFooter || undefined, )} ${renderProtocolCard( 'DMARC', @@ -611,6 +644,7 @@ function renderResult(result: CheckResult): void { result.dmarcBreakdown, detailedBreakdown, dmarcHint(result), + renderFixGuidance('DMARC', result) || undefined, )} ${renderProtocolCard( 'DKIM', @@ -619,6 +653,8 @@ function renderResult(result: CheckResult): void { dkimRaw, result.dkimBreakdown, detailedBreakdown, + undefined, + renderFixGuidance('DKIM', result) || undefined, )} ${result.mailInfra .map((c) => diff --git a/entrypoints/popup/style.css b/entrypoints/popup/style.css index 720b4cf..2f0236b 100644 --- a/entrypoints/popup/style.css +++ b/entrypoints/popup/style.css @@ -1103,6 +1103,30 @@ body { margin-bottom: 2px; } +.fix-guidance { + margin-top: 12px; + padding-top: 10px; + border-top: 1px solid var(--border); + font-size: 0.74rem; + color: var(--muted); +} + +.fix-guidance summary { + cursor: pointer; + font-weight: 700; + color: var(--accent); +} + +.fix-guidance summary:hover { + color: #7ea6ff; +} + +.fix-guidance p { + margin: 8px 0 0; + line-height: 1.45; + color: #a8afbf; +} + .badge { font-size: 0.65rem; font-weight: 600; diff --git a/lib/checks/mailProviderSpfHint.ts b/lib/checks/mailProviderSpfHint.ts index a2e7ccc..032936a 100644 --- a/lib/checks/mailProviderSpfHint.ts +++ b/lib/checks/mailProviderSpfHint.ts @@ -5,6 +5,7 @@ import type { HealthStatus } from '@/lib/score/common'; /** Supplementary SPF check vs inbound MX provider profile; does not affect scoring. */ export type SpfMailProviderHint = { providerName: string; + expectedInclude: string; status: HealthStatus; summary: string; lines: string[]; @@ -26,6 +27,7 @@ export function buildMailProviderSpfHint( if (orgSpfTxt.dnsState === 'error') { return { providerName, + expectedInclude: expected, status: 'fail', summary: 'SPF lookup failed', lines: [ @@ -40,6 +42,7 @@ export function buildMailProviderSpfHint( if (!spfA.present) { return { providerName, + expectedInclude: expected, status: 'missing', summary: 'No SPF on mail domain', lines: [ @@ -62,6 +65,7 @@ export function buildMailProviderSpfHint( // Summary already states the include; avoid repeating the same line below. return { providerName, + expectedInclude: expected, status: spfA.multipleRecords ? 'warn' : 'pass', summary: `Includes ${expected}`, lines, @@ -76,6 +80,7 @@ export function buildMailProviderSpfHint( ); return { providerName, + expectedInclude: expected, status: 'warn', summary: `Missing include:${expected}`, lines, From 10e9dde5138ce1dd19e0dc13679c3391becbf87c Mon Sep 17 00:00:00 2001 From: TJBK Date: Fri, 15 May 2026 19:38:55 +0100 Subject: [PATCH 04/15] Add custom DKIM selectors --- entrypoints/background.ts | 1 + entrypoints/popup/main.ts | 35 ++++++++++++++++++++++++++++++++++- entrypoints/popup/style.css | 25 +++++++++++++++++++++++++ lib/checkDomain.ts | 7 ++++++- lib/parse/dkim.test.ts | 14 ++++++++++++++ lib/parse/dkim.ts | 10 ++++++++++ lib/settings.ts | 20 ++++++++++++++++++++ 7 files changed, 110 insertions(+), 2 deletions(-) diff --git a/entrypoints/background.ts b/entrypoints/background.ts index 6f927bb..2e85dbf 100644 --- a/entrypoints/background.ts +++ b/entrypoints/background.ts @@ -56,6 +56,7 @@ async function refreshToolbarIconForTab( treatDnsResolutionErrorsAsFailure: settings.treatDnsResolutionErrorsAsFailure, dnsProvider: settings.dnsProvider, + customDkimSelectors: settings.customDkimSelectors, }); if (toolbarRefreshGenByTabId.get(tabId) !== token) { return; diff --git a/entrypoints/popup/main.ts b/entrypoints/popup/main.ts index 29a6a06..70a22d0 100644 --- a/entrypoints/popup/main.ts +++ b/entrypoints/popup/main.ts @@ -736,6 +736,13 @@ function renderSettings(): void {
Advanced
+