Guard SMTP Command
SMTP command-line validator. Gates every command verb the framework's inbound MX listener (v0.9.34) and outbound submission listener (v0.9.35) accept from peers — EHLO / HELO / MAIL FROM / RCPT TO / DATA / BDAT / VRFY / EXPN / NOOP / RSET / QUIT / AUTH / STARTTLS / HELP.
## Smuggling defense — bare-CR / bare-LF refusal
The SMTP smuggling class (CVE-2023-51764 Postfix, CVE-2023-51765 Sendmail, CVE-2023-51766 Exim) exploits implementations that accept the non-standard end-of-data sequence or instead of the standard . The introduced break- out lets a malicious peer inject a second message past SPF / DMARC checks performed only on the outer envelope.
At the command-line level the defense is the same: every command line MUST be CRLF-terminated; bare \r or \n anywhere inside a command line is refused. Operators with peers that legitimately speak bare-LF (rare; legacy Sendmail-to-Sendmail) opt into permissive profile with audit emit per accepted bare-LF line.
## STARTTLS command-buffer injection
CVE-2021-38371 (Exim STARTTLS response injection) and CVE-2021-33515 (Dovecot lib-smtp STARTTLS command injection) exploit implementations that don't drain the pre-STARTTLS receive buffer when negotiating TLS — commands queued by an MitM before the handshake get applied to the post-handshake (TLS-protected) stream. The fix is stateful (drain the buffer on STARTTLS), so this guard alone can't fully defend; it surfaces the requirement to the v0.9.34 listener via validate({ verb: "STARTTLS" }) refusing trailing payload on the STARTTLS line and the listener's pipelining-after-STARTTLS check enforcing buffer drain.
## Per-verb shape
Each verb has a fixed argument shape (RFC 5321 §3 / §4.1):
- EHLO / HELO — exactly one arg (domain or address literal). - MAIL — FROM: (RFC 5321 §3.3) + optional SIZE= / BODY= / RET= / ENVID= / AUTH= extension params. - RCPT — TO: (RFC 5321 §3.3) + optional NOTIFY= / ORCPT= extension params. - DATA — no args. - BDAT — single decimal chunk size + optional LAST keyword (RFC 3030 CHUNKING). - VRFY / EXPN — single mailbox arg. - NOOP — optional opaque string. - RSET / QUIT / STARTTLS — no args. - AUTH — SASL mechanism name + optional initial-response (RFC 4954). - HELP — optional argument.
Anything not matching the shape under strict profile is refused with guard-smtp-command/bad-shape.
## Caps
- Command line (path + arguments + CRLF) capped at 512 bytes per RFC 5321 §4.5.3.1.1. SMTPUTF8 / EAI peers (RFC 6531) may send longer command lines for non-ASCII addresses; balanced profile bumps the cap to 1024. - Forward-path / reverse-path mailbox capped at 256 bytes per RFC 5321 §4.5.3.1.3. - Domain part of a path capped at 255 bytes per RFC 1035 §2.3.4. - Local part capped at 64 bytes per RFC 5321 §4.5.3.1.1.
Throws GuardSmtpCommandError on every refusal. Pure-functional — no I/O, no state. The MX / submission listener composes one instance per accepted connection.
b.guardSmtpCommand.validate(line, opts?) #
{
profile: "strict" | "balanced" | "permissive",
posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
}
Validate a single SMTP command line (without its CRLF terminator — the listener strips that before calling this). Returns a structured { verb, args, params } shape on success; throws GuardSmtpCommandError on any refusal.
var parsed = b.guardSmtpCommand.validate("MAIL FROM: SIZE=12345");
// → { verb: "MAIL", args: ["FROM:"], params: { SIZE: "12345" } }
b.guardSmtpCommand.gate(opts?) #
{
profile: "strict" | "balanced" | "permissive",
posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
name: string, // gate identity label
}
Build a guard gate compatible with b.guardAll.allGuards(). The gate's decide(ctx) reads ctx.identifier (or ctx.commandLine) and routes through validate(); refuse on any thrown GuardSmtpCommandError, serve otherwise.
var gate = b.guardSmtpCommand.gate({ profile: "strict" });
await gate.decide({ identifier: "EHLO mail.example.com" });
// → { ok: true, action: "serve" }
b.guardSmtpCommand.detectBodySmuggling(buf) #
Scan a DATA-body byte buffer for the SMTP smuggling shape per CVE-2023-51764 (Postfix), CVE-2023-51765 (Sendmail), CVE-2023-51766 (Exim). RFC 5321 §2.3.8 mandates canonical CRLF line termination; the smuggling exploit relies on parsers that accept \n.\n (bare LF before / after the dot) as an alternate body terminator and then resume parsing the NEXT bytes as a new SMTP transaction.
Returns true if the buffer contains a bare-LF dot-line (a \n NOT preceded by \r, immediately followed by .\n), false otherwise. Operators wiring an MX / submission listener call this on every DATA chunk + refuse the whole transaction on true per the framework's strict-CRLF posture.
b.guardSmtpCommand.detectBodySmuggling(Buffer.from("body\r\n.\r\n"));
// → false
b.guardSmtpCommand.detectBodySmuggling(Buffer.from("body\n.\n"));
// → true (bare-LF dot-line — CVE-2023-51764 shape)
b.guardSmtpCommand.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.guardSmtpCommand.compliancePosture("hipaa"); // → "strict"
b.guardSmtpCommand.compliancePosture("not-a-regime"); // → null
Last updated 2026-08-08T16:39:49.652Z by seeder.