From 635cd292c2f092b12db3fc2804d37778f1e87f1a Mon Sep 17 00:00:00 2001 From: goetchstone Date: Thu, 18 Jun 2026 21:13:40 -0400 Subject: [PATCH 1/3] fix: harden DMARC checker DNS and DKIM checks Transient DNS errors no longer read as a missing record (was indistinguishable from NXDOMAIN); a probed TXT counts as DKIM only when it declares v=DKIM1 or p=. --- __tests__/dmarc-dns.test.ts | 33 ++++++++++++++++ app/api/tools/dmarc-check/route.ts | 63 +++++++++++++++++++++--------- lib/dmarc-dns.ts | 28 +++++++++++++ 3 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 __tests__/dmarc-dns.test.ts create mode 100644 lib/dmarc-dns.ts diff --git a/__tests__/dmarc-dns.test.ts b/__tests__/dmarc-dns.test.ts new file mode 100644 index 0000000..cc09700 --- /dev/null +++ b/__tests__/dmarc-dns.test.ts @@ -0,0 +1,33 @@ +// __tests__/dmarc-dns.test.ts +import { describe, it, expect } from "vitest"; +import { classifyDnsError, isDkimKeyRecord } from "@/lib/dmarc-dns"; + +describe("classifyDnsError", () => { + it("treats NXDOMAIN / no-data as a genuinely missing record", () => { + expect(classifyDnsError({ code: "ENOTFOUND" })).toBe("missing"); + expect(classifyDnsError({ code: "ENODATA" })).toBe("missing"); + }); + + it("treats resolver failures as transient, never a verdict", () => { + expect(classifyDnsError({ code: "ESERVFAIL" })).toBe("transient"); + expect(classifyDnsError({ code: "ETIMEOUT" })).toBe("transient"); + expect(classifyDnsError({ code: "ECONNREFUSED" })).toBe("transient"); + expect(classifyDnsError(new Error("boom"))).toBe("transient"); + expect(classifyDnsError(null)).toBe("transient"); + }); +}); + +describe("isDkimKeyRecord", () => { + it("accepts real DKIM key records", () => { + expect(isDkimKeyRecord("v=DKIM1; k=rsa; p=MIGfMA0GCSq")).toBe(true); + expect(isDkimKeyRecord("k=rsa; p=MIGfMA0GCSq")).toBe(true); // v= tag omitted + expect(isDkimKeyRecord("v=DKIM1; p=")).toBe(true); // revoked, still a DKIM record + }); + + it("rejects unrelated TXT records at the probed name", () => { + expect(isDkimKeyRecord("v=spf1 include:_spf.google.com -all")).toBe(false); + expect(isDkimKeyRecord("google-site-verification=abc123")).toBe(false); + expect(isDkimKeyRecord("MS=ms12345678")).toBe(false); + expect(isDkimKeyRecord("")).toBe(false); + }); +}); diff --git a/app/api/tools/dmarc-check/route.ts b/app/api/tools/dmarc-check/route.ts index 6f3b7b3..e506135 100644 --- a/app/api/tools/dmarc-check/route.ts +++ b/app/api/tools/dmarc-check/route.ts @@ -6,6 +6,7 @@ import { NextRequest, NextResponse } from "next/server"; import { promises as dns } from "node:dns"; import { rateLimit } from "@/server/rate-limit"; +import { classifyDnsError, isDkimKeyRecord } from "@/lib/dmarc-dns"; // Common DKIM selectors used by major mail providers. We probe these because // DKIM selector names aren't discoverable from the domain itself — DNS gives @@ -131,23 +132,40 @@ function isValidDomain(s: string): boolean { return re.test(s); } +// Resolve a TXT lookup, distinguishing "no such record" (return null) from a +// transient resolver failure (throw) so a SERVFAIL/timeout never masquerades as +// a missing record and falsely tanks the score. async function lookupTxt(host: string): Promise { try { return await dns.resolveTxt(host); - } catch { + } catch (err) { + if (classifyDnsError(err) === "transient") throw err; return null; } } async function lookupMx(domain: string): Promise<{ exchange: string; priority: number }[] | null> { try { - const records = await dns.resolveMx(domain); - return records.sort((a, b) => a.priority - b.priority); - } catch { + return (await dns.resolveMx(domain)).sort((a, b) => a.priority - b.priority); + } catch (err) { + if (classifyDnsError(err) === "transient") throw err; return null; } } +// DKIM selector probe — best-effort. A non-existent selector (the common case) +// or any resolver hiccup is simply "not found"; a present TXT counts only when +// it's actually a DKIM key, not a stray record sitting at the probed name. +async function probeDkim(selector: string, domain: string): Promise { + try { + const txt = await dns.resolveTxt(`${selector}._domainkey.${domain}`); + const record = txt.map((parts) => parts.join("")).find(isDkimKeyRecord); + return { selector, found: !!record, record }; + } catch { + return { selector, found: false }; + } +} + function findSpf(txtRecords: string[][] | null): string | null { if (!txtRecords) return null; // SPF records may be split across multiple strings in a single TXT record. @@ -320,19 +338,28 @@ export async function POST(request: NextRequest) { ); } - // Run all DNS lookups in parallel for speed. - const [rootTxt, dmarcTxt, mxRecords, ...dkimResults] = await Promise.all([ - lookupTxt(domain), - lookupTxt(`_dmarc.${domain}`), - lookupMx(domain), - ...DKIM_SELECTORS.map((selector) => - lookupTxt(`${selector}._domainkey.${domain}`).then((txt) => ({ - selector, - found: !!txt && txt.length > 0, - record: txt?.[0]?.join("") ?? undefined, - })) - ), - ]); + // Core records first: a transient resolver failure here must surface as an + // error, not as a false "everything is missing" verdict. + let rootTxt: string[][] | null; + let dmarcTxt: string[][] | null; + let mxRecords: { exchange: string; priority: number }[] | null; + try { + [rootTxt, dmarcTxt, mxRecords] = await Promise.all([ + lookupTxt(domain), + lookupTxt(`_dmarc.${domain}`), + lookupMx(domain), + ]); + } catch { + return NextResponse.json( + { error: "Couldn't complete the DNS lookup right now. Please try again in a moment." }, + { status: 503 } + ); + } + + // DKIM selector probes are best-effort and never fail the whole request. + const dkimResults = await Promise.all( + DKIM_SELECTORS.map((selector) => probeDkim(selector, domain)) + ); const spfRecord = findSpf(rootTxt); const spf = { record: spfRecord, ...analyzeSpf(spfRecord) }; @@ -340,7 +367,7 @@ export async function POST(request: NextRequest) { const dmarcRecord = findDmarc(dmarcTxt); const dmarcAnalysis = analyzeDmarc(dmarcRecord); - const dkimFound = (dkimResults as DkimResult[]).filter((r) => r.found); + const dkimFound = dkimResults.filter((r) => r.found); const dkimStatus: "ok" | "warn" | "missing" = dkimFound.length >= 1 ? "ok" : "missing"; diff --git a/lib/dmarc-dns.ts b/lib/dmarc-dns.ts new file mode 100644 index 0000000..ee0f4dd --- /dev/null +++ b/lib/dmarc-dns.ts @@ -0,0 +1,28 @@ +// lib/dmarc-dns.ts +// Pure DNS-record classification helpers for the email-auth checker. Kept out of +// the route handler so they can be unit-tested without performing live DNS. + +/** + * Classify a node:dns rejection. A record that genuinely doesn't exist must not + * be confused with a resolver that's momentarily failing — otherwise a SERVFAIL + * or timeout gets reported to the user as "you have no SPF/DMARC", tanking their + * score on a false negative. + */ +export function classifyDnsError(err: unknown): "missing" | "transient" { + const code = (err as { code?: string } | null)?.code; + // ENOTFOUND = NXDOMAIN; ENODATA = name exists but no record of this type. + // Both mean the record truly isn't there. Everything else (SERVFAIL, timeout, + // refused, …) is a resolver problem, not a verdict about the domain. + if (code === "ENOTFOUND" || code === "ENODATA") return "missing"; + return "transient"; +} + +/** + * True only when a TXT record at `._domainkey` is actually a DKIM key + * — it declares v=DKIM1 or publishes a p= public-key tag. Guards against + * counting an unrelated/leftover TXT at the probed name as a valid selector, + * which would otherwise award DKIM credit for a record that isn't DKIM. + */ +export function isDkimKeyRecord(record: string): boolean { + return /(^|;|\s)v=DKIM1\b/i.test(record) || /(^|;|\s)p=/i.test(record); +} From 6cfb974e962784de11d1d194b9d543163847caae Mon Sep 17 00:00:00 2001 From: goetchstone Date: Thu, 18 Jun 2026 21:13:40 -0400 Subject: [PATCH 2/3] feat: structured data, OG cards, real tool links Tool pages had no JSON-LD or social-card image and the blog JSON-LD pointed at 404 logo/og-default assets; services tool links were plain text, not anchors. --- app/layout.tsx | 3 +- app/services/page.tsx | 23 +++++++++-- app/tools/dmarc-check/page.tsx | 14 ++++++- app/tools/dmarc-report/page.tsx | 11 ++++- components/site/json-ld.tsx | 26 ++++++++++++ public/logo.png | Bin 0 -> 12256 bytes public/og-default.png | Bin 0 -> 52776 bytes scripts/gen-brand-assets.mjs | 70 ++++++++++++++++++++++++++++++++ 8 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 public/logo.png create mode 100644 public/og-default.png create mode 100644 scripts/gen-brand-assets.mjs diff --git a/app/layout.tsx b/app/layout.tsx index c1a7c2c..62089f9 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -40,8 +40,9 @@ export async function generateMetadata(): Promise { siteName: "Akritos", title: TITLE, description: DESCRIPTION, + images: ["/og-default.png"], }, - twitter: { card: "summary_large_image", title: TITLE, description: DESCRIPTION }, + twitter: { card: "summary_large_image", title: TITLE, description: DESCRIPTION, images: ["/og-default.png"] }, robots: { index: true, follow: true }, alternates: { canonical: "https://akritos.com" }, verification: { diff --git a/app/services/page.tsx b/app/services/page.tsx index 36260d1..27da329 100644 --- a/app/services/page.tsx +++ b/app/services/page.tsx @@ -27,8 +27,17 @@ import { ShieldAlert, Key, Mail, + type LucideIcon, } from "lucide-react"; +type ServiceEntry = { + icon: LucideIcon; + title: string; + description: string; + details: string[]; + link?: { label: string; href: string }; +}; + async function getCompanyName() { try { const setting = await db.setting.findUnique({ where: { key: "company_name" } }); @@ -95,7 +104,7 @@ const coreServices = [ }, ]; -const advisoryServices = [ +const advisoryServices: ServiceEntry[] = [ { icon: CreditCard, title: "Payment Processing & PCI Scope Reduction", @@ -155,8 +164,8 @@ const advisoryServices = [ "Vendor evaluation — data terms, audit trail, exit risk", "Team training — spotting confidently-wrong output", "SME workflow design — keeping the human as decision-maker", - "Linked: detailed page at /ai-risk", ], + link: { label: "Read the full AI risk & guardrails breakdown", href: "/ai-risk" }, }, { icon: Mail, @@ -169,8 +178,8 @@ const advisoryServices = [ "30-day monitoring period to catch legitimate senders we missed", "Move to p=quarantine or p=reject once alignment is clean", "Documentation you own — DNS records, provider settings, monitoring access", - "Linked: free checker tool at /tools/dmarc-check", ], + link: { label: "Check your domain with the free DMARC, SPF & DKIM checker", href: "/tools/dmarc-check" }, }, ]; @@ -364,6 +373,14 @@ export default async function ServicesPage() {

