@blamejs/core 0.14.6 → 0.14.8

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 (110) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +3 -2
  3. package/lib/a2a-tasks.js +6 -6
  4. package/lib/agent-event-bus.js +4 -4
  5. package/lib/agent-idempotency.js +6 -6
  6. package/lib/agent-orchestrator.js +9 -9
  7. package/lib/agent-posture-chain.js +10 -10
  8. package/lib/agent-saga.js +6 -7
  9. package/lib/agent-snapshot.js +8 -8
  10. package/lib/agent-stream.js +3 -3
  11. package/lib/agent-tenant.js +4 -4
  12. package/lib/agent-trace.js +5 -5
  13. package/lib/ai-disclosure.js +3 -3
  14. package/lib/ai-input.js +1 -1
  15. package/lib/app.js +2 -2
  16. package/lib/archive-read.js +1 -1
  17. package/lib/archive-tar-read.js +1 -1
  18. package/lib/archive-wrap.js +5 -5
  19. package/lib/audit-tools.js +65 -5
  20. package/lib/audit.js +2 -2
  21. package/lib/auth/acr-vocabulary.js +1 -1
  22. package/lib/auth/ciba.js +4 -4
  23. package/lib/auth/dpop.js +1 -1
  24. package/lib/auth/fal.js +1 -1
  25. package/lib/auth/fido-mds3.js +2 -3
  26. package/lib/auth/jwt-external.js +2 -2
  27. package/lib/auth/oauth.js +10 -10
  28. package/lib/auth/oid4vci.js +8 -8
  29. package/lib/auth/oid4vp.js +1 -1
  30. package/lib/auth/openid-federation.js +6 -6
  31. package/lib/auth/passkey.js +6 -6
  32. package/lib/auth/saml.js +1 -1
  33. package/lib/auth/sd-jwt-vc.js +3 -6
  34. package/lib/backup/index.js +18 -18
  35. package/lib/breach-deadline.js +3 -3
  36. package/lib/cache.js +4 -4
  37. package/lib/calendar.js +7 -7
  38. package/lib/circuit-breaker.js +1 -1
  39. package/lib/cms-codec.js +2 -2
  40. package/lib/compliance.js +14 -14
  41. package/lib/content-credentials.js +3 -3
  42. package/lib/crypto-field.js +58 -21
  43. package/lib/crypto.js +5 -6
  44. package/lib/db-query.js +131 -9
  45. package/lib/db.js +106 -22
  46. package/lib/ddl-change-control.js +2 -2
  47. package/lib/did.js +2 -2
  48. package/lib/dsr.js +4 -4
  49. package/lib/external-db.js +65 -17
  50. package/lib/framework-schema.js +4 -4
  51. package/lib/guard-cidr.js +1 -1
  52. package/lib/guard-image.js +1 -1
  53. package/lib/guard-list-id.js +2 -2
  54. package/lib/guard-list-unsubscribe.js +2 -3
  55. package/lib/guard-time.js +1 -1
  56. package/lib/guard-xml.js +1 -1
  57. package/lib/http-client-cache.js +1 -1
  58. package/lib/iab-tcf.js +4 -4
  59. package/lib/incident-report.js +150 -0
  60. package/lib/json-schema.js +1 -1
  61. package/lib/jtd.js +1 -1
  62. package/lib/mail-auth.js +1 -1
  63. package/lib/mail-bimi.js +1 -1
  64. package/lib/mail-crypto-smime.js +2 -2
  65. package/lib/mail-deploy.js +3 -3
  66. package/lib/mail-server-managesieve.js +2 -2
  67. package/lib/mail-server-mx.js +1 -1
  68. package/lib/mail-server-pop3.js +2 -2
  69. package/lib/mail-server-rate-limit.js +1 -1
  70. package/lib/mail-server-submission.js +1 -1
  71. package/lib/mail-store.js +1 -1
  72. package/lib/mcp.js +7 -7
  73. package/lib/mdoc.js +1 -1
  74. package/lib/metrics.js +10 -10
  75. package/lib/middleware/compose-pipeline.js +1 -1
  76. package/lib/middleware/csrf-protect.js +1 -1
  77. package/lib/middleware/dpop.js +5 -5
  78. package/lib/middleware/idempotency-key.js +21 -22
  79. package/lib/middleware/protected-resource-metadata.js +2 -2
  80. package/lib/network-dns-resolver.js +2 -2
  81. package/lib/network-dns.js +1 -2
  82. package/lib/network-dnssec.js +2 -2
  83. package/lib/network-smtp-policy.js +1 -1
  84. package/lib/network-tls.js +1 -2
  85. package/lib/network-tsig.js +3 -3
  86. package/lib/outbox.js +1 -1
  87. package/lib/pqc-agent.js +1 -1
  88. package/lib/retention.js +1 -1
  89. package/lib/retry.js +1 -1
  90. package/lib/rfc3339.js +2 -2
  91. package/lib/safe-archive.js +2 -2
  92. package/lib/safe-decompress.js +1 -1
  93. package/lib/safe-ical.js +2 -2
  94. package/lib/safe-mime.js +1 -1
  95. package/lib/self-update-standalone-verifier.js +1 -1
  96. package/lib/self-update.js +2 -2
  97. package/lib/standard-webhooks.js +3 -3
  98. package/lib/static.js +1 -1
  99. package/lib/stream-throttle.js +2 -2
  100. package/lib/structured-fields.js +1 -1
  101. package/lib/subject.js +2 -2
  102. package/lib/vault/index.js +64 -1
  103. package/lib/vault/rotate.js +19 -0
  104. package/lib/vault/seal-pem-file.js +1 -1
  105. package/lib/vendor-data.js +1 -1
  106. package/lib/web-push-vapid.js +1 -1
  107. package/lib/webhook.js +1 -1
  108. package/lib/websocket.js +1 -1
  109. package/package.json +1 -1
  110. package/sbom.cdx.json +6 -6
