@blamejs/core 0.9.49 → 0.10.1
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 +951 -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 +67 -5
- package/lib/circuit-breaker.js +10 -2
- 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-jmap.js +321 -0
- package/lib/guard-managesieve-command.js +566 -0
- package/lib/guard-pop3-command.js +317 -0
- package/lib/guard-smtp-command.js +58 -3
- 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/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/vendor-data.js
CHANGED
|
@@ -38,8 +38,17 @@
|
|
|
38
38
|
|
|
39
39
|
var nodeCrypto = require("node:crypto");
|
|
40
40
|
var safeEnv = require("./parsers/safe-env");
|
|
41
|
+
var lazyRequire = require("./lazy-require");
|
|
41
42
|
var { defineClass } = require("./framework-error");
|
|
42
43
|
var pqcSoftware = require("./pqc-software");
|
|
44
|
+
var bCrypto = lazyRequire(function () { return require("./crypto"); });
|
|
45
|
+
|
|
46
|
+
var _audit = lazyRequire(function () { return require("./audit"); });
|
|
47
|
+
|
|
48
|
+
// Lazy: audit imports b.crypto which imports b.audit indirectly via
|
|
49
|
+
// vendor-data verifyAll on framework boot. Defer the audit module
|
|
50
|
+
// until the first opt-out / digest-mismatch path runs.
|
|
51
|
+
var audit = lazyRequire(function () { return require("./audit"); });
|
|
43
52
|
|
|
44
53
|
// Framework-pinned vendor-data public key. Inlined as a CommonJS
|
|
45
54
|
// module (lib/vendor/vendor-data-pubkey.js) so the loader has zero
|
|
@@ -166,9 +175,58 @@ function _loadAndVerify(name) {
|
|
|
166
175
|
"expected=" + meta.sha3_512.slice(0, 12) + "… got=" + actual3.slice(0, 12) + "…");
|
|
167
176
|
}
|
|
168
177
|
|
|
169
|
-
//
|
|
170
|
-
|
|
178
|
+
// Cross-check meta.publicKeyFingerprint against the inlined pubkey
|
|
179
|
+
// before signature verify. An attacker who has swapped BOTH the
|
|
180
|
+
// payload AND the pubkey module (matched signature under the swap
|
|
181
|
+
// key) would otherwise pass every previous integrity layer; the
|
|
182
|
+
// fingerprint becomes decorative. Compute the sha3-512(PUBKEY_PEM)
|
|
183
|
+
// once at module load (memoized below) and compare each entry's
|
|
184
|
+
// declared fingerprint against the actual pubkey we'll verify
|
|
185
|
+
// under.
|
|
186
|
+
var actualPubkeyFp = _actualPubkeyFingerprint();
|
|
187
|
+
if (typeof meta.publicKeyFingerprint === "string" &&
|
|
188
|
+
meta.publicKeyFingerprint.length > 0 &&
|
|
189
|
+
meta.publicKeyFingerprint !== actualPubkeyFp) {
|
|
190
|
+
throw new VendorDataError("vendor-data/pubkey-fingerprint-mismatch",
|
|
191
|
+
"vendorData: '" + name + "' declared publicKeyFingerprint '" +
|
|
192
|
+
meta.publicKeyFingerprint + "' does not match the inlined pubkey " +
|
|
193
|
+
"fingerprint '" + actualPubkeyFp + "'. An attacker has either swapped " +
|
|
194
|
+
"the pubkey module + payload + signature in lockstep (impossible " +
|
|
195
|
+
"without the maintainer private key) OR the .data.js file was " +
|
|
196
|
+
"generated against a different signing key. Re-run scripts/vendor-data-keygen.js " +
|
|
197
|
+
"+ scripts/vendor-update.sh --refresh-data.");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Layer 3a: operator-supplied fingerprint pin. SLSA L3 / in-toto
|
|
201
|
+
// attestation pattern — the runtime trust root (PUBKEY_PEM, inlined
|
|
202
|
+
// at build time) defends against payload swap; an operator-supplied
|
|
203
|
+
// env-var pin defends against attacker-swap of both pubkey AND
|
|
204
|
+
// signature in the same compromise. The pin is SHA-256 over the
|
|
205
|
+
// PEM-decoded raw SPKI bytes; operators capture it once at install
|
|
206
|
+
// time from a trusted channel (npm provenance / sigstore attestation
|
|
207
|
+
// / out-of-band copy) and re-verify on every boot.
|
|
171
208
|
var pubkeyBytes = _pemToRaw(PUBKEY_PEM);
|
|
209
|
+
var pubkeyFp = nodeCrypto.createHash("sha256").update(pubkeyBytes).digest("hex");
|
|
210
|
+
var operatorFp = safeEnv.readVar("BLAMEJS_VENDOR_DATA_PUBKEY_FINGERPRINT", { default: "" });
|
|
211
|
+
if (operatorFp.length > 0) {
|
|
212
|
+
var normalizedOperatorFp = operatorFp.replace(/^sha256:/i, "").toLowerCase().trim();
|
|
213
|
+
if (!/^[0-9a-f]{64}$/.test(normalizedOperatorFp)) {
|
|
214
|
+
throw new VendorDataError("vendor-data/operator-fingerprint-bad-shape",
|
|
215
|
+
"vendorData: BLAMEJS_VENDOR_DATA_PUBKEY_FINGERPRINT must be a 64-hex SHA-256 (optionally `sha256:`-prefixed)");
|
|
216
|
+
}
|
|
217
|
+
// Length-tolerant timing-safe compare via b.crypto.timingSafeEqual.
|
|
218
|
+
if (!bCrypto.timingSafeEqual(normalizedOperatorFp, pubkeyFp)) {
|
|
219
|
+
throw new VendorDataError("vendor-data/operator-fingerprint-mismatch",
|
|
220
|
+
"vendorData: '" + name + "' SLH-DSA pubkey fingerprint does not match the " +
|
|
221
|
+
"operator-supplied BLAMEJS_VENDOR_DATA_PUBKEY_FINGERPRINT pin. " +
|
|
222
|
+
"expected=" + normalizedOperatorFp.slice(0, 12) + "… got=" +
|
|
223
|
+
pubkeyFp.slice(0, 12) + "…. The inlined PUBKEY_PEM has been swapped " +
|
|
224
|
+
"from what the operator captured at install time — refuse to load.");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Layer 3b: SLH-DSA-SHAKE-256f signature verify against maintainer pubkey
|
|
229
|
+
var sigBytes = Buffer.from(meta.signatureB64, "base64");
|
|
172
230
|
var slh = pqcSoftware.slh_dsa_shake_256f;
|
|
173
231
|
var sigOk = false;
|
|
174
232
|
try {
|
|
@@ -206,6 +264,22 @@ function _loadAndVerify(name) {
|
|
|
206
264
|
return mod.payload;
|
|
207
265
|
}
|
|
208
266
|
|
|
267
|
+
// Memoized — "sha256:" + sha256(pemToRaw(PUBKEY_PEM)). Matches the
|
|
268
|
+
// canonical fingerprint shape `scripts/vendor-data-gen.js` writes into
|
|
269
|
+
// each .data.js's `metadata.publicKeyFingerprint`. Computed lazily on
|
|
270
|
+
// first verify (also lazily by verifyAll at boot). CRYPTO-11 — every
|
|
271
|
+
// per-entry verify cross-checks this against the entry's declared
|
|
272
|
+
// `meta.publicKeyFingerprint` so a pubkey-swap attack fails before
|
|
273
|
+
// signature verify even runs.
|
|
274
|
+
var _PUBKEY_FINGERPRINT = null;
|
|
275
|
+
function _actualPubkeyFingerprint() {
|
|
276
|
+
if (_PUBKEY_FINGERPRINT !== null) return _PUBKEY_FINGERPRINT;
|
|
277
|
+
var raw = _pemToRaw(PUBKEY_PEM);
|
|
278
|
+
_PUBKEY_FINGERPRINT = "sha256:" +
|
|
279
|
+
nodeCrypto.createHash("sha256").update(raw).digest("hex");
|
|
280
|
+
return _PUBKEY_FINGERPRINT;
|
|
281
|
+
}
|
|
282
|
+
|
|
209
283
|
// _pemToRaw — extract the raw SPKI bytes from a PEM-wrapped public key.
|
|
210
284
|
// The .vendor-data-pubkey file ships as PEM (BEGIN PUBLIC KEY ... END
|
|
211
285
|
// PUBLIC KEY); the SLH-DSA verifier expects raw bytes. We strip the
|
|
@@ -315,7 +389,16 @@ function verifyAll() {
|
|
|
315
389
|
* " — signed by " + entry.signedBy);
|
|
316
390
|
* });
|
|
317
391
|
*/
|
|
392
|
+
// Rendered-inventory cache. Operators wire `b.vendorData.inventory()`
|
|
393
|
+
// into compliance-reporting endpoints (e.g. /admin/vendor-inventory)
|
|
394
|
+
// where the call surface is read-heavy and the underlying inputs
|
|
395
|
+
// (verified payload buffers + frozen KNOWN_VENDOR_DATA + immutable
|
|
396
|
+
// .data.js metadata) never change after boot. Cache once and serve
|
|
397
|
+
// the same frozen array reference. ~30ms saved per call.
|
|
398
|
+
var _inventoryCache = null;
|
|
399
|
+
|
|
318
400
|
function inventory() {
|
|
401
|
+
if (_inventoryCache !== null) return _inventoryCache;
|
|
319
402
|
var out = [];
|
|
320
403
|
var names = Object.keys(KNOWN_VENDOR_DATA);
|
|
321
404
|
for (var i = 0; i < names.length; i++) {
|
|
@@ -324,7 +407,7 @@ function inventory() {
|
|
|
324
407
|
_loadAndVerify(name);
|
|
325
408
|
var mod = _MODULES[name];
|
|
326
409
|
var meta = mod.metadata;
|
|
327
|
-
out.push({
|
|
410
|
+
out.push(Object.freeze({
|
|
328
411
|
name: name,
|
|
329
412
|
source: meta.source,
|
|
330
413
|
fetchedAt: meta.fetchedAt,
|
|
@@ -335,9 +418,10 @@ function inventory() {
|
|
|
335
418
|
canary: entry.canary,
|
|
336
419
|
byteLength: mod.payload.length,
|
|
337
420
|
description: entry.description,
|
|
338
|
-
});
|
|
421
|
+
}));
|
|
339
422
|
}
|
|
340
|
-
|
|
423
|
+
_inventoryCache = Object.freeze(out);
|
|
424
|
+
return _inventoryCache;
|
|
341
425
|
}
|
|
342
426
|
|
|
343
427
|
// Eager verification at module-load. The first time anything
|
|
@@ -346,10 +430,44 @@ function inventory() {
|
|
|
346
430
|
// framework consumer's import graph), every registered vendor data
|
|
347
431
|
// file is dual-hash + signature + canary-verified before any caller
|
|
348
432
|
// gets the chance to call get(). Tamper = fail-fast at boot, not at
|
|
349
|
-
// first-request-touches-PSL surprise.
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
|
|
433
|
+
// first-request-touches-PSL surprise.
|
|
434
|
+
//
|
|
435
|
+
// Operators wanting to defer verification (test rigs that mock
|
|
436
|
+
// require() resolution, install-pipeline contexts that intentionally
|
|
437
|
+
// run before the trust roots are populated) must opt in via TWO env
|
|
438
|
+
// vars: BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY=1 PLUS a non-empty
|
|
439
|
+
// BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY_REASON explaining WHY.
|
|
440
|
+
// Setting the flag alone is refused so a misconfigured CI / Docker
|
|
441
|
+
// image can't silently bypass tamper detection. SSDF PW.4 — every
|
|
442
|
+
// security-default-disable lives in the audit log with an operator-
|
|
443
|
+
// attributed reason.
|
|
444
|
+
var _deferFlag = safeEnv.readVar("BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY", { default: "" });
|
|
445
|
+
if (_deferFlag === "1") {
|
|
446
|
+
var _deferReason = safeEnv.readVar("BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY_REASON", { default: "" });
|
|
447
|
+
if (_deferReason.length === 0) {
|
|
448
|
+
throw new VendorDataError("vendor-data/defer-reason-required",
|
|
449
|
+
"vendorData: BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY=1 set WITHOUT " +
|
|
450
|
+
"BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY_REASON. Boot-time vendor " +
|
|
451
|
+
"verification is a security default — disabling it requires the " +
|
|
452
|
+
"operator to record WHY in the env so the misconfig is grep-able in " +
|
|
453
|
+
"the next deploy diff. Set BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY_REASON " +
|
|
454
|
+
"to a non-empty string (e.g. 'test-rig:require-mock' or 'install-pipeline:pre-trust-root-populated').");
|
|
455
|
+
}
|
|
456
|
+
// Audit emit is best-effort: the audit module may not be initialized
|
|
457
|
+
// yet (boot sequencing). Operators alerting on "boot-time
|
|
458
|
+
// verification deferred" catch a malicious pivot that set the flag
|
|
459
|
+
// through a compromised env source.
|
|
460
|
+
try {
|
|
461
|
+
audit().safeEmit({
|
|
462
|
+
action: "vendor-data.boot_verify_deferred",
|
|
463
|
+
outcome: "denied",
|
|
464
|
+
metadata: {
|
|
465
|
+
reason: _deferReason.slice(0, 256), // allow:raw-byte-literal — audit metadata truncation limit
|
|
466
|
+
vendorDataKnown: Object.keys(KNOWN_VENDOR_DATA),
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
} catch (_e) { /* drop-silent — audit not ready at boot */ }
|
|
470
|
+
} else {
|
|
353
471
|
verifyAll();
|
|
354
472
|
}
|
|
355
473
|
|