@blamejs/core 0.9.46 → 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.
Files changed (78) hide show
  1. package/CHANGELOG.md +951 -893
  2. package/index.js +30 -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 +67 -5
  23. package/lib/circuit-breaker.js +10 -2
  24. package/lib/compliance.js +176 -8
  25. package/lib/crypto-field.js +114 -14
  26. package/lib/crypto.js +216 -20
  27. package/lib/db.js +1 -0
  28. package/lib/guard-imap-command.js +335 -0
  29. package/lib/guard-jmap.js +321 -0
  30. package/lib/guard-managesieve-command.js +566 -0
  31. package/lib/guard-pop3-command.js +317 -0
  32. package/lib/guard-smtp-command.js +58 -3
  33. package/lib/mail-agent.js +20 -7
  34. package/lib/mail-arc-sign.js +12 -8
  35. package/lib/mail-auth.js +323 -34
  36. package/lib/mail-crypto-pgp.js +934 -0
  37. package/lib/mail-crypto-smime.js +340 -0
  38. package/lib/mail-crypto.js +108 -0
  39. package/lib/mail-dav.js +1224 -0
  40. package/lib/mail-deploy.js +492 -0
  41. package/lib/mail-dkim.js +431 -26
  42. package/lib/mail-journal.js +435 -0
  43. package/lib/mail-scan.js +502 -0
  44. package/lib/mail-server-imap.js +1102 -0
  45. package/lib/mail-server-jmap.js +488 -0
  46. package/lib/mail-server-managesieve.js +853 -0
  47. package/lib/mail-server-mx.js +164 -34
  48. package/lib/mail-server-pop3.js +836 -0
  49. package/lib/mail-server-rate-limit.js +269 -0
  50. package/lib/mail-server-submission.js +1032 -0
  51. package/lib/mail-server-tls.js +445 -0
  52. package/lib/mail-sieve.js +557 -0
  53. package/lib/mail-spam-score.js +284 -0
  54. package/lib/mail.js +99 -0
  55. package/lib/metrics.js +130 -10
  56. package/lib/middleware/dpop.js +58 -3
  57. package/lib/middleware/idempotency-key.js +255 -42
  58. package/lib/middleware/protected-resource-metadata.js +114 -2
  59. package/lib/network-dns-resolver.js +33 -0
  60. package/lib/network-tls.js +46 -0
  61. package/lib/outbox.js +62 -12
  62. package/lib/pqc-agent.js +13 -5
  63. package/lib/retry.js +23 -9
  64. package/lib/router.js +23 -1
  65. package/lib/safe-ical.js +634 -0
  66. package/lib/safe-icap.js +502 -0
  67. package/lib/safe-mime.js +15 -0
  68. package/lib/safe-sieve.js +684 -0
  69. package/lib/safe-smtp.js +57 -0
  70. package/lib/safe-url.js +37 -0
  71. package/lib/safe-vcard.js +473 -0
  72. package/lib/self-update-standalone-verifier.js +32 -3
  73. package/lib/self-update.js +168 -17
  74. package/lib/vendor/MANIFEST.json +161 -156
  75. package/lib/vendor-data.js +127 -9
  76. package/lib/vex.js +324 -59
  77. package/package.json +1 -1
  78. package/sbom.cdx.json +6 -6
@@ -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
- // Layer 3: SLH-DSA-SHAKE-256f signature verify against maintainer pubkey
170
- var sigBytes = Buffer.from(meta.signatureB64, "base64");
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
- return out;
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. Operators wanting to defer
350
- // verification (e.g. test rigs that mock require() resolution) set
351
- // BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY=1 in the environment.
352
- if (safeEnv.readVar("BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY", { default: "" }) !== "1") {
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