@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.
- package/CHANGELOG.md +952 -908
- package/index.js +25 -0
- package/lib/_test/crypto-fixtures.js +67 -0
- package/lib/agent-event-bus.js +52 -6
- package/lib/agent-idempotency.js +169 -16
- package/lib/agent-orchestrator.js +263 -9
- package/lib/agent-posture-chain.js +163 -5
- package/lib/agent-saga.js +146 -16
- package/lib/agent-snapshot.js +349 -19
- package/lib/agent-stream.js +34 -2
- package/lib/agent-tenant.js +179 -23
- package/lib/agent-trace.js +84 -21
- package/lib/auth/aal.js +8 -1
- package/lib/auth/ciba.js +6 -1
- package/lib/auth/dpop.js +7 -2
- package/lib/auth/fal.js +17 -8
- package/lib/auth/jwt-external.js +128 -4
- package/lib/auth/oauth.js +232 -10
- package/lib/auth/oid4vci.js +67 -7
- package/lib/auth/openid-federation.js +71 -25
- package/lib/auth/passkey.js +140 -6
- package/lib/auth/sd-jwt-vc.js +78 -5
- package/lib/circuit-breaker.js +10 -2
- package/lib/cli.js +13 -0
- package/lib/compliance.js +176 -8
- package/lib/crypto-field.js +114 -14
- package/lib/crypto.js +216 -20
- package/lib/db.js +1 -0
- package/lib/guard-graphql.js +37 -0
- package/lib/guard-jmap.js +321 -0
- package/lib/guard-managesieve-command.js +566 -0
- package/lib/guard-pop3-command.js +317 -0
- package/lib/guard-regex.js +138 -1
- package/lib/guard-smtp-command.js +58 -3
- package/lib/guard-xml.js +39 -1
- package/lib/mail-agent.js +20 -7
- package/lib/mail-arc-sign.js +12 -8
- package/lib/mail-auth.js +323 -34
- package/lib/mail-crypto-pgp.js +934 -0
- package/lib/mail-crypto-smime.js +340 -0
- package/lib/mail-crypto.js +108 -0
- package/lib/mail-dav.js +1224 -0
- package/lib/mail-deploy.js +492 -0
- package/lib/mail-dkim.js +431 -26
- package/lib/mail-journal.js +435 -0
- package/lib/mail-scan.js +502 -0
- package/lib/mail-server-imap.js +64 -26
- package/lib/mail-server-jmap.js +488 -0
- package/lib/mail-server-managesieve.js +853 -0
- package/lib/mail-server-mx.js +40 -30
- package/lib/mail-server-pop3.js +836 -0
- package/lib/mail-server-rate-limit.js +13 -0
- package/lib/mail-server-submission.js +70 -24
- package/lib/mail-server-tls.js +445 -0
- package/lib/mail-sieve.js +557 -0
- package/lib/mail-spam-score.js +284 -0
- package/lib/mail.js +99 -0
- package/lib/metrics.js +80 -3
- package/lib/middleware/dpop.js +58 -3
- package/lib/middleware/idempotency-key.js +255 -42
- package/lib/middleware/protected-resource-metadata.js +114 -2
- package/lib/network-dns-resolver.js +33 -0
- package/lib/network-tls.js +46 -0
- package/lib/otel-export.js +13 -4
- package/lib/outbox.js +62 -12
- package/lib/pqc-agent.js +13 -5
- package/lib/retry.js +23 -9
- package/lib/router.js +23 -1
- package/lib/safe-ical.js +634 -0
- package/lib/safe-icap.js +502 -0
- package/lib/safe-mime.js +15 -0
- package/lib/safe-sieve.js +684 -0
- package/lib/safe-smtp.js +57 -0
- package/lib/safe-url.js +37 -0
- package/lib/safe-vcard.js +473 -0
- package/lib/self-update-standalone-verifier.js +32 -3
- package/lib/self-update.js +153 -33
- package/lib/vendor/MANIFEST.json +161 -156
- package/lib/vendor-data.js +127 -9
- package/lib/vex.js +324 -59
- package/package.json +1 -1
- 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
|
-
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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) {
|
package/lib/mail-arc-sign.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|