Guard ManageSieve Command

ManageSieve command-line validator (RFC 5804 — "A Protocol for Remotely Managing Sieve Scripts"). Gates every verb the framework's ManageSieve listener accepts from peers — AUTHENTICATE / STARTTLS / LOGOUT / CAPABILITY / HAVESPACE / PUTSCRIPT / LISTSCRIPTS / SETACTIVE / GETSCRIPT / DELETESCRIPT / RENAMESCRIPT / NOOP.

ManageSieve is a line-oriented text protocol that mixes simple single-line commands with literal-syntax payloads ({N} / {N+}) carrying script bytes verbatim. Responses are OK ... / NO ... / BYE ... per RFC 5804 §1.2.

## Smuggling defense — bare-CR / bare-LF refusal

Same wire-protocol concern as SMTP / IMAP / POP3. Command lines MUST be canonical CRLF-terminated; bare-CR or bare-LF inside a command (outside the literal-payload window) is refused. The listener clears the receive buffer at STARTTLS upgrade to defend the same pre-handshake injection class that affected STARTTLS in Exim (CVE-2021-38371) / Dovecot (CVE-2021-33515) / Postfix (CVE-2011-0411).

## Cleartext-AUTH refusal under strict

RFC 5804 §1.1 + RFC 4954 §4 — AUTHENTICATE with credential- bearing mechanisms (PLAIN / LOGIN) over a cleartext channel exposes the password to passive observation. Strict + balanced refuse AUTHENTICATE PLAIN / AUTHENTICATE LOGIN pre-TLS; the listener composes this gate at the validate boundary AND re-checks at the dispatch boundary as defense-in-depth.

AUTHENTICATE EXTERNAL (RFC 4422 §4) is exempt — the credential is the TLS client certificate already presented, so cleartext is not the concern. AUTHENTICATE SCRAM-SHA-256 (RFC 7677) and AUTHENTICATE OAUTHBEARER (RFC 7628) are mechanism-side credential-protected and may run pre-TLS under permissive; strict still requires TLS (defense-in-depth + active-MITM resistance).

## Script-name shape (RFC 5804 §2.1)

Script names are UTF-8 strings of 1-512 octets containing no NUL (0x00), CR (0x0D), LF (0x0A), forward-slash (0x2F), backslash (0x5C), or double-quote (0x22). The forward-slash + backslash refusal blocks path-traversal-style storage-backend collisions; the NUL/CR/LF refusal blocks wire-protocol smuggling.

## Literal syntax (RFC 5804 §2.3 + RFC 7888 LITERAL+)

PUTSCRIPT name {N} / PUTSCRIPT name {N+} introduces an N-byte script payload. The bare {N} form is synchronizing (server replies with a continuation request before the client sends the payload); {N+} (RFC 7888) is non-synchronizing. The validator refuses N values above the per-profile script-byte cap (matching b.safeSieve's maxScriptBytes: 64 KiB strict / 256 KiB balanced / 1 MiB permissive).

## Per-verb shape

RFC 5804 §2.1-§2.10:

- AUTHENTICATE "" [] - STARTTLS — no args - LOGOUT — no args - CAPABILITY — no args - NOOP [string] — optional echo-tag arg - HAVESPACE "" — name + non-negative integer - PUTSCRIPT "" - LISTSCRIPTS — no args - SETACTIVE "" — single script-name arg (empty string deactivates all per §2.8) - GETSCRIPT "" — single script-name arg - DELETESCRIPT "" — single script-name arg - RENAMESCRIPT "" "" — two script-name args

## Caps

- Per-line cap (excluding the literal payload itself): 8 KiB strict / 16 KiB balanced / 64 KiB permissive. ManageSieve's command lines are LONGER than POP3/IMAP because script names may carry UTF-8 + the literal-payload announcement. - Script-byte cap (literal {N} value): same as b.safeSieve.PROFILES..maxScriptBytes — 64 KiB / 256 KiB / 1 MiB. - Script name: RFC 5804 §2.1 1-512 octets.

Throws GuardManageSieveCommandError on every refusal.

b.guardManageSieveCommand.validate(line, opts?) #

stable0.9.57
{
  profile:   "strict" | "balanced" | "permissive",
  posture:   "hipaa" | "pci-dss" | "gdpr" | "soc2",
  tls:       boolean,    // when false + AUTHENTICATE PLAIN/LOGIN
                           under strict, refuse with
                           `guard-managesieve-command/cleartext-auth`
                           (RFC 4954 §4 + RFC 5804 §1.1)
}

Validate a single ManageSieve command line (without its CRLF terminator, and without the literal-script payload that may follow). Returns a shape describing the parsed verb + arguments + (when applicable) the trailing literal-byte count the listener must read from the wire. Throws GuardManageSieveCommandError on refusal.

var p = b.guardManageSieveCommand.validate('PUTSCRIPT "myscript" {52+}', { tls: true });
// → { verb: "PUTSCRIPT", args: ["myscript"], literalBytes: 52, literalPlus: true }

var c = b.guardManageSieveCommand.validate("CAPABILITY", { tls: true });
// → { verb: "CAPABILITY", args: [] }

b.guardManageSieveCommand.compliancePosture(name) #

stable0.9.57hipaapci-dssgdprsoc2

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.guardManageSieveCommand.compliancePosture("hipaa");                   // → "strict"
b.guardManageSieveCommand.compliancePosture("not-a-regime");            // → null

Last updated 2026-08-08T16:39:49.652Z by seeder.