@blamejs/core 0.9.49 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +952 -908
  2. package/index.js +25 -0
  3. package/lib/_test/crypto-fixtures.js +67 -0
  4. package/lib/agent-event-bus.js +52 -6
  5. package/lib/agent-idempotency.js +169 -16
  6. package/lib/agent-orchestrator.js +263 -9
  7. package/lib/agent-posture-chain.js +163 -5
  8. package/lib/agent-saga.js +146 -16
  9. package/lib/agent-snapshot.js +349 -19
  10. package/lib/agent-stream.js +34 -2
  11. package/lib/agent-tenant.js +179 -23
  12. package/lib/agent-trace.js +84 -21
  13. package/lib/auth/aal.js +8 -1
  14. package/lib/auth/ciba.js +6 -1
  15. package/lib/auth/dpop.js +7 -2
  16. package/lib/auth/fal.js +17 -8
  17. package/lib/auth/jwt-external.js +128 -4
  18. package/lib/auth/oauth.js +232 -10
  19. package/lib/auth/oid4vci.js +67 -7
  20. package/lib/auth/openid-federation.js +71 -25
  21. package/lib/auth/passkey.js +140 -6
  22. package/lib/auth/sd-jwt-vc.js +78 -5
  23. package/lib/circuit-breaker.js +10 -2
  24. package/lib/cli.js +13 -0
  25. package/lib/compliance.js +176 -8
  26. package/lib/crypto-field.js +114 -14
  27. package/lib/crypto.js +216 -20
  28. package/lib/db.js +1 -0
  29. package/lib/guard-graphql.js +37 -0
  30. package/lib/guard-jmap.js +321 -0
  31. package/lib/guard-managesieve-command.js +566 -0
  32. package/lib/guard-pop3-command.js +317 -0
  33. package/lib/guard-regex.js +138 -1
  34. package/lib/guard-smtp-command.js +58 -3
  35. package/lib/guard-xml.js +39 -1
  36. package/lib/mail-agent.js +20 -7
  37. package/lib/mail-arc-sign.js +12 -8
  38. package/lib/mail-auth.js +323 -34
  39. package/lib/mail-crypto-pgp.js +934 -0
  40. package/lib/mail-crypto-smime.js +340 -0
  41. package/lib/mail-crypto.js +108 -0
  42. package/lib/mail-dav.js +1224 -0
  43. package/lib/mail-deploy.js +492 -0
  44. package/lib/mail-dkim.js +431 -26
  45. package/lib/mail-journal.js +435 -0
  46. package/lib/mail-scan.js +502 -0
  47. package/lib/mail-server-imap.js +64 -26
  48. package/lib/mail-server-jmap.js +488 -0
  49. package/lib/mail-server-managesieve.js +853 -0
  50. package/lib/mail-server-mx.js +40 -30
  51. package/lib/mail-server-pop3.js +836 -0
  52. package/lib/mail-server-rate-limit.js +13 -0
  53. package/lib/mail-server-submission.js +70 -24
  54. package/lib/mail-server-tls.js +445 -0
  55. package/lib/mail-sieve.js +557 -0
  56. package/lib/mail-spam-score.js +284 -0
  57. package/lib/mail.js +99 -0
  58. package/lib/metrics.js +80 -3
  59. package/lib/middleware/dpop.js +58 -3
  60. package/lib/middleware/idempotency-key.js +255 -42
  61. package/lib/middleware/protected-resource-metadata.js +114 -2
  62. package/lib/network-dns-resolver.js +33 -0
  63. package/lib/network-tls.js +46 -0
  64. package/lib/otel-export.js +13 -4
  65. package/lib/outbox.js +62 -12
  66. package/lib/pqc-agent.js +13 -5
  67. package/lib/retry.js +23 -9
  68. package/lib/router.js +23 -1
  69. package/lib/safe-ical.js +634 -0
  70. package/lib/safe-icap.js +502 -0
  71. package/lib/safe-mime.js +15 -0
  72. package/lib/safe-sieve.js +684 -0
  73. package/lib/safe-smtp.js +57 -0
  74. package/lib/safe-url.js +37 -0
  75. package/lib/safe-vcard.js +473 -0
  76. package/lib/self-update-standalone-verifier.js +32 -3
  77. package/lib/self-update.js +153 -33
  78. package/lib/vendor/MANIFEST.json +161 -156
  79. package/lib/vendor-data.js +127 -9
  80. package/lib/vex.js +324 -59
  81. package/package.json +1 -1
  82. package/sbom.cdx.json +6 -6
