Guard DSN
RFC 3464 Delivery Status Notification parser. Reads the multipart/report; report-type=delivery-status structure that bounces, delayed-delivery notices, and successful-delivery confirmations carry and surfaces the per-recipient action + enhanced status code so operator-side delivery-failure routing (b.mail.bounce retry curve, address-book invalidation, mailing- list cleanup, transactional-mail dead-letter handling) reads a stable shape regardless of MTA wording.
## RFC 3464 structure
multipart/report per RFC 6522 §3:
1. text/plain (or text/html) — human-readable wording ("Your message could not be delivered to alice@example.com"); the framework does NOT route on this prose. 2. **message/delivery-status** (RFC 3464 §2) — the machine-readable DSN body the framework parses. 3. Optional message/rfc822 (or text/rfc822-headers) — the original message (or its headers) that bounced.
## Required fields the parser extracts
**Per-message fields (RFC 3464 §2.2)**: - Reporting-MTA — MTA that issued the DSN. Mandatory. - Original-Envelope-Id (optional) — DSN-tied envelope id. - Arrival-Date (optional) — when the original message arrived at the reporting MTA.
**Per-recipient fields (RFC 3464 §2.3)** — repeated, one block per recipient: - Final-Recipient — recipient address as the reporting MTA knows it. Mandatory. - Action — failed / delayed / delivered / relayed / expanded. Mandatory. - Status — RFC 3463 enhanced status code, format D.D[D[D]].D[D[D]] (e.g. 5.1.1 = bad address). - Original-Recipient (optional). - Diagnostic-Code (optional) — raw MTA error line.
## RFC 3463 status-class semantics
The first digit classifies the verdict and drives the framework's downstream routing:
- **2.x.y** — success (delivered / relayed / expanded). Used by mailing-list verp tracking + delivery-receipt auditing. - **4.x.y** — persistent transient failure. Operator's b.outbox retry curve applies; address stays valid. - **5.x.y** — permanent failure. Address-book invalidation trigger; mailing-list cleanup; no further retries.
The framework surfaces statusClass (success / temporary / permanent) so operator routing reads one shape regardless of the exact subcode.
## Defenses
- **Oversize DSN** — bounded body cap (default 256 KiB strict) per the profile; legitimate DSNs are KB-scale, multi-MB DSNs are pathological / DoS-shaped. - **Recipient-count cap** — per-DSN recipient cap (default 256 strict). A DSN with thousands of recipients is forged or misconfigured; operator opts permissive for mailing-list blast-bounces. - **Header-line cap** — each field-line capped at 998 bytes per RFC 5322 §2.1.1. - **CRLF + control-char refusal** — header injection defense for fields that propagate to operator's audit log / monitoring dashboard.
## CVE / threat model
- **Bounce-flood / backscatter** — operator's MX should refuse mail with envelope-from that doesn't pass SPF before generating a DSN (the existing b.mail.bounce primitive does this); this guard parses INBOUND DSNs and gates the parse surface bounds, not the bounce-generation policy. - **DSN header-injection class** (CVE-2026-32178 — .NET CWE-138 special-element / header-injection spoofing, the System.Net.Mail vector per MSRC, at outbound; the inbound parse path here) — refuses CR/LF/NUL/C0 in header lines. - **CSAF / iSchedule prose tampering** — operator inspecting the prose part for the original recipient runs into the ambiguous wording that DSNs vary across MTAs (Postfix vs Exchange vs SES vs Gmail). The parser surfaces the STRUCTURED fields so operator routing doesn't have to regex MTA-specific prose.
b.guardDsn.parse(deliveryStatusBody, opts?) #
{
profile: "strict" | "balanced" | "permissive",
posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
}
Parse a message/delivery-status body (the MIME part body, not the entire RFC 3464 multipart/report — extract that via b.safeMime.parse first). Returns { perMessage, perRecipients, worstStatusClass, action }.
Throws GuardDsnError on oversize body / recipient count / header-line length / malformed status code / required-field missing / control-char in field value.
var mime = b.safeMime.parse(rawBouncedMessage);
var deliveryStatusPart = b.safeMime.findFirst(mime, function (p) {
return p.leaf && p.leaf.contentType === "message/delivery-status";
});
var dsn = b.guardDsn.parse(deliveryStatusPart.leaf.body);
if (dsn.worstStatusClass === "permanent") {
dsn.perRecipients.forEach(function (r) { invalidateAddress(r.finalRecipient); });
}
b.guardDsn.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.guardDsn.compliancePosture("hipaa"); // → "strict"
b.guardDsn.compliancePosture("not-a-regime"); // → null
Last updated 2026-08-08T16:39:49.652Z by seeder.