@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
package/lib/guard-xml.js CHANGED
@@ -91,6 +91,16 @@ var PROCESSING_INSTR_RE = /<\?[A-Za-z][\w:-]*/;
91
91
  var CDATA_RE = /<!\[CDATA\[/;
92
92
  var XMLDSIG_RE = /<\w*:?Signature\b[^>]*xmldsig/i;
93
93
 
94
+ // Numeric character reference (NCR) detector. Per XML 1.0 §4.1 every
95
+ // `&#<digits>;` / `&#x<hex>;` is a character reference; a hostile input
96
+ // fanning these out in the hundreds of thousands bypasses entity-
97
+ // expansion caps that count only `&name;` general entities (CVE-2026-
98
+ // 26278 / CVE-2026-33036 .NET XmlReader class). Per-document NCR count
99
+ // is gated by `maxNumericCharRefs` independent of the entity-policy
100
+ // branch so the operator can't disable the cap by setting
101
+ // `entityPolicy: "allow"` for a downstream signed-XML case.
102
+ var NUMERIC_CHAR_REF_RE = /&#(?:[0-9]+|x[0-9a-fA-F]+);/g; // allow:regex-no-length-cap — input bounded by maxBytes above
103
+
94
104
  // ---- Profile presets ----
95
105
 
96
106
  var PROFILES = Object.freeze({
@@ -112,6 +122,7 @@ var PROFILES = Object.freeze({
112
122
  maxElements: 8192, // allow:raw-byte-literal — element count cap, not byte size
113
123
  maxAttrsPerElement: 64, // allow:raw-byte-literal — attr count, not byte size
114
124
  maxAttrValueBytes: C.BYTES.kib(8),
125
+ maxNumericCharRefs: 1024, // allow:raw-byte-literal — NCR fan-out cap (CVE-2026-26278)
115
126
  },
116
127
  "balanced": {
117
128
  doctypePolicy: "reject", // DOCTYPE is XXE vector regardless
@@ -131,6 +142,7 @@ var PROFILES = Object.freeze({
131
142
  maxElements: 65536, // allow:raw-byte-literal — element count cap, not byte size
132
143
  maxAttrsPerElement: 128, // allow:raw-byte-literal — attr count, not byte size
133
144
  maxAttrValueBytes: C.BYTES.kib(32),
145
+ maxNumericCharRefs: 16384, // allow:raw-byte-literal — NCR fan-out cap (CVE-2026-26278)
134
146
  },
135
147
  "permissive": {
136
148
  doctypePolicy: "reject", // billion-laughs class always
@@ -150,6 +162,7 @@ var PROFILES = Object.freeze({
150
162
  maxElements: 262144, // allow:raw-byte-literal — element count cap, not byte size
151
163
  maxAttrsPerElement: 256, // allow:raw-byte-literal — attr count, not byte size
152
164
  maxAttrValueBytes: C.BYTES.kib(64),
165
+ maxNumericCharRefs: 262144, // allow:raw-byte-literal — NCR fan-out cap (CVE-2026-26278)
153
166
  },
154
167
  });
155
168
 
@@ -282,6 +295,30 @@ function _detectIssues(input, opts) {
282
295
  });
283
296
  }
284
297
 
298
+ // 8a. Numeric character reference fan-out — `&#NNNN;` / `&#xHHHH;`.
299
+ // Bypasses the `<!ENTITY>`-counting expansion caps because NCRs are
300
+ // parser-resolved, not document-level entities (CVE-2026-26278 /
301
+ // CVE-2026-33036 .NET XmlReader class). Counted regardless of
302
+ // entityPolicy so signed-XML paths that need entities-allowed don't
303
+ // get the NCR cap disabled with them. The `maxNumericCharRefs` opt
304
+ // is validated by `numericBounds.requireAllPositiveFiniteIntIfPresent`
305
+ // at the public-surface boundary above.
306
+ var ncrCap = opts.maxNumericCharRefs; // allow:numeric-opt-no-bounds-check — validated at public boundary
307
+ if (ncrCap !== undefined && ncrCap !== null) {
308
+ var ncrMatches = input.match(NUMERIC_CHAR_REF_RE); // allow:regex-no-length-cap — input bounded by maxBytes above
309
+ var ncrCount = ncrMatches === null ? 0 : ncrMatches.length;
310
+ if (ncrCount > ncrCap) {
311
+ issues.push({
312
+ kind: "numeric-char-ref-cap", severity: "critical",
313
+ ruleId: "xml.numeric-char-ref-cap",
314
+ snippet: "numeric character reference count " + ncrCount +
315
+ " exceeds maxNumericCharRefs " + ncrCap +
316
+ " — NCR fan-out bypasses entity-expansion caps " +
317
+ "(CVE-2026-26278 / CVE-2026-33036)",
318
+ });
319
+ }
320
+ }
321
+
285
322
  // 9. Codepoint-class threats.
286
323
  issues.push.apply(issues, codepointClass.detectCharThreats(input, opts, "xml"));
287
324
 
@@ -369,6 +406,7 @@ function _detectIssues(input, opts) {
369
406
  * maxElements: number, // total open-tag count cap
370
407
  * maxAttrsPerElement: number, // attribute count cap per element
371
408
  * maxAttrValueBytes: number, // per-attr-value length cap
409
+ * maxNumericCharRefs: number, // numeric character reference cap
372
410
  *
373
411
  * @example
374
412
  * var hostile = '<?xml version="1.0"?>\n' +
@@ -381,7 +419,7 @@ function validate(input, opts) {
381
419
  opts = _resolveOpts(opts);
382
420
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
383
421
  ["maxBytes", "maxDepth", "maxElements", "maxAttrsPerElement",
384
- "maxAttrValueBytes"],
422
+ "maxAttrValueBytes", "maxNumericCharRefs"],
385
423
  "guardXml.validate", GuardXmlError, "xml.bad-opt");
386
424
  if (typeof input !== "string") {
387
425
  return {
package/lib/mail-agent.js CHANGED
@@ -485,17 +485,30 @@ async function _delete(ctx, args) {
485
485
  }
486
486
 
487
487
  async function _sievePut(ctx, args) {
488
- // Pre-parse shape-only validation lands today; full b.safeSieve
489
- // parse lands v0.9.26 and will be invoked from the throw-stub at
490
- // that slice. The agent-level guard lets operators wire RBAC + name
491
- // shape today.
488
+ // Two-stage validation: agent-level shape guard for RBAC + name +
489
+ // size, then the full RFC 5228 grammar parse via b.safeSieve. The
490
+ // grammar parse refuses unknown / not-yet-implemented capabilities
491
+ // at `require` time (RFC 5228 §3.2) so the operator's persistence
492
+ // step never gets a script the framework can't actually execute.
492
493
  _entry(ctx, "sieve.put", args);
493
494
  guardMailSieve.validate({
494
495
  kind: "put", actor: args.actor, name: args.name, script: args.script,
495
496
  }, { profile: _profileFor(ctx), posture: ctx.posture, ownedNames: args.ownedNames });
496
- throw new MailAgentError("mail-agent/not-implemented",
497
- "agent.sieve.put: full Sieve parser lands at v0.9.26 (b.safeSieve); " +
498
- "shape-only validation passed — wire the persistence step in the operator handler");
497
+ var safeSieve = require("./safe-sieve"); // allow:inline-require — lazy-load until first sieve.put call
498
+ var rv = safeSieve.validate(args.script, {
499
+ profile: _profileFor(ctx),
500
+ compliancePosture: ctx.posture,
501
+ });
502
+ if (!rv.ok) {
503
+ throw new MailAgentError("mail-agent/sieve-parse-error",
504
+ "agent.sieve.put: Sieve script refused — " +
505
+ (rv.issues[0] && rv.issues[0].snippet ? rv.issues[0].snippet : "parse failed"));
506
+ }
507
+ ctx.auditEmit("mail.agent.sieve.put", args && args.actor, {
508
+ name: args.name,
509
+ requiredCaps: rv.requiredCaps,
510
+ });
511
+ return { ok: true, requiredCaps: rv.requiredCaps };
499
512
  }
500
513
 
501
514
  function _notImplemented(ctx, method, args) {
@@ -52,6 +52,7 @@ var nodeCrypto = require("node:crypto");
52
52
  var lazyRequire = require("./lazy-require");
53
53
  var validateOpts = require("./validate-opts");
54
54
  var safeBuffer = require("./safe-buffer");
55
+ var dkim = require("./mail-dkim");
55
56
  var { defineClass } = require("./framework-error");
56
57
 
57
58
  var MailAuthError = defineClass("MailAuthError", { alwaysPermanent: true });
@@ -107,20 +108,23 @@ function _canonRelaxedHeader(name, value) {
107
108
  return name.toLowerCase() + ":" + trimmed + "\r\n";
108
109
  }
109
110
 
111
+ // RFC 8617 §5.1.1 references RFC 6376 §3.4.4 for body canonicalization.
112
+ // The DKIM verifier and signer share `_canonBodyRelaxed`; ARC MUST
113
+ // produce a byte-identical canon so a downstream ARC-verifier (which
114
+ // composes the DKIM verifier per §5.1.1) reaches the same body hash.
115
+ // Earlier inline shape collapsed `[ \t]+` across newlines (the regex
116
+ // is global and not bound per-line), which diverged from DKIM's
117
+ // per-line `safeBuffer.stripTrailingHspace` on a line whose only WSP
118
+ // run sat at the end. Compose the DKIM canon directly.
110
119
  function _canonRelaxedBody(body) {
111
- // Relaxed body canon: collapse runs of WSP within lines, strip
112
- // trailing WSP, remove all trailing empty lines, append single CRLF
113
- // unless body is empty.
114
- if (body.length === 0) return "";
115
- var normalized = body.replace(/\r?\n/g, "\r\n");
116
- var collapsed = normalized.replace(/[ \t]+/g, " ").replace(/[ \t]+\r\n/g, "\r\n");
117
- collapsed = collapsed.replace(/(\r\n)+$/, "");
118
- return collapsed + "\r\n";
120
+ return dkim._canonBodyRelaxedForTest(body || "");
119
121
  }
120
122
 
121
123
  function _bodyHashB64(body, algorithm) {
122
124
  var hashAlgo = algorithm.indexOf("sha256") !== -1 ? "sha256" : "sha512";
123
125
  var canonical = _canonRelaxedBody(body);
126
+ // RFC 6376 §3.4.4 — empty body canon is `\r\n` (one CRLF). Hash
127
+ // includes that CRLF.
124
128
  return nodeCrypto.createHash(hashAlgo).update(canonical).digest("base64");
125
129
  }
126
130