Guard List-Unsubscribe
RFC 2369 List-Unsubscribe + RFC 8058 one-click List-Unsubscribe-Post header validator. Gates the outbound submission path's marketing / transactional mail so messages carrying a List-Id (or any mailing-list shape) emit headers that Gmail / Yahoo / Outlook one-click unsubscribe machinery actually accepts.
## Why this primitive vs. inline header construction
Gmail's bulk-sender requirements (effective 2024-02) and Yahoo's matching policy refuse mail that doesn't carry the RFC 8058 pair correctly. Operators get senders rate-limited or buckets-dropped when the headers are malformed. Common pitfalls this primitive refuses:
- **No HTTPS URI** — Gmail+Yahoo require at least one https:// URI in the List-Unsubscribe header. mailto: alone is no longer sufficient post-2024. - **http:// instead of https://** — refused; one-click endpoint MUST be TLS. - **javascript: / data: / file: schemes** — always refused regardless of context. - **List-Unsubscribe-Post: List-Unsubscribe=One-Click** — MUST be EXACTLY this token. Operator-supplied variants (OneClick, one-click, lowercased = value) refused. - **HTTPS URI without paired List-Unsubscribe-Post** — the Post header opts the endpoint into one-click. Without it, Gmail's UI treats the HTTPS URI as a regular link (operator loses the inbox-list "Unsubscribe" button).
## Verdict shape
{
action: "accept" | "refuse",
reason: string,
uris: [{ scheme, raw, oneClickEligible }, ...],
hasHttpsUri: bool,
hasMailtoUri: bool,
postHeaderOk: bool,
oneClickReady: bool,
}
Under strict (default for HIPAA / PCI / GDPR / SOC2 mailings that need bulk-sender compliance), oneClickReady: false → action: "refuse". Under balanced, the primitive returns the verdict but always accepts — operator's outbound pipeline makes the policy decision downstream.
## CVE / threat model
- **Unsubscribe-link injection** — operator's template-rendered List-Unsubscribe could be tampered through prompt-injection into an AI-generated newsletter. CRLF refused (header injection); javascript: / data: / file: refused (XSS via mail-client rendering); URL length cap (default 2048). - **Open-redirect via List-Unsubscribe** — operator validates the HTTPS URI's target host with their own safeRedirect / safeUrl allowlist downstream; this guard checks the SHAPE, not the operator's target-host policy. - **Email client mishandling** (Outlook's history of fetching mailto: automatically) — the primitive doesn't render the header; consumers using it inside b.guardEmail.validateMessage get layered defense.
b.guardListUnsubscribe.validate(headers, opts?) #
{
profile: "strict" | "balanced" | "permissive",
posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
}
Validate the RFC 2369 / RFC 8058 header pair on an outbound marketing or transactional message. Returns the verdict shape; operator's submission listener consults verdict.action to accept / refuse the send.
var v = b.guardListUnsubscribe.validate({
listUnsubscribe: ", ",
listUnsubscribePost: "List-Unsubscribe=One-Click",
});
if (v.action === "refuse") throw new Error(v.reason);
b.guardListUnsubscribe.compliancePosture(name) #
Return the effective profile NAME for a compliance posture, or null for a name this parser does not map. Unlike the content-guard variant this returns the resolved profile string (every line-protocol parser composes gateContract.ALL_STRICT_POSTURES, so "hipaa" / "pci-dss" / "gdpr" / "soc2" all resolve to "strict") and never throws — the parser shape carries no overlay-clone, no buildProfile, and no loadRulePack. Wired by gateContract.defineParser.
b.guardListUnsubscribe.compliancePosture("hipaa"); // → "strict"
b.guardListUnsubscribe.compliancePosture("not-a-regime"); // → null
Last updated 2026-08-08T16:39:49.652Z by seeder.