From 86d4d27f8002e85f29e3cea1faff07bbaf65ee43 Mon Sep 17 00:00:00 2001 From: Luke Steward <29278153+LukeSteward@users.noreply.github.com> Date: Fri, 15 May 2026 19:09:57 +0100 Subject: [PATCH 1/3] Add DMARC issue reporting functionality - Introduced functions to analyze DMARC records and generate issue links for the Wall of Shame. - Updated the modal to request the organization name instead of the company name for DMARC submissions. - Enhanced URL parameters for new issue creation to include DMARC record snippets and issue types. --- entrypoints/popup/main.ts | 64 +++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/entrypoints/popup/main.ts b/entrypoints/popup/main.ts index 4624dfb..ccb0891 100644 --- a/entrypoints/popup/main.ts +++ b/entrypoints/popup/main.ts @@ -7,6 +7,7 @@ import { } from '@/lib/checkDomain'; import { filterMailInfraLinesWhenCompact } from '@/lib/checks/mailInfra'; import type { SpfMailProviderHint } from '@/lib/checks/mailProviderSpfHint'; +import { analyzeDmarc } from '@/lib/parse/dmarc'; import { getActiveTabHostname } from '@/lib/tabHost'; import { filterBreakdownForCompactMode, @@ -115,26 +116,57 @@ function mxtoolboxEmailHealthUrl(domain: string): string { return `https://mxtoolbox.com/emailhealth/${encodeURIComponent(domain)}`; } +/** DMARC SuperTool deep link (matches Wall of Shame issue template placeholder). */ +function mxtoolboxDmarcLookupUrl(domain: string): string { + return `https://mxtoolbox.com/SuperTool.aspx?action=${encodeURIComponent(`dmarc:${domain}`)}`; +} + const DNS_TECHNIQUE_DISCLOSURE = 'DNS queries use DNS-over-HTTPS (Cloudflare / Google). Entra probe uses HTTPS only; no MTA-STS policy files or cert inspection. DKIM probes _domainkey for null DKIM, then provider/common selectors, then *._domainkey.'; -const WALL_OF_SHAME_REPO = 'jkerai1/DMARC-WallOfShame'; +const WALL_OF_SHAME_NEW_ISSUE = + 'https://github.com/jkerai1/DMARC-WallOfShame/issues/new'; + +/** Max chars for DMARC TXT prefilled via URL (avoid GitHub URI limits). */ +const WALL_OF_SHAME_DMARC_RECORD_URL_MAX = 3500; +function wallOfShameDmarcIssueType(result: CheckResult): string { + const a = analyzeDmarc(result.dmarcRecords); + if (a.multipleRecords) { + return 'Malformed / invalid DMARC record'; + } + if (!a.present) { + return 'No DMARC record (missing)'; + } + if (a.policy === 'none') { + return "DMARC policy set to 'none' (p=none)"; + } + return 'Malformed / invalid DMARC record'; +} + +function wallOfShameDmarcRecordSnippet(result: CheckResult): string { + if (!result.dmarcRecords.length) return ''; + const joined = + result.dmarcRecords.length === 1 + ? result.dmarcRecords[0] + : result.dmarcRecords.join('\n---\n'); + return truncate(joined, WALL_OF_SHAME_DMARC_RECORD_URL_MAX); +} -function wallOfShameNewIssueUrl(company: string, result: CheckResult): string { +function wallOfShameNewIssueUrl(orgName: string, result: CheckResult): string { const domain = result.dmarcLookupHost; - const title = `${company} (${domain})`; - const bodyParts = [ - `**Company:** ${company}`, - `**Domain:** ${domain}`, - ]; - if (result.queryHostname !== result.dmarcLookupHost) { - bodyParts.push(`**Checked hostname:** ${result.queryHostname}`); + const params = new URLSearchParams(); + params.set('template', 'dmarc_submission.yml'); + params.set('title', `[DMARC] ${domain}`); + params.set('org_name', orgName); + params.set('domain', domain); + params.set('issue_type', wallOfShameDmarcIssueType(result)); + const dmarcSnippet = wallOfShameDmarcRecordSnippet(result); + if (dmarcSnippet) { + params.set('dmarc_record', dmarcSnippet); } - bodyParts.push('', '_Submitted via JayQuery browser extension._'); - const body = bodyParts.join('\n'); - const params = new URLSearchParams({ title, body }); - return `https://github.com/${WALL_OF_SHAME_REPO}/issues/new?${params}`; + params.set('lookup_url', mxtoolboxDmarcLookupUrl(domain)); + return `${WALL_OF_SHAME_NEW_ISSUE}?${params}`; } /** Opens a URL from a user gesture (e.g. modal submit) without extra extension permissions. */ @@ -174,10 +206,10 @@ function renderCastShameModal(result: CheckResult): string {