@@ -32,7 +32,7 @@ var { defineClass } = require("./framework-error");
32
32
 
33
33
  var StandardWebhooksError = defineClass("StandardWebhooksError", { alwaysPermanent: true });
34
34
 
35
- var DEFAULT_TOLERANCE_SEC = 300; // allow:raw-time-literal — 5min default per StandardWebhooks §3.2
35
+ var DEFAULT_TOLERANCE_SEC = 300;
36
36
 
37
37
  /**
38
38
  * @primitive b.standardWebhooks.sign
@@ -70,7 +70,7 @@ function sign(opts) {
70
70
  var id = opts.id || ("msg_" + bCrypto.generateToken(32)); // 32-char id token
71
71
  var timestamp = typeof opts.timestamp === "number"
72
72
  ? opts.timestamp
73
- : Math.floor(Date.now() / 1000); // allow:raw-time-literal — wall-clock seconds
73
+ : Math.floor(Date.now() / 1000);
74
74
  if (timestamp <= 0 || !isFinite(timestamp)) {
75
75
  throw new StandardWebhooksError("standard-webhooks/bad-timestamp",
76
76
  "sign: timestamp must be a positive finite integer");
@@ -148,7 +148,7 @@ function verify(opts) {
148
148
  numericBounds.requirePositiveFiniteIntIfPresent(opts.toleranceSec, "toleranceSec",
149
149
  StandardWebhooksError, "standard-webhooks/bad-tolerance");
150
150
  var tolerance = typeof opts.toleranceSec === "number" ? opts.toleranceSec : DEFAULT_TOLERANCE_SEC;
151
- var nowSec = Math.floor(Date.now() / 1000); // allow:raw-time-literal — wall-clock seconds
151
+ var nowSec = Math.floor(Date.now() / 1000);
152
152
  if (Math.abs(nowSec - ts) > tolerance) {
153
153
  throw new StandardWebhooksError("standard-webhooks/timestamp-skew",
154
154
  "verify: timestamp skew " + Math.abs(nowSec - ts) + "s exceeds tolerance " + tolerance + "s");
package/lib/static.js CHANGED
@@ -216,7 +216,7 @@ function _resolveSafe(root, requestedPath) {
216
216
  // deposited disk content: shell-exec extensions (.exe / .bin / .so /
217
217
  // legitimate `<name>.<hash>.js` bundler output) are valid here. The
218
218
  // other balanced checks still reject the traversal + smuggling
219
- // surface the user surfaced.
219
+ // surface.
220
220
  var fname = nodePath.basename(resolved);
221
221
  var rv = guardFilename().validate(fname, {
222
222
  profile: "balanced",
@@ -71,9 +71,9 @@ var StreamThrottleError = defineClass("StreamThrottleError", { alwaysPermanent:
71
71
  // (bytes/sec ↔ wait-ms). This is a unit-conversion constant, not a
72
72
  // memory cap or protocol-byte literal; the framework's C.TIME / C.BYTES
73
73
  // helpers don't apply.
74
- var MS_PER_SECOND = 1000; // allow:raw-time-literal — ms/sec unit conversion
74
+ var MS_PER_SECOND = 1000;
75
75
  var NS_PER_MS = 1e6; // ns/ms unit conversion
76
- var MS_PER_SECOND_HRTIME = 1000; // allow:raw-time-literal — hrtime seconds→ms
76
+ var MS_PER_SECOND_HRTIME = 1000;
77
77
 
78
78
  /**
79
79
  * @primitive b.streamThrottle.create
@@ -542,7 +542,7 @@ function parse(input, type, opts) {
542
542
 
543
543
  function _serDecimal(v, E) {
544
544
  if (!isFinite(v)) throw E("structured-fields/serialize", "cannot serialize a non-finite decimal");
545
- var n = Math.round(v * 1000) / 1000; // allow:raw-time-literal — RFC 8941 §4.1.5 decimal scale 10^3 (3 fractional digits), not a size or duration
545
+ var n = Math.round(v * 1000) / 1000; // allow:raw-time-literal — RFC 8941 4.1.5 decimal-scale 10^3 rounding; coincidental * 1000, not a duration, C.TIME N/A
546
546
  if (Math.abs(Math.trunc(n)).toString().length > 12) throw E("structured-fields/serialize", "decimal integer part exceeds 12 digits"); // §4.1.5 cap
547
547
  var s = n.toString();
548
548
  if (s.indexOf(".") === -1) s += ".0"; // a Decimal must carry a fractional part
package/lib/subject.js CHANGED
@@ -362,7 +362,7 @@ function erase(subjectId, opts) {
362
362
 
363
363
  // ---- Crypto-shred erase (Art. 17 + WAL/replica residual closure) ----
364
364
  //
365
- // F-RTBF-3 — when a table opts into per-row keying via
365
+ // When a table opts into per-row keying via
366
366
  // b.cryptoField.declarePerRowKey, this primitive deletes the
367
367
  // per-row K_row entries from _blamejs_per_row_keys, leaving any
368
368
  // residual ciphertext in WAL / replica / backup storage
@@ -477,7 +477,7 @@ function eraseHard(subjectId, opts) {
477
477
  totalDeleted += deleted;
478
478
  perTable[spec.name] = deleted;
479
479
  // REINDEX the table so B-tree pages holding the deleted row's
480
- // index entries are rebuilt — closes the F-RTBF-2 residual class.
480
+ // index entries are rebuilt — closes the erase-vacuum residual class.
481
481
  try { db().runSql('REINDEX "' + spec.name + '"'); } // table name comes from FRAMEWORK_SCHEMA
482
482
  catch (_e) { /* cluster mode / unsupported dialect */ }
