Safe MIME
Bounded MIME parser substrate for the mail stack. Walks RFC 5322 + 2045 / 2046 / 2047 / 6532 (EAI) / 6533 (i18n-DSN) message structure into a part tree with caps on every dimension an attacker can grow to DoS the framework.
Foundation for everything above:
- b.mailStore.appendMessage parses inbound bytes via b.safeMime.parse(...) to extract headers + body parts before sealing per-column. - b.mail.server.mx runs every received message through b.safeMime.parse before SPF / DKIM / DMARC / ARC verification. - b.guardEmail.validateMessage already operates on raw bytes at the line-shape level; b.safeMime.parse is the structured follow-up that lets b.guardHtml / b.guardArchive / b.guardSvg inspect individual MIME parts. - b.mail.crypto.{pgp,smime} (v0.9.34a) parses signed/encrypted containers via this primitive before reaching the underlying crypto.
Defends CVE-2024-39929 (Exim MIME multipart parser) and CVE-2026-26312 (Stalwart nested message/rfc822 MIME OOM) by capping total parts, nesting depth, boundary length, header bytes, header-line bytes, decoded body bytes, message bytes — plus charset + transfer-encoding allowlists.
Throws SafeMimeError on every cap exceeded, malformed boundary, unknown charset, unknown transfer-encoding, NUL byte in headers, bidi/control chars in header values.
The parser is purely functional — no I/O, no async, no side effects. Operators run it in b.workerPool workers for any incoming message above a threshold.
b.safeMime.parse(bytes, opts?) #
{
maxParts: number, // default 64
maxNestingDepth: number, // default 16
maxBoundary: number, // default 70 (RFC 2046 §5.1.1)
maxHeaderBytes: number, // default 64 KiB
maxHeaderLineBytes: number, // default 998 (RFC 5322 §2.1.1)
maxHeaderCount: number, // default 512 (DoS bound)
maxBodyBytes: number, // default 25 MiB
maxMessageBytes: number, // default 50 MiB
charsetAllowlist: string[], // default UTF-8 / US-ASCII / common legacy 8-bit
transferEncodingAllowlist: string[], // default 7bit/8bit/quoted-printable/base64 (binary is opt-in, RFC 3030 BINARYMIME)
}
Parse bytes into a MIME part tree. Returns { headers, parts, leaf, decoded }. Multipart parts have non-null parts; leaf parts have non-null leaf carrying decoded body.
Throws SafeMimeError with codes: safe-mime/oversize-message / oversize-part-count / oversize-nesting / oversize-boundary / oversize-headers / oversize-header-line / oversize-body / unknown-charset / unknown-transfer-encoding / malformed-boundary / too-many-headers / malformed-headers / control-char-in-header / bad-input.
var msg = b.safeMime.parse(messageBuffer);
msg.headers.get("subject");
msg.parts.length;
msg.parts[0].leaf.body.toString("utf8");
b.safeMime.walk(tree, visitor) #
Depth-first walk. Invokes visitor(part, path) for every part where path is the position array ([] for root, [0] for first child). Visitor returning false short-circuits.
b.safeMime.walk(tree, function (part) {
if (part.leaf && part.leaf.contentType === "application/pdf") {
console.log("pdf", part.leaf.body.length);
}
});
b.safeMime.findFirst(tree, predicate) #
Return the first part for which predicate(part) is truthy, or null. Common use: pull the first text/plain or text/html.
var t = b.safeMime.findFirst(tree, function (p) {
return p.leaf && p.leaf.contentType === "text/plain";
});
b.safeMime.extractText(tree, opts?) #
{
prefer: "plain" | "html" | "any", // default "plain"
}
Pull the rendering-preferred text payload. Honors RFC 2046 §5.1.4 "last wins" semantics for multipart/alternative. Returns { contentType, charset, body } (body is decoded string) or null.
var tree = b.safeMime.parse(messageBuffer);
var text = b.safeMime.extractText(tree, { prefer: "plain" });
text.body; // → "Hello, world!"
text.contentType; // → "text/plain"
b.safeMime.extractAttachments(tree, opts?) #
{
includeInline: boolean, // default false — Content-Disposition: inline skipped
}
Return array of attachment-shaped parts. Each entry is { filename, contentType, body, headers }. Operators pipe each attachment through b.fileType.detect then through the per-type guard (b.guardArchive / b.guardPdf / etc.).
var tree = b.safeMime.parse(messageBuffer);
var atts = b.safeMime.extractAttachments(tree);
atts[0].filename; // → "report.pdf"
atts[0].contentType; // → "application/pdf"
atts[0].body.length; // → 12345 (decoded bytes)
Last updated 2026-08-08T16:39:49.652Z by seeder.