Sieve parser

Bounded RFC 5228 Sieve parser. Produces an AST that b.mail.sieve.run walks at delivery time + at agent.sieve.put pre-validation. Caps script bytes / nesting depth / string-list length / per-string bytes per profile so a hostile script can't exhaust the parser. Refuses C0 / DEL / NUL controls outside string literals, refuses bare LF / bare CR (Sieve uses CRLF line terminators per RFC 5228 §2.1), and refuses oversized scripts at the byte level before tokenization.

Grammar coverage: - require ["module" ...] - control: if / elsif / else with block-body - tests: address / header / exists / size / envelope (when envelope capability declared), plus not / allof / anyof / true / false - actions: keep / fileinto / discard / redirect / stop - match-types: :is (default) / :contains / :matches - comparators: i;octet (default) / i;ascii-casemap - address-parts: :all (default) / :localpart / :domain - string lists, quoted strings ("..." with backslash escapes), multi-line strings (text:\r\n...\r\n.\r\n) - comments: # line and /* block * /

Extensions deferred (RFC 5229 variables, 5230 vacation, 5231 relational, 5232 imap4flags, 5233 subaddress, 5235 spamtest / virustest, 5260 date / index, 5293 editheader, 5429 reject / extlists, 5435 enotify, 5703 mime / replace / enclose / extracttext, 6009 ihave, 6131 mailboxid, 6134 extlists, 6558 mailbox, 6609 include, 6785 imapsieve, 8580 fcc) — refused at require time so scripts depending on them fail fast rather than silently mis-execute. The framework will light these incrementally as the operator-roadmap calls for them; until then, ship the base grammar that covers ~80% of operator-written scripts.

b.safeSieve.parse(script, opts?) #

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

Parse a Sieve script (RFC 5228) and return an AST. Refuses oversized scripts, control bytes, unknown capabilities, and RFC-defined-but- not-implemented capabilities at require time. The returned AST is the input to b.mail.sieve.run(ast, env).

var ast = b.safeSieve.parse('require ["fileinto"];\r\n' +
  'if header :contains "Subject" "[bug]" {\r\n' +
  '  fileinto "bugs";\r\n' +
  '}\r\n');
// → { kind: "script", commands: [...], requiredCaps: ["fileinto"] }

b.safeSieve.validate(script, opts?) #

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

Parse-only validation — returns { ok, requiredCaps, issues } shape mirroring the rest of the guard family. Operator-facing primitives that want a JMAP-style SieveScript/validate response (RFC 9661 — JMAP for Sieve Scripts) compose this and surface issues directly.

var v = b.safeSieve.validate('require ["fileinto"];\r\nkeep;\r\n');
v.ok;                                              // → true
v.requiredCaps;                                    // → ["fileinto"]

b.safeSieve.compliancePosture(name) #

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

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