{s.description}

+ {s.link && ( + + {s.link.label} + + )}
    {s.details.map((d) => ( diff --git a/app/tools/dmarc-check/page.tsx b/app/tools/dmarc-check/page.tsx index 8bfb6cc..5a698ec 100644 --- a/app/tools/dmarc-check/page.tsx +++ b/app/tools/dmarc-check/page.tsx @@ -6,12 +6,14 @@ import Link from "next/link"; import { SiteNav } from "@/components/site/site-nav"; import { SiteFooter } from "@/components/site/site-footer"; import { DmarcCheckForm } from "@/components/tools/dmarc-check-form"; +import { JsonLd, softwareApplicationSchema, breadcrumbSchema } from "@/components/site/json-ld"; import { db } from "@/server/db"; import { ArrowRight, Mail, ShieldCheck, MailWarning } from "lucide-react"; const SITE_URL = "https://akritos.com"; const URL = `${SITE_URL}/tools/dmarc-check`; -const TITLE = "Free DMARC, SPF & DKIM Checker — Akritos"; +// No brand suffix here — the root layout's title template appends " | Akritos". +const TITLE = "Free DMARC, SPF & DKIM Checker"; const DESCRIPTION = "Free tool: enter your domain to instantly check SPF, DKIM, DMARC, and MX records. Plain-English explanations of what's wrong and how to fix it. No email required to use."; @@ -25,8 +27,9 @@ export const metadata: Metadata = { title: TITLE, description: DESCRIPTION, siteName: "Akritos", + images: [`${SITE_URL}/og-default.png`], }, - twitter: { card: "summary_large_image", title: TITLE, description: DESCRIPTION }, + twitter: { card: "summary_large_image", title: TITLE, description: DESCRIPTION, images: [`${SITE_URL}/og-default.png`] }, }; async function getCompanyName() { @@ -61,6 +64,13 @@ export default async function DmarcCheckPage() { return (
    + + {/* Hero + form */} diff --git a/app/tools/dmarc-report/page.tsx b/app/tools/dmarc-report/page.tsx index b59fc35..a808fdb 100644 --- a/app/tools/dmarc-report/page.tsx +++ b/app/tools/dmarc-report/page.tsx @@ -6,6 +6,7 @@ import Link from "next/link"; import { SiteNav } from "@/components/site/site-nav"; import { SiteFooter } from "@/components/site/site-footer"; import { DmarcReportForm } from "@/components/tools/dmarc-report-form"; +import { JsonLd, softwareApplicationSchema, breadcrumbSchema } from "@/components/site/json-ld"; import { db } from "@/server/db"; import { ArrowRight, FileSearch, ShieldCheck, Eye } from "lucide-react"; @@ -25,8 +26,9 @@ export const metadata: Metadata = { title: TITLE, description: DESCRIPTION, siteName: "Akritos", + images: [`${SITE_URL}/og-default.png`], }, - twitter: { card: "summary_large_image", title: TITLE, description: DESCRIPTION }, + twitter: { card: "summary_large_image", title: TITLE, description: DESCRIPTION, images: [`${SITE_URL}/og-default.png`] }, }; async function getCompanyName() { @@ -61,6 +63,13 @@ export default async function DmarcReportPage() { return (
    + + {/* Hero + tool */} diff --git a/components/site/json-ld.tsx b/components/site/json-ld.tsx index 56636e5..52e9052 100644 --- a/components/site/json-ld.tsx +++ b/components/site/json-ld.tsx @@ -176,3 +176,29 @@ export function breadcrumbSchema(items: { name: string; url: string }[]) { })), }; } + +export function softwareApplicationSchema(app: { + name: string; + description: string; + url: string; + category?: string; +}) { + return { + "@context": "https://schema.org", + "@type": "WebApplication", + name: app.name, + description: app.description, + url: app.url, + applicationCategory: app.category ?? "SecurityApplication", + operatingSystem: "Web", + browserRequirements: "Requires JavaScript.", + // `offers` (free), not a fabricated aggregateRating, is what makes the tool + // eligible for the Software App rich result — honest, no invented reviews. + offers: { "@type": "Offer", price: "0", priceCurrency: "USD" }, + provider: { + "@type": "Organization", + name: "Akritos Technology Partners LLC", + url: "https://akritos.com", + }, + }; +} diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..00950a9ba45e522b45cb6b7b02b14b297533b44e GIT binary patch literal 12256 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 literal 0 HcmV?d00001 diff --git a/public/og-default.png b/public/og-default.png new file mode 100644 index 0000000000000000000000000000000000000000..f1fd3e079db23e6bf576b86079157a07c987e8dd GIT binary patch literal 52776 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 literal 0 HcmV?d00001 diff --git a/scripts/gen-brand-assets.mjs b/scripts/gen-brand-assets.mjs new file mode 100644 index 0000000..4a739fb --- /dev/null +++ b/scripts/gen-brand-assets.mjs @@ -0,0 +1,70 @@ +// scripts/gen-brand-assets.mjs +// One-off generator for the static brand rasters: og-default.png (the Open +// Graph / Twitter card and the JSON-LD Article image fallback) and logo.png +// (the JSON-LD Organization logo). Re-run when the brand card changes: +// npx tsx scripts/gen-brand-assets.mjs +// Uses next/og (already a dependency) so there's no new tooling to maintain. + +import { createElement as h } from "react"; +import { ImageResponse } from "next/og"; +import { writeFile } from "node:fs/promises"; + +const MIDNIGHT = "#1C1F2E"; +const GOLD = "#C8A96E"; +const BONE = "#E8E4DC"; + +const ogCard = h( + "div", + { + style: { + width: "100%", + height: "100%", + display: "flex", + flexDirection: "column", + justifyContent: "space-between", + padding: 80, + background: MIDNIGHT, + color: BONE, + }, + }, + [ + h("div", { key: "top", style: { display: "flex", alignItems: "center", gap: 16 } }, [ + h("div", { key: "m", style: { width: 36, height: 36, background: GOLD, borderRadius: 2 } }), + h("div", { key: "w", style: { fontSize: 22, letterSpacing: "0.2em", textTransform: "uppercase", color: GOLD } }, "Akritos"), + ]), + h("div", { key: "mid", style: { display: "flex", flexDirection: "column", gap: 24 } }, [ + h("div", { key: "h", style: { fontSize: 60, fontWeight: 500, lineHeight: 1.1, maxWidth: 920 } }, "Technology Partners for Small Business"), + h("div", { key: "s", style: { fontSize: 28, color: "rgba(232, 228, 220, 0.6)", maxWidth: 920, lineHeight: 1.4 } }, "Apple Business, MDM, infrastructure, e-commerce — published rates, zero vendor markup, no lock-in."), + ]), + h("div", { key: "url", style: { fontSize: 20, color: "rgba(232, 228, 220, 0.4)" } }, "akritos.com"), + ] +); + +const logoMark = h( + "div", + { + style: { + width: "100%", + height: "100%", + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + gap: 28, + background: MIDNIGHT, + }, + }, + [ + h("div", { key: "m", style: { width: 96, height: 96, background: GOLD, borderRadius: 6 } }), + h("div", { key: "w", style: { fontSize: 64, fontWeight: 600, letterSpacing: "0.12em", color: BONE } }, "AKRITOS"), + ] +); + +async function toPng(node, size) { + const res = new ImageResponse(node, size); + return Buffer.from(await res.arrayBuffer()); +} + +await writeFile("public/og-default.png", await toPng(ogCard, { width: 1200, height: 630 })); +await writeFile("public/logo.png", await toPng(logoMark, { width: 512, height: 512 })); +console.log("wrote public/og-default.png and public/logo.png"); From 3ce330d400b0024ee7cb91c1a0668446c45b27f1 Mon Sep 17 00:00:00 2001 From: goetchstone Date: Thu, 18 Jun 2026 21:13:40 -0400 Subject: [PATCH 3/3] docs: log checker + OG metadata lessons --- docs/LESSONS.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/LESSONS.md b/docs/LESSONS.md index fda0820..e7a4659 100644 --- a/docs/LESSONS.md +++ b/docs/LESSONS.md @@ -226,6 +226,26 @@ Read at session start (loaded by `/boot`). Add to it whenever: --- +## 2026-06-18 — File-based `opengraph-image` doesn't cover pages that export their own `openGraph` + +**What happened:** Added a root `app/opengraph-image.tsx` as a site-wide default card. Curling `/tools/dmarc-check` showed no `og:image` — a page that exports its own `metadata.openGraph` object does not inherit an ancestor segment's `opengraph-image` file; the file convention only fills routes that don't define `openGraph` themselves. + +**Lesson:** For any page with custom `openGraph`/`twitter` metadata, set `images` explicitly. We dropped the dynamic route for one static `public/og-default.png` referenced from the root layout (default) and from each overriding page. Verify `og:image` with a real request — never assume inheritance. + +**Where it applies:** Every public page with custom OG/Twitter metadata. Brand rasters are regenerated via `scripts/gen-brand-assets.mjs`. + +--- + +## 2026-06-18 — DNS "no record" must be distinguished from a resolver failure + +**What happened:** The DMARC checker's `lookupTxt`/`lookupMx` swallowed every DNS error to `null`, so a transient SERVFAIL/timeout read identically to NXDOMAIN — telling a user on a flaky network they have no SPF/DMARC and tanking their score. Separately, any TXT at a probed `_domainkey` selector counted as a valid DKIM key (+20), even an unrelated record. + +**Lesson:** Classify DNS errors by code (`ENOTFOUND`/`ENODATA` = genuinely missing; anything else = transient → surface an error, don't assert "missing"). When probing for a record type, validate the content (a DKIM key declares `v=DKIM1` or `p=`) — presence at the name is not a match. + +**Where it applies:** `app/api/tools/dmarc-check/route.ts`, `lib/dmarc-dns.ts`. Any DNS-probe-based checker. + +--- + ## How to add to this file When you finish a task and a real lesson emerged, add an entry. Keep it terse. The point is to avoid repeating the mistake — not to write an essay. If the lesson is big enough to drive an architectural change, it goes in `docs/DECISIONS.md` instead. If it's about how the codebase works, update `CLAUDE.md`. If it's about how *we* work — it lives here.