Sieve interpreter

RFC 5228 Sieve interpreter that walks the AST produced by b.safeSieve.parse(script) and emits an ordered action list the delivery agent applies to the inbound message. Runs under a gas counter (default 10 000 ops) so a hostile or runaway script can't stall the delivery thread. The interpreter is synchronous and pure — every test reads from the operator-supplied env object; the interpreter never touches the mail store, never opens a socket, never executes operator-supplied code. Side-effects (file- into / redirect / discard) materialize as result entries the caller dispatches against the store.

Tests implemented at v0.9.55: - address — header-address-list test with :all / :localpart / :domain address-parts and :is / :contains / :matches match-types - header — header-value test with the same match-types - envelope — RFC 5228 §5.4; reads env.envelope.{from,to} - exists — header-presence test - size:over N / :under N byte-count test - not / allof / anyof / true / false

Actions implemented at v0.9.55: - keep — implicit default per §2.10.2 - fileinto "Folder" — RFC 5228 §4.1 - discard — RFC 5228 §4.4 - redirect "addr" — RFC 5228 §4.2; tagged with the address - stop — RFC 5228 §3.2; halts further command execution

Comparators: i;octet (default, exact byte) + i;ascii-casemap (case-insensitive ASCII). Other comparators refused at script parse time (require 'comparator-NAME' not in KNOWN_CAPABILITIES).

Match-type wildcards: :matches uses * (any sequence) and ? (one byte), per RFC 5228 §2.7.1. Both wildcards are converted to a bounded RegExp built from escaped literal byte segments — no user-controlled backtracking surface.

The interpreter does NOT execute multi-script chains, sieve includes (RFC 6609), notify actions (RFC 5435), or vacation responses (RFC 5230); each of those will land with the corresponding extension RFC slice. Until then, scripts declaring them via require are refused at parse time.

b.mail.sieve.run(ast, env, opts?) #

stable0.9.55
{
  maxGas: number,    // default 10000; cap 1_000_000
}

Walk a parsed Sieve AST against the message environment + return the ordered action list. The interpreter is pure — it reads only from env and never mutates it; every action surfaces as an entry in the returned list for the caller to dispatch.

var ast = b.safeSieve.parse('if header :contains "X-Spam" "yes" { fileinto "Junk"; }');
var rv  = b.mail.sieve.run(ast, {
  headers:    [{ name: "X-Spam", value: "yes" }],
  envelope:   { from: "sender@example.com", to: "rcpt@example.com" },
  sizeBytes:  1024,
});
// → { actions: [{ kind: "fileinto", folder: "Junk" }, { kind: "keep" }], gas: 3, stopped: false }

b.mail.sieve.runScript(script, env, opts?) #

stable0.9.55
{
  profile:           "strict" | "balanced" | "permissive",
  compliancePosture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
  maxGas:            number,
}

Parse + run in one call. Most call sites — JMAP SieveScript/validate, MX delivery hook — want this shape.

var rv = b.mail.sieve.runScript(
  'require ["fileinto"];\nif header :is "From" "boss@x.com" { fileinto "Important"; }',
  { headers: [{ name: "From", value: "boss@x.com" }] }
);
rv.actions[0].folder;                              // → "Important"

b.mail.sieve.create(opts?) #

stable0.9.55
{
  maxGas:            number,
  profile:           "strict" | "balanced" | "permissive",
  compliancePosture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
  audit:             { safeEmit: function },
}

Returns a stateful Sieve handle the delivery agent + JMAP SieveScript/validate method compose. Distinct from the bare b.mail.sieve.run(ast, env) entry — the handle carries operator- supplied opts (maxGas, profile, compliancePosture, audit) so every invocation runs with the same posture.

var sieve = b.mail.sieve.create({ profile: "strict", audit: b.audit });
sieve.validateScript(operatorScript);
var rv = await sieve.runScript(operatorScript, mailEnv);

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