483
483
  }
@@ -102,11 +102,12 @@ function resolvePaths(dataDir) {
102
102
  plaintext: nodePath.join(dataDir, "vault.key"),
103
103
  sealed: nodePath.join(dataDir, "vault.key.sealed"),
104
104
  derivedHashSalt: nodePath.join(dataDir, "vault.derived-hash-salt"),
105
+ derivedHashMacKey: nodePath.join(dataDir, "vault.derived-hash-mac.sealed"),
105
106
  };
106
107
  }
107
108
 
108
109
  // derivedHashSalt — per-deployment salt for crypto-field
109
- // derivedHashes (D-H1). Pre-v0.8.42 the deterministic
110
+ // derivedHashes. Pre-v0.8.42 the deterministic
110
111
  // sha3(namespace + plaintext) shape allowed cross-deployment
111
112
  // rainbow + cross-table correlation; binding a 32-byte
112
113
  // per-deployment salt closes that class without breaking
@@ -175,6 +176,66 @@ function getDerivedHashSalt() {
175
176
  return _cachedDerivedHashSalt;
176
177
  }
177
178
 
179
+ // derivedHashMacKey — per-deployment SECRET key for crypto-field's
180
+ // keyed (hmac-shake256) derived-hash mode. Unlike the salt, this is
181
+ // SEALED at rest (vault.derived-hash-mac.sealed), so an attacker with
182
+ // disk access alone cannot recompute the keyed digest and correlate
183
+ // low-entropy plaintexts. Like the salt, it is keypair-bound and
184
+ // survives a passphrase-only rotation; an ENVELOPE rotation re-seals it
185
+ // because it is registered in rotate's additionalSealed sweep.
186
+ function _readOrCreateDerivedHashMacKey() {
187
+ if (!paths) {
188
+ throw new VaultError("vault/not-initialized",
189
+ "vault.getDerivedHashMacKey() requires init()");
190
+ }
191
+ if (nodeFs.existsSync(paths.derivedHashMacKey)) {
192
+ var sealed = atomicFile.readSync(paths.derivedHashMacKey, { encoding: "utf8" }).trim();
193
+ var b64 = unseal(sealed);
194
+ var key = Buffer.from(b64, "base64");
195
+ if (key.length !== 32) { // 32-byte (256-bit) MAC key
196
+ throw new VaultError("vault/derived-hash-mac-key-corrupted",
197
+ "vault.derived-hash-mac key must unseal to exactly 32 bytes; got " + key.length);
198
+ }
199
+ return key;
200
+ }
201
+ var nodeCrypto = require("node:crypto");
202
+ var raw = nodeCrypto.randomBytes(32); // 32-byte MAC key
203
+ atomicFile.writeSync(paths.derivedHashMacKey, seal(raw.toString("base64")), { fileMode: 0o600 });
204
+ log("generated per-deployment derivedHash MAC key at " + paths.derivedHashMacKey);
205
+ return raw;
206
+ }
207
+
208
+ var _cachedDerivedHashMacKey = null;
209
+ /**
210
+ * @primitive b.vault.getDerivedHashMacKey
211
+ * @signature b.vault.getDerivedHashMacKey()
212
+ * @since 0.14.7
213
+ * @related b.vault.getDerivedHashSalt, b.cryptoField.registerTable
214
+ *
215
+ * Returns the 32-byte per-deployment SECRET key that backs crypto-
216
+ * field's keyed (`hmac-shake256`) derived-hash mode. Generated once on
217
+ * first use, SEALED at rest (`vault.derived-hash-mac.sealed`, mode
218
+ * `0o600`) so disk access alone does not expose it, and re-sealed by an
219
+ * envelope vault rotation. Distinct from `getDerivedHashSalt`, which is
220
+ * a non-secret salt stored in plaintext.
221
+ *
222
+ * Throws `VaultError("vault/not-initialized")` before `init()`, or
223
+ * `vault/derived-hash-mac-key-corrupted` if the sealed file does not
224
+ * unseal to exactly 32 bytes.
225
+ *
226
+ * @example
227
+ * await b.vault.init({ dataDir: "/var/lib/blamejs", mode: "plaintext" });
228
+ * var k = b.vault.getDerivedHashMacKey();
229
+ * k.length; // → 32
230
+ * Buffer.isBuffer(k); // → true
231
+ */
232
+ function getDerivedHashMacKey() {
233
+ if (_cachedDerivedHashMacKey === null) {
234
+ _cachedDerivedHashMacKey = _readOrCreateDerivedHashMacKey();
235
+ }
236
+ return _cachedDerivedHashMacKey;
237
+ }
238
+
178
239
  // ---- Init dispatch ----
