Mail ManageSieve Server

ManageSieve listener (RFC 5804 — "A Protocol for Remotely Managing Sieve Scripts"). Lets MUAs upload, replace, list, activate, fetch, delete, and rename Sieve filter scripts on the server. Composes b.safeSieve.validate for pre-storage validation per RFC 5804 §2.3: "An implementation MUST verify the script's validity ... and MUST reject scripts which fail validity tests."

## State machine (RFC 5804 §1)

NOT-AUTHENTICATED → STARTTLS → AUTHENTICATED → LOGOUT

- **NOT-AUTHENTICATED**: CAPABILITY / NOOP / STARTTLS / AUTHENTICATE / LOGOUT. The listener sends an unsolicited capability banner on connect (RFC 5804 §1.7). - **STARTTLS** (transient): triggered by STARTTLS. Pre-handshake receive buffer is drained before the TLS upgrade to defend the STARTTLS-injection class (CVE-2021-38371 / CVE-2021-33515 / CVE-2011-0411). Capabilities are re-emitted post-TLS so the client sees the post-TLS mechanism list (RFC 5804 §2.2). - **AUTHENTICATED**: HAVESPACE / PUTSCRIPT / LISTSCRIPTS / SETACTIVE / GETSCRIPT / DELETESCRIPT / RENAMESCRIPT / NOOP / CAPABILITY / LOGOUT.

## Wire-protocol defenses

- **No-implicit-plaintext** — opts.tlsContext is required at create(). Operators that genuinely need plaintext (intra-rack testing) explicitly pass allowPlaintext: true, which emits a mail.server.managesieve.plaintext_warning audit on every boot.

- **AUTHENTICATE-mechanism advertisement parity** — CAPABILITY output advertises ONLY the mechanisms listed in opts.auth.mechanisms. The framework hardcodes no defaults; an operator who omits mechanisms gets a listener that refuses every AUTHENTICATE attempt with "mechanism not advertised" (otherwise advertising AUTH=PLAIN when authConfig is null sets clients up to attempt PLAIN against a listener that hasn't wired the verifier).

- **Cleartext-AUTH refusal under strict** — RFC 5804 §1.1 + RFC 4954 §4. AUTHENTICATE PLAIN / LOGIN / SCRAM* pre-TLS under strict refused at both the validator and the dispatch boundary. AUTHENTICATE EXTERNAL exempt (TLS client-cert credential, not a password).

- **STARTTLS injection (CVE-2021-33515 class)** — STARTTLS upgrade clears the per-connection receive buffer; any pipelined command queued before the upgrade is discarded. Capabilities are re-emitted on the post-TLS socket per RFC 5804 §2.2.

- **PUTSCRIPT pre-validation (RFC 5804 §2.3)** — every PUTSCRIPT payload is parsed via b.safeSieve.validate before mailStore.sieveScripts.put. Invalid scripts are refused with NO (QUOTA/MAXSCRIPTS) "..." per §2.3 + audited with the safe-sieve/... issue code so operators can correlate refusals.

- **Per-IP rate limit + AUTH-failure budget** — composes b.mail.server.rateLimit (default-on). Brute-force protection applies to AUTHENTICATE failures identically to POP3/IMAP.

## Audit lifecycle

## What v1 does NOT ship

- **CHECKSCRIPT** (RFC 5804 §2.12) — parse-only verb. Operators who want it compose b.safeSieve.validate directly via JMAP SieveScript/validate (RFC 9661). The MTA-side ManageSieve surface is PUTSCRIPT + HAVESPACE; CHECKSCRIPT adds a third entry point with no operator demand yet. - **UNAUTHENTICATE** (RFC 5804 §2.14) — exotic. Operators close the TCP connection or send LOGOUT + reconnect.

## Composition contract

- b.guardManageSieveCommand — wire-protocol gate - b.safeSieve.validate — PUTSCRIPT pre-validation - b.mail.server.rateLimit — DoS defense - b.mailStore — operator-supplied backend (must expose sieveScripts.put(actor, name, body) / sieveScripts.list(actor) / sieveScripts.get(actor, name) / sieveScripts.setActive(actor, name) / sieveScripts.delete(actor, name) / sieveScripts.rename(actor, oldName, newName) / sieveScripts.haveSpace(actor, name, size)) - operator's auth.verify(mechanism, credentials) async predicate - b.network.tls.context — TLS posture

b.mail.server.managesieve.create(opts) #

stable0.9.57
{
  tlsContext:        SecureContext,                       // required (no implicit plaintext)
  allowPlaintext:    boolean,                              // explicit opt-in; emits warning audit
  greeting:          string,                               // default "blamejs ManageSieve"
  maxLineBytes:      number,                               // default 8192
  idleTimeoutMs:     number,                               // default 5 min
  profile:           "strict" | "balanced" | "permissive", // default "strict"
  auth: {
    mechanisms:      ["SCRAM-SHA-256", "OAUTHBEARER", ...], // SASL mechs to advertise
    verify:          async function (mech, credentials) → { ok, actor },
  },
  mailStore:         b.mailStore handle,                    // must expose sieveScripts.*
  rateLimit:         b.mail.server.rateLimit handle | opts | false,
  audit:             b.audit
}

Build a ManageSieve listener (RFC 5804). Returns a handle exposing listen({ port, address }) and close(). Composes b.safeSieve for PUTSCRIPT pre-validation per RFC 5804 §2.3.

var msv = b.mail.server.managesieve.create({
  tlsContext: b.mail.server.tls.context({ certFile, keyFile }).secureContext,
  auth: {
    mechanisms: ["SCRAM-SHA-256", "OAUTHBEARER", "EXTERNAL"],
    verify:     async function (mech, creds) {
      return { ok: true, actor: { username: creds.authzid, tenantId: "t1" } };
    },
  },
  mailStore: b.mailStore.create({ backend: b.db.handle() }),
});
await msv.listen({ port: 4190 });

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