@@ -0,0 +1,566 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.guardManageSieveCommand
4
+ * @nav Guards
5
+ * @title Guard ManageSieve Command
6
+ * @order 454
7
+ *
8
+ * @intro
9
+ * ManageSieve command-line validator (RFC 5804 — "A Protocol for
10
+ * Remotely Managing Sieve Scripts"). Gates every verb the framework's
11
+ * ManageSieve listener accepts from peers — `AUTHENTICATE` /
12
+ * `STARTTLS` / `LOGOUT` / `CAPABILITY` / `HAVESPACE` / `PUTSCRIPT` /
13
+ * `LISTSCRIPTS` / `SETACTIVE` / `GETSCRIPT` / `DELETESCRIPT` /
14
+ * `RENAMESCRIPT` / `NOOP`.
15
+ *
16
+ * ManageSieve is a line-oriented text protocol that mixes simple
17
+ * single-line commands with literal-syntax payloads (`{N}` / `{N+}`)
18
+ * carrying script bytes verbatim. Responses are `OK ...` / `NO ...` /
19
+ * `BYE ...` per RFC 5804 §1.2.
20
+ *
21
+ * ## Smuggling defense — bare-CR / bare-LF refusal
22
+ *
23
+ * Same wire-protocol concern as SMTP / IMAP / POP3. Command lines
24
+ * MUST be canonical CRLF-terminated; bare-CR or bare-LF inside a
25
+ * command (outside the literal-payload window) is refused. The
26
+ * listener clears the receive buffer at STARTTLS upgrade to defend
27
+ * the same pre-handshake injection class that affected STARTTLS in
28
+ * Exim (CVE-2021-38371) / Dovecot (CVE-2021-33515) / Postfix
29
+ * (CVE-2011-0411).
30
+ *
31
+ * ## Cleartext-AUTH refusal under strict
32
+ *
33
+ * RFC 5804 §1.1 + RFC 4954 §4 — `AUTHENTICATE` with credential-
34
+ * bearing mechanisms (PLAIN / LOGIN) over a cleartext channel
35
+ * exposes the password to passive observation. Strict + balanced
36
+ * refuse `AUTHENTICATE PLAIN` / `AUTHENTICATE LOGIN` pre-TLS; the
37
+ * listener composes this gate at the validate boundary AND
38
+ * re-checks at the dispatch boundary as defense-in-depth.
39
+ *
40
+ * `AUTHENTICATE EXTERNAL` (RFC 4422 §4) is exempt — the credential
41
+ * is the TLS client certificate already presented, so cleartext is
42
+ * not the concern. `AUTHENTICATE SCRAM-SHA-256` (RFC 7677) and
43
+ * `AUTHENTICATE OAUTHBEARER` (RFC 7628) are mechanism-side
44
+ * credential-protected and may run pre-TLS under permissive; strict
45
+ * still requires TLS (defense-in-depth + active-MITM resistance).
46
+ *
47
+ * ## Script-name shape (RFC 5804 §2.1)
48
+ *
49
+ * Script names are UTF-8 strings of 1-512 octets containing no NUL
50
+ * (0x00), CR (0x0D), LF (0x0A), forward-slash (0x2F), backslash
51
+ * (0x5C), or double-quote (0x22). The forward-slash + backslash
52
+ * refusal blocks path-traversal-style storage-backend collisions;
53
+ * the NUL/CR/LF refusal blocks wire-protocol smuggling.
54
+ *
55
+ * ## Literal syntax (RFC 5804 §2.3 + RFC 7888 LITERAL+)
56
+ *
57
+ * `PUTSCRIPT name {N}` / `PUTSCRIPT name {N+}` introduces an N-byte
58
+ * script payload. The bare `{N}` form is synchronizing (server
59
+ * replies with a continuation request before the client sends the
60
+ * payload); `{N+}` (RFC 7888) is non-synchronizing. The validator
61
+ * refuses N values above the per-profile script-byte cap (matching
62
+ * `b.safeSieve`'s `maxScriptBytes`: 64 KiB strict / 256 KiB balanced
63
+ * / 1 MiB permissive).
64
+ *
65
+ * ## Per-verb shape
66
+ *
67
+ * RFC 5804 §2.1-§2.10:
68
+ *
69
+ * - `AUTHENTICATE` "<mech>" [<literal-initial-response>]
70
+ * - `STARTTLS` — no args
71
+ * - `LOGOUT` — no args
72
+ * - `CAPABILITY` — no args
73
+ * - `NOOP` [string] — optional echo-tag arg
74
+ * - `HAVESPACE` "<name>" <N> — name + non-negative integer
75
+ * - `PUTSCRIPT` "<name>" <literal-script>
76
+ * - `LISTSCRIPTS` — no args
77
+ * - `SETACTIVE` "<name>" — single script-name arg (empty
78
+ * string deactivates all per §2.8)
79
+ * - `GETSCRIPT` "<name>" — single script-name arg
80
+ * - `DELETESCRIPT` "<name>" — single script-name arg
81
+ * - `RENAMESCRIPT` "<old>" "<new>" — two script-name args
82
+ *
83
+ * ## Caps
84
+ *
85
+ * - Per-line cap (excluding the literal payload itself): 8 KiB
86
+ * strict / 16 KiB balanced / 64 KiB permissive. ManageSieve's
87
+ * command lines are LONGER than POP3/IMAP because script names
88
+ * may carry UTF-8 + the literal-payload announcement.
89
+ * - Script-byte cap (literal `{N}` value): same as
90
+ * `b.safeSieve.PROFILES.<profile>.maxScriptBytes` — 64 KiB /
91
+ * 256 KiB / 1 MiB.
92
+ * - Script name: RFC 5804 §2.1 1-512 octets.
93
+ *
94
+ * Throws `GuardManageSieveCommandError` on every refusal.
95
+ *
96
+ * @card
97
+ * ManageSieve command-line validator (RFC 5804 + RFC 7888 LITERAL+).
98
+ * Refuses bare-CR / bare-LF (smuggling defense), caps per-line +
99
+ * script-name + literal-script bytes, refuses cleartext AUTHENTICATE
100
+ * under strict (RFC 4954 §4 class), validates per-verb shape.
101
+ */
102
+
103
+ var { defineClass } = require("./framework-error");
104
+
105
+ var GuardManageSieveCommandError = defineClass("GuardManageSieveCommandError",
106
+ { alwaysPermanent: true });
107
+
108
+ var DEFAULT_PROFILE = "strict";
109
+
110
+ var PROFILES = Object.freeze({
111
+ strict: {
112
+ maxLineBytes: 8192, // allow:raw-byte-literal — 8 KiB per-line cap (strict)
113
+ maxScriptBytes: 65536, // allow:raw-byte-literal — 64 KiB script cap (matches safeSieve strict)
114
+ maxScriptNameBytes: 512, // allow:raw-byte-literal — RFC 5804 §2.1 script-name cap
115
+ allowBareLf: false,
116
+ allowCleartextAuth: false,
117
+ allowLiteralPlus: true, // RFC 7888 LITERAL+ accepted under strict (operator MAY refuse via opts.allowLiteralPlus=false) // allow:raw-byte-literal — RFC number
118
+ },
119
+ balanced: {
120
+ maxLineBytes: 16384, // allow:raw-byte-literal — 16 KiB per-line cap (balanced)
121
+ maxScriptBytes: 262144, // allow:raw-byte-literal — 256 KiB script cap (matches safeSieve balanced)
122
+ maxScriptNameBytes: 512, // allow:raw-byte-literal — RFC 5804 §2.1 script-name cap
123
+ allowBareLf: false,
124
+ allowCleartextAuth: false,
125
+ allowLiteralPlus: true,
126
+ },
127
+ permissive: {
128
+ maxLineBytes: 65536, // allow:raw-byte-literal — 64 KiB per-line cap (permissive)
129
+ maxScriptBytes: 1048576, // allow:raw-byte-literal — 1 MiB script cap (matches safeSieve permissive)
130
+ maxScriptNameBytes: 512, // allow:raw-byte-literal — RFC 5804 §2.1 script-name cap
131
+ allowBareLf: true,
132
+ allowCleartextAuth: true,
133
+ allowLiteralPlus: true,
134
+ },
135
+ });
136
+
137
+ var COMPLIANCE_POSTURES = Object.freeze({
138
+ hipaa: "strict",
139
+ "pci-dss": "strict",
140
+ gdpr: "strict",
141
+ soc2: "strict",
142
+ });
143
+
144
+ // ManageSieve verbs per RFC 5804 §2.
145
+ var KNOWN_VERBS = Object.freeze({
146
+ AUTHENTICATE: true, STARTTLS: true, LOGOUT: true,
147
+ CAPABILITY: true, NOOP: true, HAVESPACE: true,
148
+ PUTSCRIPT: true, LISTSCRIPTS: true, SETACTIVE: true,
149
+ GETSCRIPT: true, DELETESCRIPT: true, RENAMESCRIPT: true,
150
+ });
151
+
152
+ var ZERO_ARG_VERBS = Object.freeze({
153
+ STARTTLS: true,
154
+ LOGOUT: true,
155
+ CAPABILITY: true,
156
+ LISTSCRIPTS: true,
157
+ });
158
+
159
+ // SASL mechanisms that carry credentials in cleartext — refused
160
+ // pre-TLS under strict + balanced per RFC 4954 §4.
161
+ var CLEARTEXT_VULNERABLE_MECHS = Object.freeze({
162
+ PLAIN: true,
163
+ LOGIN: true,
164
+ });
165
+
166
+ // Numeric arg for HAVESPACE / literal length — anchored bounded
167
+ // decimal, up to 10 digits (10^10 - 1 = 9999999999, well below the
168
+ // 1 MiB permissive script cap).
169
+ var NUM_RE = /^[0-9]{1,10}$/; // allow:regex-no-length-cap — anchored + bounded repeat
170
+
171
+ // Literal-length suffix: `{N}` (synchronizing) or `{N+}` (LITERAL+).
172
+ var LITERAL_RE = /^\{([0-9]{1,10})(\+?)\}$/; // allow:regex-no-length-cap — anchored + bounded digits
173
+
174
+ /**
175
+ * @primitive b.guardManageSieveCommand.validate
176
+ * @signature b.guardManageSieveCommand.validate(line, opts?)
177
+ * @since 0.9.57
178
+ * @status stable
179
+ * @related b.guardPop3Command.validate, b.guardImapCommand.validate, b.safeSieve.parse
180
+ *
181
+ * Validate a single ManageSieve command line (without its CRLF
182
+ * terminator, and without the literal-script payload that may follow).
183
+ * Returns a shape describing the parsed verb + arguments + (when
184
+ * applicable) the trailing literal-byte count the listener must read
185
+ * from the wire. Throws `GuardManageSieveCommandError` on refusal.
186
+ *
187
+ * @opts
188
+ * profile: "strict" | "balanced" | "permissive",
189
+ * posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
190
+ * tls: boolean, // when false + AUTHENTICATE PLAIN/LOGIN
191
+ * under strict, refuse with
192
+ * `guard-managesieve-command/cleartext-auth`
193
+ * (RFC 4954 §4 + RFC 5804 §1.1)
194
+ *
195
+ * @example
196
+ * var p = b.guardManageSieveCommand.validate('PUTSCRIPT "myscript" {52+}', { tls: true });
197
+ * // → { verb: "PUTSCRIPT", args: ["myscript"], literalBytes: 52, literalPlus: true }
198
+ *
199
+ * var c = b.guardManageSieveCommand.validate("CAPABILITY", { tls: true });
200
+ * // → { verb: "CAPABILITY", args: [] }
201
+ */
202
+ function validate(line, opts) {
203
+ opts = opts || {};
204
+ var profileName = typeof opts.profile === "string" ? opts.profile : DEFAULT_PROFILE;
205
+ if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
206
+ profileName = COMPLIANCE_POSTURES[opts.posture];
207
+ }
208
+ var caps = PROFILES[profileName];
209
+ if (!caps) {
210
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-profile",
211
+ "guardManageSieveCommand.validate: unknown profile '" + profileName + "'");
212
+ }
213
+ if (typeof line !== "string") {
214
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-input",
215
+ "guardManageSieveCommand.validate: line must be a string");
216
+ }
217
+ if (line.length === 0) {
218
+ throw new GuardManageSieveCommandError("guard-managesieve-command/empty-line",
219
+ "guardManageSieveCommand.validate: empty command line");
220
+ }
221
+ if (line.length > caps.maxLineBytes) {
222
+ throw new GuardManageSieveCommandError("guard-managesieve-command/line-too-long",
223
+ "guardManageSieveCommand.validate: line " + line.length +
224
+ " bytes exceeds cap " + caps.maxLineBytes);
225
+ }
226
+
227
+ // Control-byte refusal outside string literals — quoted-string
228
+ // payload (the script-name argument) is scanned separately in
229
+ // `_parseQuotedString`; here we walk the line and skip over the
230
+ // quoted-string regions.
231
+ var inQuote = false;
232
+ for (var i = 0; i < line.length; i += 1) {
233
+ var c = line.charCodeAt(i);
234
+ if (c === 0x22 && !_isEscaped(line, i)) { // allow:raw-byte-literal — DQUOTE
235
+ inQuote = !inQuote;
236
+ continue;
237
+ }
238
+ if (inQuote) continue;
239
+ if (c === 0x00 || c === 0x7F || (c < 0x20 && c !== 0x09)) { // allow:raw-byte-literal — control-byte refusal
240
+ if (c === 0x0A && caps.allowBareLf) continue;
241
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-byte",
242
+ "guardManageSieveCommand.validate: control byte 0x" +
243
+ c.toString(16) + " at offset " + i); // allow:raw-byte-literal — base-16 toString radix
244
+ }
245
+ }
246
+ if (inQuote) {
247
+ throw new GuardManageSieveCommandError("guard-managesieve-command/unterminated-string",
248
+ "guardManageSieveCommand.validate: unterminated quoted string");
249
+ }
250
+
251
+ var firstSpace = line.indexOf(" ");
252
+ var verb = (firstSpace === -1 ? line : line.slice(0, firstSpace)).toUpperCase();
253
+ var rest = firstSpace === -1 ? "" : line.slice(firstSpace + 1);
254
+
255
+ if (!KNOWN_VERBS[verb]) {
256
+ throw new GuardManageSieveCommandError("guard-managesieve-command/unknown-verb",
257
+ "guardManageSieveCommand.validate: unknown verb '" + verb + "' (RFC 5804 §2)");
258
+ }
259
+ if (ZERO_ARG_VERBS[verb] && rest.length > 0) {
260
+ throw new GuardManageSieveCommandError("guard-managesieve-command/unexpected-args",
261
+ "guardManageSieveCommand.validate: verb '" + verb + "' takes no arguments");
262
+ }
263
+
264
+ switch (verb) {
265
+ case "AUTHENTICATE": return _validateAuthenticate(rest, caps, profileName, opts);
266
+ case "NOOP": return _validateNoop(rest, caps);
267
+ case "HAVESPACE": return _validateHavespace(rest, caps);
268
+ case "PUTSCRIPT": return _validatePutscript(rest, caps);
269
+ case "SETACTIVE": return _validateSingleName(verb, rest, caps, { allowEmpty: true });
270
+ case "GETSCRIPT": return _validateSingleName(verb, rest, caps, { allowEmpty: false });
271
+ case "DELETESCRIPT": return _validateSingleName(verb, rest, caps, { allowEmpty: false });
272
+ case "RENAMESCRIPT": return _validateRenamescript(rest, caps);
273
+ default:
274
+ // STARTTLS / LOGOUT / CAPABILITY / LISTSCRIPTS — ZERO_ARG_VERBS
275
+ // guard above already enforced no-args.
276
+ return { verb: verb, args: [] };
277
+ }
278
+ }
279
+
280
+ // AUTHENTICATE "<mech>" [<literal-initial-response>]
281
+ // Initial-response is either a literal `{N}` / `{N+}` or a quoted
282
+ // base64 string per RFC 5804 §2.1 + RFC 4422.
283
+ function _validateAuthenticate(rest, caps, profileName, opts) {
284
+ if (!rest) {
285
+ throw new GuardManageSieveCommandError("guard-managesieve-command/missing-mechanism",
286
+ "guardManageSieveCommand.validate: AUTHENTICATE requires a mechanism");
287
+ }
288
+ var parsed = _parseQuotedString(rest);
289
+ if (parsed === null) {
290
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-mechanism",
291
+ "guardManageSieveCommand.validate: AUTHENTICATE mechanism must be a quoted string");
292
+ }
293
+ var mech = parsed.value.toUpperCase();
294
+ var trailing = parsed.rest;
295
+ // Cleartext-AUTH refusal — RFC 4954 §4 + RFC 5804 §1.1. Strict +
296
+ // balanced refuse credential-bearing mechanisms (PLAIN / LOGIN)
297
+ // pre-TLS. EXTERNAL is exempt (credential is the TLS client cert,
298
+ // not a password). SCRAM* / OAUTHBEARER are still refused under
299
+ // strict (defense-in-depth + active-MITM resistance).
300
+ if (opts.tls === false && !caps.allowCleartextAuth) {
301
+ if (CLEARTEXT_VULNERABLE_MECHS[mech] || profileName === "strict") {
302
+ if (mech !== "EXTERNAL") {
303
+ throw new GuardManageSieveCommandError("guard-managesieve-command/cleartext-auth",
304
+ "guardManageSieveCommand.validate: AUTHENTICATE " + mech +
305
+ " refused over cleartext (use STARTTLS first; RFC 5804 §1.1 + RFC 4954 §4)");
306
+ }
307
+ }
308
+ }
309
+ var literalBytes = null;
310
+ var literalPlus = false;
311
+ if (trailing) {
312
+ // Optional initial-response — either `{N+?}` literal or a quoted
313
+ // base64 string.
314
+ var lit = LITERAL_RE.exec(trailing); // allow:regex-no-length-cap — LITERAL_RE anchored + bounded digits
315
+ if (lit) {
316
+ var n = parseInt(lit[1], 10);
317
+ var isPlus = lit[2] === "+";
318
+ if (isPlus && !caps.allowLiteralPlus) {
319
+ throw new GuardManageSieveCommandError("guard-managesieve-command/literal-plus-refused",
320
+ "guardManageSieveCommand.validate: LITERAL+ refused under profile '" + profileName + "'");
321
+ }
322
+ // Base64-initial-response cap: bound by the script-name cap
323
+ // (initial-response is a SASL token, not a script body; 4 KiB
324
+ // is generous).
325
+ if (n > 4096) { // allow:raw-byte-literal — 4 KiB SASL initial-response cap
326
+ throw new GuardManageSieveCommandError("guard-managesieve-command/literal-too-large",
327
+ "guardManageSieveCommand.validate: AUTHENTICATE initial-response " +
328
+ n + " bytes exceeds 4096-byte cap");
329
+ }
330
+ literalBytes = n;
331
+ literalPlus = isPlus;
332
+ } else {
333
+ var inner = _parseQuotedString(trailing);
334
+ if (inner === null || inner.rest.length > 0) {
335
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-initial-response",
336
+ "guardManageSieveCommand.validate: AUTHENTICATE initial-response must be a " +
337
+ "literal `{N}` / `{N+}` or quoted base64 string");
338
+ }
339
+ }
340
+ }
341
+ return { verb: "AUTHENTICATE", args: [mech], literalBytes: literalBytes, literalPlus: literalPlus };
342
+ }
343
+
344
+ function _validateNoop(rest, caps) {
345
+ if (!rest) return { verb: "NOOP", args: [] };
346
+ var parsed = _parseQuotedString(rest);
347
+ if (parsed === null || parsed.rest.length > 0) {
348
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-noop-arg",
349
+ "guardManageSieveCommand.validate: NOOP optional arg must be a quoted string");
350
+ }
351
+ _checkScriptNameBytes(parsed.value, caps);
352
+ return { verb: "NOOP", args: [parsed.value] };
353
+ }
354
+
355
+ function _validateHavespace(rest, caps) {
356
+ if (!rest) {
357
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-havespace",
358
+ "guardManageSieveCommand.validate: HAVESPACE requires `\"name\" size`");
359
+ }
360
+ var parsed = _parseQuotedString(rest);
361
+ if (parsed === null) {
362
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-havespace",
363
+ "guardManageSieveCommand.validate: HAVESPACE script-name must be a quoted string");
364
+ }
365
+ _checkScriptName(parsed.value, caps);
366
+ var sizeStr = parsed.rest;
367
+ if (!sizeStr || !NUM_RE.test(sizeStr)) { // allow:regex-no-length-cap — NUM_RE anchored + bounded
368
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-havespace",
369
+ "guardManageSieveCommand.validate: HAVESPACE size must be a positive decimal integer");
370
+ }
371
+ var size = parseInt(sizeStr, 10);
372
+ if (size > caps.maxScriptBytes) {
373
+ throw new GuardManageSieveCommandError("guard-managesieve-command/script-too-large",
374
+ "guardManageSieveCommand.validate: HAVESPACE size " + size +
375
+ " bytes exceeds cap " + caps.maxScriptBytes);
376
+ }
377
+ return { verb: "HAVESPACE", args: [parsed.value, size] };
378
+ }
379
+
380
+ function _validatePutscript(rest, caps) {
381
+ if (!rest) {
382
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-putscript",
383
+ "guardManageSieveCommand.validate: PUTSCRIPT requires `\"name\" {N[+]}`");
384
+ }
385
+ var parsed = _parseQuotedString(rest);
386
+ if (parsed === null) {
387
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-putscript",
388
+ "guardManageSieveCommand.validate: PUTSCRIPT script-name must be a quoted string");
389
+ }
390
+ _checkScriptName(parsed.value, caps);
391
+ var litStr = parsed.rest;
392
+ if (!litStr) {
393
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-putscript",
394
+ "guardManageSieveCommand.validate: PUTSCRIPT requires a literal `{N}` or `{N+}` payload announcement");
395
+ }
396
+ var m = LITERAL_RE.exec(litStr); // allow:regex-no-length-cap — LITERAL_RE anchored + bounded digits
397
+ if (!m) {
398
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-literal",
399
+ "guardManageSieveCommand.validate: PUTSCRIPT literal must match `{N}` or `{N+}` (RFC 5804 §2.3 + RFC 7888)");
400
+ }
401
+ var n = parseInt(m[1], 10);
402
+ var isPlus = m[2] === "+";
403
+ if (isPlus && !caps.allowLiteralPlus) {
404
+ throw new GuardManageSieveCommandError("guard-managesieve-command/literal-plus-refused",
405
+ "guardManageSieveCommand.validate: LITERAL+ refused under current profile");
406
+ }
407
+ if (n > caps.maxScriptBytes) {
408
+ throw new GuardManageSieveCommandError("guard-managesieve-command/script-too-large",
409
+ "guardManageSieveCommand.validate: PUTSCRIPT script " + n +
410
+ " bytes exceeds cap " + caps.maxScriptBytes);
411
+ }
412
+ return {
413
+ verb: "PUTSCRIPT",
414
+ args: [parsed.value],
415
+ literalBytes: n,
416
+ literalPlus: isPlus,
417
+ };
418
+ }
419
+
420
+ function _validateSingleName(verb, rest, caps, nameOpts) {
421
+ if (!rest) {
422
+ throw new GuardManageSieveCommandError("guard-managesieve-command/missing-name",
423
+ "guardManageSieveCommand.validate: " + verb + " requires a quoted script-name argument");
424
+ }
425
+ var parsed = _parseQuotedString(rest);
426
+ if (parsed === null || parsed.rest.length > 0) {
427
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-name",
428
+ "guardManageSieveCommand.validate: " + verb + " script-name must be a quoted string");
429
+ }
430
+ if (parsed.value.length === 0) {
431
+ if (!nameOpts.allowEmpty) {
432
+ throw new GuardManageSieveCommandError("guard-managesieve-command/empty-name",
433
+ "guardManageSieveCommand.validate: " + verb + " script-name must be non-empty");
434
+ }
435
+ return { verb: verb, args: [parsed.value] };
436
+ }
437
+ _checkScriptName(parsed.value, caps);
438
+ return { verb: verb, args: [parsed.value] };
439
+ }
440
+
441
+ function _validateRenamescript(rest, caps) {
442
+ if (!rest) {
443
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-rename",
444
+ "guardManageSieveCommand.validate: RENAMESCRIPT requires `\"old\" \"new\"`");
445
+ }
446
+ var first = _parseQuotedString(rest);
447
+ if (first === null || !first.rest) {
448
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-rename",
449
+ "guardManageSieveCommand.validate: RENAMESCRIPT requires two quoted script-name arguments");
450
+ }
451
+ _checkScriptName(first.value, caps);
452
+ var second = _parseQuotedString(first.rest);
453
+ if (second === null || second.rest.length > 0) {
454
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-rename",
455
+ "guardManageSieveCommand.validate: RENAMESCRIPT second arg must be a quoted script-name");
456
+ }
457
+ _checkScriptName(second.value, caps);
458
+ return { verb: "RENAMESCRIPT", args: [first.value, second.value] };
459
+ }
460
+
461
+ // _parseQuotedString — extract a leading `"..."` quoted string from
462
+ // `s` and return `{ value, rest }`, where `rest` is whitespace-trimmed.
463
+ // Returns null if `s` does not begin with a DQUOTE. RFC 5804 §1.2
464
+ // quoted strings allow UTF-8 content and `\"` / `\\` escape sequences.
465
+ function _parseQuotedString(s) {
466
+ if (s.length === 0 || s.charCodeAt(0) !== 0x22) return null; // allow:raw-byte-literal — DQUOTE
467
+ var out = "";
468
+ var i = 1;
469
+ while (i < s.length) {
470
+ var c = s.charCodeAt(i);
471
+ if (c === 0x5C) { // allow:raw-byte-literal — backslash escape
472
+ if (i + 1 >= s.length) return null;
473
+ var esc = s.charCodeAt(i + 1);
474
+ if (esc === 0x22) { out += '"'; i += 2; continue; } // allow:raw-byte-literal — DQUOTE
475
+ if (esc === 0x5C) { out += "\\"; i += 2; continue; } // allow:raw-byte-literal — backslash
476
+ return null;
477
+ }
478
+ if (c === 0x22) { // allow:raw-byte-literal — closing DQUOTE
479
+ var rest = s.slice(i + 1);
480
+ // Trim leading whitespace from rest.
481
+ var k = 0;
482
+ while (k < rest.length && (rest.charCodeAt(k) === 0x20 || rest.charCodeAt(k) === 0x09)) k += 1; // allow:raw-byte-literal — SP / HTAB
483
+ return { value: out, rest: rest.slice(k) };
484
+ }
485
+ if (c === 0x00 || c === 0x0D || c === 0x0A) return null; // allow:raw-byte-literal — NUL/CR/LF refused in quoted strings
486
+ out += s[i];
487
+ i += 1;
488
+ }
489
+ return null;
490
+ }
491
+
492
+ // _isEscaped — DQUOTE at position i is escaped if preceded by an odd
493
+ // number of backslashes. Used by the outer control-byte walker so the
494
+ // in-quote flag doesn't flip on `\"` sequences.
495
+ function _isEscaped(line, i) {
496
+ var n = 0;
497
+ var j = i - 1;
498
+ while (j >= 0 && line.charCodeAt(j) === 0x5C) { n += 1; j -= 1; } // allow:raw-byte-literal — backslash count
499
+ return (n & 1) === 1;
500
+ }
501
+
502
+ // _checkScriptName — RFC 5804 §2.1: 1-512 octets, no NUL/CR/LF/slash/
503
+ // backslash/DQUOTE. The quoted-string parser already refuses NUL/CR/LF
504
+ // + the unescaped DQUOTE that would close the literal; here we
505
+ // additionally refuse the forward-slash + backslash that the
506
+ // quoted-string layer is happy with but RFC 5804 §2.1 explicitly
507
+ // forbids in the script-name production.
508
+ function _checkScriptName(name, caps) {
509
+ if (name.length === 0) {
510
+ throw new GuardManageSieveCommandError("guard-managesieve-command/empty-name",
511
+ "guardManageSieveCommand.validate: script-name must be non-empty (RFC 5804 §2.1)");
512
+ }
513
+ _checkScriptNameBytes(name, caps);
514
+ for (var i = 0; i < name.length; i += 1) {
515
+ var c = name.charCodeAt(i);
516
+ if (c === 0x2F || c === 0x5C) { // allow:raw-byte-literal — forward-slash + backslash refused
517
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-name-byte",
518
+ "guardManageSieveCommand.validate: script-name byte 0x" +
519
+ c.toString(16) + " refused (RFC 5804 §2.1)"); // allow:raw-byte-literal — base-16 toString radix
520
+ }
521
+ if (c === 0x00) { // allow:raw-byte-literal — NUL refused
522
+ throw new GuardManageSieveCommandError("guard-managesieve-command/bad-name-byte",
523
+ "guardManageSieveCommand.validate: NUL byte refused in script-name (RFC 5804 §2.1)");
524
+ }
525
+ }
526
+ }
527
+
528
+ function _checkScriptNameBytes(name, caps) {
529
+ // RFC 5804 §2.1 script-name limits are octet-based, not UTF-16
530
+ // code-unit based. Use Buffer.byteLength so non-ASCII script names
531
+ // (multibyte UTF-8) honor the byte cap and downstream
532
+ // filesystem/storage backends never see a name longer than the
533
+ // advertised limit.
534
+ var byteLen = Buffer.byteLength(name, "utf8");
535
+ if (byteLen > caps.maxScriptNameBytes) {
536
+ throw new GuardManageSieveCommandError("guard-managesieve-command/name-too-long",
537
+ "guardManageSieveCommand.validate: script-name " + byteLen +
538
+ " UTF-8 bytes exceeds cap " + caps.maxScriptNameBytes + " (RFC 5804 §2.1)");
539
+ }
540
+ }
541
+
542
+ /**
543
+ * @primitive b.guardManageSieveCommand.compliancePosture
544
+ * @signature b.guardManageSieveCommand.compliancePosture(posture)
545
+ * @since 0.9.57
546
+ * @status stable
547
+ *
548
+ * Return the effective profile for a compliance posture, or `null`
549
+ * for unknown names.
550
+ *
551
+ * @example
552
+ * b.guardManageSieveCommand.compliancePosture("hipaa"); // → "strict"
553
+ */
554
+ function compliancePosture(posture) {
555
+ return COMPLIANCE_POSTURES[posture] || null;
556
+ }
557
+
558
+ module.exports = {
559
+ validate: validate,
560
+ compliancePosture: compliancePosture,
561
+ PROFILES: PROFILES,
562
+ COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
563
+ KNOWN_VERBS: KNOWN_VERBS,
564
+ ZERO_ARG_VERBS: ZERO_ARG_VERBS,
565
+ GuardManageSieveCommandError: GuardManageSieveCommandError,
566
+ };