179
240
 
180
241
  /**
@@ -620,6 +681,7 @@ module.exports = {
620
681
  seal: seal,
621
682
  unseal: unseal,
622
683
  getDerivedHashSalt: getDerivedHashSalt,
684
+ getDerivedHashMacKey: getDerivedHashMacKey,
623
685
  _zeroizeAndReplace: _zeroizeAndReplace,
624
686
  aad: vaultAad,
625
687
  getKeysJson: getKeysJson,
@@ -633,6 +695,7 @@ module.exports = {
633
695
  _resetForTest: function () {
634
696
  if (currentPassphrase) safeBuffer.secureZero(currentPassphrase);
635
697
  keys = null; initialized = false; currentPassphrase = null; paths = null; currentMode = null;
698
+ _cachedDerivedHashSalt = null; _cachedDerivedHashMacKey = null;
636
699
  },
637
700
  _getKeysForTest: function () { return keys; },
638
701
  _getPathsForTest: function () { return paths; },
@@ -651,6 +651,25 @@ async function rotate(opts) {
651
651
  _reSealValue(current, oldKeys, newKeys), { mode: 0o600 });
652
652
  }
653
653
 
654
+ // 3b. Framework-managed crypto-field derived-hash files — always
655
+ // rotated regardless of operator opts.paths, so the staging copy is
656
+ // complete. The plaintext salt is copied verbatim; the SEALED MAC key
657
+ // (keyed hmac-shake256 mode) is re-sealed under the new keypair so an
658
+ // envelope rotation doesn't orphan it (a passphrase-only rotation
659
+ // re-seals to the same value since the keypair is unchanged).
660
+ var saltSrc = nodePath.join(dataDir, "vault.derived-hash-salt");
661
+ if (nodeFs.existsSync(saltSrc)) {
662
+ nodeFs.copyFileSync(saltSrc, nodePath.join(stagingDir, "vault.derived-hash-salt"));
663
+ }
664
+ var macSrc = nodePath.join(dataDir, "vault.derived-hash-mac.sealed");
665
+ if (nodeFs.existsSync(macSrc)) {
666
+ var macCurrent = nodeFs.readFileSync(macSrc, "utf8").trim();
667
+ if (macCurrent.indexOf(C.VAULT_PREFIX) === 0) {
668
+ nodeFs.writeFileSync(nodePath.join(stagingDir, "vault.derived-hash-mac.sealed"),
669
+ _reSealValue(macCurrent, oldKeys, newKeys), { mode: 0o600 });
670
+ }
671
+ }
672
+
654
673
  // 4. decrypt + rotate + re-encrypt db.enc
655
674
  _emit(progress, { phase: "rotate_db" });
656
675
  var encDbPath = nodePath.join(dataDir, paths.encryptedDb);
@@ -81,7 +81,7 @@ var SealPemFileError = defineClass("SealPemFileError", { alwaysPermanent: true }
81
81
  // doesn't sneak past the watcher. Operators with extremely-quiet
82
82
  // renewal cycles can override via opts.pollInterval; the cost of
83
83
  // 500ms polling on an idle PEM file is ~2 stat() syscalls/sec.
84
- var DEFAULT_POLL_MS = 500; // allow:raw-time-literal — 500ms watchFile cadence (sub-second)
84
+ var DEFAULT_POLL_MS = 500;
85
85
 
86
86
  // PEM files are tiny — 4 KiB for an ECDSA key, ~8 KiB for a 4096-bit
87
87
  // RSA key, ~64 KiB for a long cert chain. Cap at 1 MiB so an operator
@@ -306,7 +306,7 @@ function _loadAndVerify(name) {
306
306
  // Memoized — "sha256:" + sha256(pemToRaw(PUBKEY_PEM)). Matches the
307
307
  // canonical fingerprint shape `scripts/vendor-data-gen.js` writes into
308
308
  // each .data.js's `metadata.publicKeyFingerprint`. Computed lazily on
309
- // first verify (also lazily by verifyAll at boot). CRYPTO-11 — every
309
+ // first verify (also lazily by verifyAll at boot). Every
310
310
  // per-entry verify cross-checks this against the entry's declared
311
311
  // `meta.publicKeyFingerprint` so a pubkey-swap attack fails before
312
312
  // signature verify even runs.
@@ -134,7 +134,7 @@ function buildVapidAuthHeader(opts) {
134
134
  "buildVapidAuthHeader: subscription.endpoint is not a parseable URL");
135
135
  }
136
136
  var aud = endpointUrl.origin;
137
- var now = Math.floor(Date.now() / 1000); // allow:raw-time-literal — wall-clock seconds for JWT exp
137
+ var now = Math.floor(Date.now() / 1000);
138
138
  // Inline JWT sign with ES256 — VAPID strictly mandates ECDSA-P256
139
139
  // (RFC 8292 §3.1). The framework jwt.sign is PQC-first and refuses
140
140
  // ES256 by design; VAPID is a wire-protocol constraint outside
package/lib/webhook.js CHANGED
@@ -955,7 +955,7 @@ function sign(input) {
955
955
  }
956
956
  ts = Math.floor(input.timestamp);
957
957
  } else {
958
- ts = Math.floor(Date.now() / 1000); // allow:raw-time-literal — unix-seconds conversion, Stripe spec uses seconds-not-ms
958
+ ts = Math.floor(Date.now() / 1000);
959
959
  }
960
960
  var hex = _hmacSha256Hex(secretBytes, ts + "." + bodyStr);
961
961
  return "t=" + ts + ",v1=" + hex;
package/lib/websocket.js CHANGED
@@ -191,7 +191,7 @@ var CLOSE_GRACE_MS = C.TIME.seconds(2);
191
191
  function _isValidCloseCode(code) {
192
192
  if (code === 1004 || code === 1005 || code === 1006 || code === 1015) return false; // RFC 6455 §7.4.2 reserved codes
193
193
  if (code >= 1000 && code <= 1011) return true; // allow:raw-time-literal — code is a numeric, not seconds
194
- if (code >= 3000 && code <= 4999) return true; // allow:raw-time-literal — code is a numeric, not seconds
194
+ if (code >= 3000 && code <= 4999) return true; // allow:raw-time-literal — WebSocket close-code range bound (RFC 6455 7.4.2); coincidental multiple-of-60, C.TIME N/A
195
195
  return false;
196
196
  }
197
197
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.14.6",
3
+ "version": "0.14.8",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:ba3e9bdd-f642-4d42-a03c-794d89554a0a",
5
+ "serialNumber": "urn:uuid:013c541c-8703-45e9-9154-89bc05b3998c",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-30T16:30:23.236Z",
8
+ "timestamp": "2026-05-30T23:34:15.711Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/core@0.14.6",
22
+ "bom-ref": "@blamejs/core@0.14.8",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.14.6",
25
+ "version": "0.14.8",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "The Node framework that owns its stack.",
29
- "purl": "pkg:npm/%40blamejs/core@0.14.6",
29
+ "purl": "pkg:npm/%40blamejs/core@0.14.8",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/core@0.14.6",
57
+ "ref": "@blamejs/core@0.14.8",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]