@blamejs/core 0.15.12 → 0.15.14

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 (365) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +45 -29
  4. package/lib/acme.js +6 -5
  5. package/lib/agent-event-bus.js +18 -4
  6. package/lib/agent-idempotency.js +7 -6
  7. package/lib/agent-orchestrator.js +2 -5
  8. package/lib/agent-saga.js +3 -5
  9. package/lib/agent-snapshot.js +32 -2
  10. package/lib/agent-tenant.js +2 -5
  11. package/lib/ai-adverse-decision.js +2 -15
  12. package/lib/ai-aedt-bias-audit.js +2 -1
  13. package/lib/ai-capability.js +1 -6
  14. package/lib/ai-content-detect.js +1 -3
  15. package/lib/ai-dp.js +1 -5
  16. package/lib/ai-frontier-protocol.js +1 -1
  17. package/lib/ai-input.js +2 -2
  18. package/lib/ai-model-manifest.js +1 -1
  19. package/lib/ai-output.js +16 -7
  20. package/lib/ai-pref.js +3 -8
  21. package/lib/ai-quota.js +3 -14
  22. package/lib/api-key.js +37 -28
  23. package/lib/api-snapshot.js +4 -1
  24. package/lib/app-shutdown.js +7 -1
  25. package/lib/archive-adapters.js +2 -4
  26. package/lib/archive-entry-policy.js +32 -0
  27. package/lib/archive-gz.js +9 -0
  28. package/lib/archive-read.js +14 -24
  29. package/lib/archive-tar-read.js +56 -24
  30. package/lib/archive.js +6 -12
  31. package/lib/arg-parser.js +7 -6
  32. package/lib/asn1-der.js +70 -22
  33. package/lib/asyncapi-traits.js +2 -6
  34. package/lib/atomic-file.js +312 -33
  35. package/lib/audit-chain.js +183 -54
  36. package/lib/audit-daily-review.js +36 -16
  37. package/lib/audit-emit.js +82 -0
  38. package/lib/audit-sign.js +258 -0
  39. package/lib/audit-tools.js +108 -23
  40. package/lib/audit.js +91 -2
  41. package/lib/auth/access-lock.js +7 -29
  42. package/lib/auth/bot-challenge.js +1 -1
  43. package/lib/auth/ciba.js +43 -4
  44. package/lib/auth/dpop.js +29 -36
  45. package/lib/auth/fido-mds3.js +14 -16
  46. package/lib/auth/jwt-external.js +26 -8
  47. package/lib/auth/jwt.js +15 -17
  48. package/lib/auth/lockout.js +24 -27
  49. package/lib/auth/oauth.js +67 -45
  50. package/lib/auth/oid4vci.js +55 -32
  51. package/lib/auth/oid4vp.js +3 -2
  52. package/lib/auth/openid-federation.js +6 -6
  53. package/lib/auth/passkey.js +4 -4
  54. package/lib/auth/password.js +8 -2
  55. package/lib/auth/saml.js +70 -40
  56. package/lib/auth/sd-jwt-vc-holder.js +2 -14
  57. package/lib/auth/sd-jwt-vc-issuer.js +2 -14
  58. package/lib/auth/sd-jwt-vc.js +25 -5
  59. package/lib/auth/status-list.js +21 -9
  60. package/lib/auth/step-up.js +15 -13
  61. package/lib/auth-bot-challenge.js +27 -39
  62. package/lib/backup/bundle.js +18 -20
  63. package/lib/backup/crypto.js +23 -8
  64. package/lib/backup/index.js +55 -67
  65. package/lib/backup/manifest.js +7 -1
  66. package/lib/bounded-map.js +112 -1
  67. package/lib/breach-deadline.js +1 -11
  68. package/lib/break-glass.js +41 -22
  69. package/lib/cache.js +7 -18
  70. package/lib/cbor.js +36 -12
  71. package/lib/cdn-cache-control.js +15 -12
  72. package/lib/cert.js +8 -13
  73. package/lib/chain-writer.js +162 -47
  74. package/lib/cli.js +10 -2
  75. package/lib/client-hints.js +7 -9
  76. package/lib/cloud-events.js +43 -33
  77. package/lib/cluster-storage.js +9 -41
  78. package/lib/cms-codec.js +2 -1
  79. package/lib/codepoint-class.js +84 -1
  80. package/lib/compliance-ai-act-logging.js +1 -6
  81. package/lib/compliance-ai-act-transparency.js +2 -4
  82. package/lib/compliance-eaa.js +2 -12
  83. package/lib/compliance-sanctions-fetcher.js +21 -28
  84. package/lib/compliance-sanctions.js +11 -21
  85. package/lib/compliance.js +3 -10
  86. package/lib/config-drift.js +24 -20
  87. package/lib/content-credentials.js +11 -8
  88. package/lib/content-digest.js +21 -12
  89. package/lib/cookies.js +15 -13
  90. package/lib/cose.js +39 -11
  91. package/lib/cra-report.js +1 -11
  92. package/lib/crdt.js +2 -1
  93. package/lib/crypto-field.js +34 -33
  94. package/lib/crypto.js +138 -7
  95. package/lib/csp.js +239 -3
  96. package/lib/daemon.js +46 -42
  97. package/lib/data-act.js +46 -13
  98. package/lib/db-file-lifecycle.js +14 -6
  99. package/lib/db-query.js +75 -49
  100. package/lib/db.js +66 -27
  101. package/lib/dbsc.js +5 -6
  102. package/lib/ddl-change-control.js +18 -14
  103. package/lib/deprecate.js +4 -5
  104. package/lib/did.js +6 -2
  105. package/lib/dora.js +43 -13
  106. package/lib/dr-runbook.js +1 -1
  107. package/lib/dsa.js +2 -1
  108. package/lib/dsr.js +30 -20
  109. package/lib/early-hints.js +19 -0
  110. package/lib/eat.js +5 -1
  111. package/lib/external-db-migrate.js +21 -22
  112. package/lib/external-db.js +74 -30
  113. package/lib/fda-21cfr11.js +42 -13
  114. package/lib/fdx.js +22 -18
  115. package/lib/file-upload.js +80 -66
  116. package/lib/flag-evaluation-context.js +5 -8
  117. package/lib/flag-providers.js +6 -2
  118. package/lib/forms.js +1 -1
  119. package/lib/framework-error.js +12 -0
  120. package/lib/framework-schema.js +19 -0
  121. package/lib/fsm.js +7 -12
  122. package/lib/gate-contract.js +911 -39
  123. package/lib/gdpr-ropa.js +22 -22
  124. package/lib/graphql-federation.js +18 -5
  125. package/lib/guard-agent-registry.js +2 -1
  126. package/lib/guard-all.js +3 -2
  127. package/lib/guard-archive.js +9 -30
  128. package/lib/guard-auth.js +23 -80
  129. package/lib/guard-cidr.js +20 -96
  130. package/lib/guard-csv.js +135 -196
  131. package/lib/guard-domain.js +23 -106
  132. package/lib/guard-dsn.js +17 -14
  133. package/lib/guard-email.js +46 -53
  134. package/lib/guard-envelope.js +2 -2
  135. package/lib/guard-event-bus-topic.js +2 -1
  136. package/lib/guard-filename.js +12 -60
  137. package/lib/guard-graphql.js +28 -75
  138. package/lib/guard-html-wcag.js +15 -2
  139. package/lib/guard-html.js +74 -128
  140. package/lib/guard-idempotency-key.js +2 -1
  141. package/lib/guard-image.js +280 -77
  142. package/lib/guard-imap-command.js +9 -10
  143. package/lib/guard-jmap.js +1 -1
  144. package/lib/guard-json.js +101 -109
  145. package/lib/guard-jsonpath.js +20 -88
  146. package/lib/guard-jwt.js +32 -114
  147. package/lib/guard-list-id.js +2 -7
  148. package/lib/guard-list-unsubscribe.js +2 -7
  149. package/lib/guard-mail-compose.js +5 -6
  150. package/lib/guard-mail-move.js +3 -2
  151. package/lib/guard-mail-query.js +5 -3
  152. package/lib/guard-mail-sieve.js +6 -7
  153. package/lib/guard-managesieve-command.js +6 -5
  154. package/lib/guard-markdown.js +76 -110
  155. package/lib/guard-message-id.js +5 -6
  156. package/lib/guard-mime.js +20 -99
  157. package/lib/guard-oauth.js +19 -73
  158. package/lib/guard-pdf.js +65 -72
  159. package/lib/guard-pop3-command.js +13 -14
  160. package/lib/guard-posture-chain.js +2 -1
  161. package/lib/guard-regex.js +24 -99
  162. package/lib/guard-saga-config.js +2 -1
  163. package/lib/guard-shell.js +22 -87
  164. package/lib/guard-smtp-command.js +9 -12
  165. package/lib/guard-sql.js +15 -13
  166. package/lib/guard-stream-args.js +2 -1
  167. package/lib/guard-svg.js +103 -149
  168. package/lib/guard-template.js +23 -80
  169. package/lib/guard-tenant-id.js +2 -1
  170. package/lib/guard-text.js +592 -0
  171. package/lib/guard-time.js +27 -95
  172. package/lib/guard-uuid.js +21 -93
  173. package/lib/guard-xml.js +76 -106
  174. package/lib/guard-yaml.js +24 -60
  175. package/lib/html-balance.js +7 -3
  176. package/lib/http-client-cache.js +5 -12
  177. package/lib/http-client-cookie-jar.js +35 -16
  178. package/lib/http-client.js +233 -74
  179. package/lib/http-message-signature.js +3 -2
  180. package/lib/i18n-messageformat.js +5 -7
  181. package/lib/i18n.js +83 -26
  182. package/lib/iab-tcf.js +3 -2
  183. package/lib/importmap-integrity.js +41 -1
  184. package/lib/inbox.js +8 -8
  185. package/lib/incident-report.js +12 -27
  186. package/lib/ip-utils.js +49 -6
  187. package/lib/jobs.js +3 -2
  188. package/lib/json-patch.js +1 -1
  189. package/lib/json-path.js +24 -3
  190. package/lib/jtd.js +2 -2
  191. package/lib/keychain.js +6 -18
  192. package/lib/legal-hold.js +30 -23
  193. package/lib/log-stream-cloudwatch.js +44 -112
  194. package/lib/log-stream-otlp-grpc.js +17 -14
  195. package/lib/log-stream-otlp.js +16 -80
  196. package/lib/log-stream-webhook.js +20 -92
  197. package/lib/log.js +2 -2
  198. package/lib/mail-agent.js +2 -2
  199. package/lib/mail-arc-sign.js +8 -3
  200. package/lib/mail-arf.js +4 -3
  201. package/lib/mail-auth.js +43 -69
  202. package/lib/mail-bimi.js +35 -55
  203. package/lib/mail-bounce.js +3 -3
  204. package/lib/mail-crypto-pgp.js +3 -2
  205. package/lib/mail-crypto-smime.js +71 -6
  206. package/lib/mail-dav.js +8 -35
  207. package/lib/mail-deploy.js +15 -14
  208. package/lib/mail-dkim.js +19 -38
  209. package/lib/mail-greylist.js +17 -30
  210. package/lib/mail-helo.js +4 -7
  211. package/lib/mail-journal.js +13 -9
  212. package/lib/mail-mdn.js +12 -7
  213. package/lib/mail-rbl.js +7 -12
  214. package/lib/mail-scan.js +5 -7
  215. package/lib/mail-send-deliver.js +33 -20
  216. package/lib/mail-server-imap.js +32 -87
  217. package/lib/mail-server-jmap.js +39 -52
  218. package/lib/mail-server-managesieve.js +29 -83
  219. package/lib/mail-server-mx.js +33 -74
  220. package/lib/mail-server-net.js +177 -0
  221. package/lib/mail-server-pop3.js +30 -83
  222. package/lib/mail-server-rate-limit.js +30 -6
  223. package/lib/mail-server-registry.js +1 -1
  224. package/lib/mail-server-submission.js +34 -73
  225. package/lib/mail-server-tls.js +98 -2
  226. package/lib/mail-spam-score.js +9 -12
  227. package/lib/mail-store-fts.js +1 -1
  228. package/lib/mail-store.js +3 -2
  229. package/lib/mail.js +28 -13
  230. package/lib/markup-escape.js +31 -0
  231. package/lib/markup-tokenizer.js +78 -0
  232. package/lib/mcp-tool-registry.js +26 -23
  233. package/lib/mcp.js +7 -5
  234. package/lib/mdoc.js +36 -14
  235. package/lib/metrics.js +46 -33
  236. package/lib/middleware/age-gate.js +3 -23
  237. package/lib/middleware/api-encrypt.js +13 -24
  238. package/lib/middleware/assetlinks.js +2 -7
  239. package/lib/middleware/bearer-auth.js +2 -1
  240. package/lib/middleware/body-parser.js +41 -52
  241. package/lib/middleware/bot-disclose.js +7 -10
  242. package/lib/middleware/bot-guard.js +28 -28
  243. package/lib/middleware/clear-site-data.js +5 -1
  244. package/lib/middleware/compression.js +9 -0
  245. package/lib/middleware/cors.js +32 -23
  246. package/lib/middleware/csrf-protect.js +73 -23
  247. package/lib/middleware/daily-byte-quota.js +12 -30
  248. package/lib/middleware/deny-response.js +19 -1
  249. package/lib/middleware/fetch-metadata.js +35 -4
  250. package/lib/middleware/idempotency-key.js +4 -20
  251. package/lib/middleware/network-allowlist.js +61 -30
  252. package/lib/middleware/rate-limit.js +45 -44
  253. package/lib/middleware/scim-server.js +5 -3
  254. package/lib/middleware/security-headers.js +33 -7
  255. package/lib/middleware/security-txt.js +2 -6
  256. package/lib/middleware/speculation-rules.js +6 -3
  257. package/lib/middleware/tus-upload.js +14 -29
  258. package/lib/middleware/web-app-manifest.js +2 -7
  259. package/lib/migrations.js +4 -13
  260. package/lib/mime-parse.js +34 -17
  261. package/lib/module-loader.js +44 -0
  262. package/lib/money.js +105 -0
  263. package/lib/mtls-ca.js +125 -41
  264. package/lib/network-byte-quota.js +4 -18
  265. package/lib/network-dane.js +8 -6
  266. package/lib/network-dns-resolver.js +98 -7
  267. package/lib/network-dns.js +9 -2
  268. package/lib/network-dnssec.js +18 -17
  269. package/lib/network-heartbeat.js +3 -2
  270. package/lib/network-smtp-policy.js +52 -46
  271. package/lib/network-tls.js +67 -47
  272. package/lib/network-tsig.js +2 -2
  273. package/lib/nis2-report.js +2 -12
  274. package/lib/nist-crosswalk.js +1 -1
  275. package/lib/nonce-store.js +81 -3
  276. package/lib/ntp-check.js +28 -0
  277. package/lib/numeric-bounds.js +31 -8
  278. package/lib/object-store/azure-blob-bucket-ops.js +2 -6
  279. package/lib/object-store/azure-blob.js +6 -47
  280. package/lib/object-store/gcs-bucket-ops.js +4 -2
  281. package/lib/object-store/gcs.js +14 -75
  282. package/lib/object-store/http-put.js +2 -7
  283. package/lib/object-store/http-request.js +157 -0
  284. package/lib/object-store/local.js +37 -17
  285. package/lib/object-store/sigv4-bucket-ops.js +2 -1
  286. package/lib/object-store/sigv4.js +10 -79
  287. package/lib/observability-otlp-exporter.js +29 -29
  288. package/lib/observability.js +95 -0
  289. package/lib/openapi-paths-builder.js +7 -3
  290. package/lib/otel-export.js +7 -10
  291. package/lib/outbox.js +54 -41
  292. package/lib/pagination.js +11 -15
  293. package/lib/parsers/safe-env.js +5 -4
  294. package/lib/parsers/safe-ini.js +10 -4
  295. package/lib/parsers/safe-toml.js +11 -9
  296. package/lib/parsers/safe-xml.js +3 -3
  297. package/lib/parsers/safe-yaml.js +28 -9
  298. package/lib/pick.js +63 -5
  299. package/lib/pipl-cn.js +69 -59
  300. package/lib/privacy-pass.js +8 -6
  301. package/lib/problem-details.js +2 -5
  302. package/lib/pubsub.js +4 -8
  303. package/lib/queue-local.js +10 -3
  304. package/lib/redact.js +9 -4
  305. package/lib/render.js +4 -2
  306. package/lib/request-helpers.js +334 -29
  307. package/lib/resource-access-lock.js +3 -3
  308. package/lib/restore-bundle.js +46 -18
  309. package/lib/restore-rollback.js +10 -4
  310. package/lib/restore.js +24 -22
  311. package/lib/retention.js +23 -9
  312. package/lib/router.js +17 -4
  313. package/lib/safe-async.js +457 -0
  314. package/lib/safe-buffer.js +137 -6
  315. package/lib/safe-decompress.js +2 -1
  316. package/lib/safe-dns.js +2 -1
  317. package/lib/safe-ical.js +14 -28
  318. package/lib/safe-icap.js +1 -1
  319. package/lib/safe-json.js +51 -8
  320. package/lib/safe-jsonpath.js +6 -5
  321. package/lib/safe-mime.js +25 -29
  322. package/lib/safe-redirect.js +2 -5
  323. package/lib/safe-schema.js +7 -6
  324. package/lib/safe-sieve.js +1 -1
  325. package/lib/safe-sql.js +126 -0
  326. package/lib/safe-vcard.js +13 -27
  327. package/lib/sandbox-worker.js +15 -2
  328. package/lib/sandbox.js +4 -3
  329. package/lib/scheduler.js +22 -13
  330. package/lib/sec-cyber.js +82 -37
  331. package/lib/seeders.js +4 -11
  332. package/lib/self-update-standalone-verifier.js +16 -0
  333. package/lib/self-update.js +92 -69
  334. package/lib/session-device-binding.js +112 -66
  335. package/lib/session.js +219 -7
  336. package/lib/sql.js +228 -81
  337. package/lib/sse.js +6 -10
  338. package/lib/static.js +201 -111
  339. package/lib/storage.js +4 -2
  340. package/lib/structured-fields.js +275 -16
  341. package/lib/template.js +7 -5
  342. package/lib/tenant-quota.js +53 -38
  343. package/lib/tsa.js +16 -17
  344. package/lib/validate-opts.js +154 -8
  345. package/lib/vault/index.js +5 -0
  346. package/lib/vault/passphrase-ops.js +22 -26
  347. package/lib/vault/passphrase-source.js +8 -3
  348. package/lib/vault/rotate.js +13 -18
  349. package/lib/vault/seal-pem-file.js +34 -49
  350. package/lib/vault-aad.js +3 -2
  351. package/lib/vc.js +3 -8
  352. package/lib/vendor/MANIFEST.json +10 -10
  353. package/lib/vendor/public-suffix-list.dat +23 -10
  354. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  355. package/lib/vex.js +35 -13
  356. package/lib/web-push-vapid.js +23 -20
  357. package/lib/webhook-dispatcher.js +706 -0
  358. package/lib/webhook.js +59 -19
  359. package/lib/websocket-channels.js +9 -7
  360. package/lib/websocket.js +8 -4
  361. package/lib/worm.js +3 -4
  362. package/lib/ws-client.js +65 -56
  363. package/lib/x509-chain.js +44 -0
  364. package/package.json +1 -1
  365. package/sbom.cdx.json +6 -6
package/lib/webhook.js CHANGED
@@ -48,6 +48,7 @@
48
48
  */
49
49
 
50
50
  var nodeCrypto = require("node:crypto");
51
+ var numericBounds = require("./numeric-bounds");
51
52
  var bCrypto = require("./crypto");
52
53
  var httpClient = require("./http-client");
53
54
  var safeBuffer = require("./safe-buffer");
@@ -57,8 +58,14 @@ var C = require("./constants");
57
58
  var lazyRequire = require("./lazy-require");
58
59
  var numericChecks = require("./numeric-checks");
59
60
  var requestHelpers = require("./request-helpers");
61
+ // Lazy — ssrf-guard pulls in the DNS/network stack; only touched on send().
62
+ var ssrfGuard = lazyRequire(function () { return require("./ssrf-guard"); });
60
63
  var validateOpts = require("./validate-opts");
61
64
  var { WebhookError } = require("./framework-error");
65
+ // b.webhook.dispatcher — durable signed-webhook delivery store. Lives in its
66
+ // own module (distinct domain: persistence) and lazyRequires this file back,
67
+ // so this top-level require is cycle-safe.
68
+ var webhookDispatcher = require("./webhook-dispatcher");
62
69
 
63
70
  var observability = lazyRequire(function () { return require("./observability"); });
64
71
 
@@ -424,6 +431,18 @@ function signer(opts) {
424
431
  allowedProtocols: httpOpts.allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
425
432
  errorClass: WebhookError,
426
433
  });
434
+ // SSRF gate at the webhook layer: refuse private / loopback / link-local /
435
+ // cloud-metadata destinations up front, before the retry loop. httpClient
436
+ // also enforces this on every request, but checking here fails fast (no
437
+ // wasted retries) and keeps the SSRF boundary explicit at the send() API
438
+ // rather than relying on the transport's internal gate. allowInternal
439
+ // mirrors the operator's http opts (its affirmative waiver).
440
+ try {
441
+ await ssrfGuard().checkUrl(url, { allowInternal: httpOpts.allowInternal === true });
442
+ } catch (e) {
443
+ if (e && e.isSsrfError) throw _err("SSRF_REFUSED", "webhook.signer.send: " + e.message);
444
+ throw e;
445
+ }
427
446
  _validateBody(body);
428
447
  var signed = sign(body, { kid: input.kid });
429
448
  var mergedHeaders = Object.assign({}, input.headers || {}, signed.headers);
@@ -641,7 +660,7 @@ function verifier(opts) {
641
660
  throw _failure("MISSING_TIMESTAMP", "webhook: t= field missing from signature header", "missing-timestamp", ctxReq);
642
661
  }
643
662
  var ts = Number(parsed.t);
644
- if (!isFinite(ts) || ts < 0 || Math.floor(ts) !== ts) {
663
+ if (!numericBounds.isNonNegativeFiniteInt(ts)) {
645
664
  throw _failure("BAD_TIMESTAMP", "webhook: t= field is not a non-negative integer, got " + JSON.stringify(parsed.t), "bad-timestamp", ctxReq);
646
665
  }
647
666
  if (parsed.id === null || parsed.id.length === 0) {
@@ -832,16 +851,28 @@ function _coerceBodyString(body) {
832
851
  * HMAC-SHA-256 over the literal `<t>.<rawBody>` string using the
833
852
  * operator's `whsec_...` secret bytes verbatim (the prefix IS the key).
834
853
  * Refuses signatures older than the tolerance window (default 5 min,
835
- * minimum 30 s). When `nonceStore` is supplied the verifier records
836
- * the accepted v1 signature so a replay within the tolerance window
837
- * is refused. Constant-time compare via `b.crypto.timingSafeEqual`.
854
+ * minimum 30 s). When `nonceStore` is supplied the verifier atomically
855
+ * records the accepted v1 signature so a replay within the tolerance window
856
+ * is refused; the store speaks the same `checkAndInsert(nonce, expireAt) →
857
+ * bool` contract as `b.webhook.verifier` and `b.nonceStore.create`, so the
858
+ * framework's own replay store plugs in directly and concurrent redeliveries
859
+ * cannot race. Constant-time compare via `b.crypto.timingSafeEqual`.
860
+ *
861
+ * @opts
862
+ * alg: "hmac-sha256-stripe",
863
+ * secret: string | Buffer, // whsec_... bytes verbatim
864
+ * header: string, // Stripe-Signature value
865
+ * body: string | Buffer, // raw request body
866
+ * toleranceMs: number, // default 5 min, min 30 s
867
+ * nonceStore: { checkAndInsert(nonce, expireAt) }, // optional atomic replay defense
838
868
  *
839
869
  * @example
840
- * b.webhook.verify({
841
- * alg: "hmac-sha256-stripe",
842
- * secret: "whsec_abc...",
843
- * header: req.headers["stripe-signature"],
844
- * body: rawBodyBuffer,
870
+ * await b.webhook.verify({
871
+ * alg: "hmac-sha256-stripe",
872
+ * secret: "whsec_abc...",
873
+ * header: req.headers["stripe-signature"],
874
+ * body: rawBodyBuffer,
875
+ * nonceStore: b.nonceStore.create({ backend: "memory" }),
845
876
  * });
846
877
  * // → { ok: true, timestamp: 1700000000, scheme: "v1" }
847
878
  */
@@ -889,24 +920,30 @@ async function verify(input) {
889
920
  "verify: no v1 signature matched HMAC-SHA-256 of <t>.<body>");
890
921
  }
891
922
 
892
- // Optional replay defense — the nonceStore is operator-supplied
893
- // (e.g. `b.kv` or a Redis-shaped { has, set, expire }) so the
894
- // primitive doesn't own retention. `has` / `set` MAY return a
895
- // Promise we always `await` them so async backends (Redis, KV,
896
- // DynamoDB) work without falsely flagging a Promise as truthy.
923
+ // Optional replay defense — the nonceStore speaks the SAME atomic
924
+ // `checkAndInsert(nonce, expireAt) bool` contract as b.webhook.verifier
925
+ // and b.nonceStore.create, so the framework's own replay store drops in
926
+ // with no adapter. The check + insert is one atomic step: a non-atomic
927
+ // check-then-set would race two concurrent redeliveries of the same event
928
+ // (both observe "unseen", both proceed) — the exact failure replay defense
929
+ // exists to prevent. checkAndInsert MAY return a Promise (Redis / KV /
930
+ // cluster SQL); we always await it. `expireAt` is the absolute epoch-ms at
931
+ // which the signature itself falls outside the tolerance window, so a stored
932
+ // nonce is retained exactly as long as a replay of it could still be fresh.
897
933
  if (input.nonceStore) {
898
934
  var ns = input.nonceStore;
899
- if (typeof ns.has !== "function" || typeof ns.set !== "function") {
935
+ if (typeof ns.checkAndInsert !== "function") {
900
936
  throw new WebhookError("webhook/bad-nonce-store",
901
- "verify: nonceStore must expose { has(key), set(key, ttlMs) }");
937
+ "verify: nonceStore must expose checkAndInsert(nonce, expireAt) — the " +
938
+ "b.nonceStore contract shared with b.webhook.verifier");
902
939
  }
903
940
  var nonceKey = "stripe:" + parsed.timestamp + ":" + expectedHex;
904
- var seen = await ns.has(nonceKey);
905
- if (seen) {
941
+ var expireAt = C.TIME.seconds(parsed.timestamp) + tolerance;
942
+ var fresh = await ns.checkAndInsert(nonceKey, expireAt);
943
+ if (!fresh) {
906
944
  throw new WebhookError("webhook/replay",
907
945
  "verify: signature already seen within tolerance window (replay)");
908
946
  }
909
- await ns.set(nonceKey, tolerance);
910
947
  }
911
948
 
912
949
  return { ok: true, timestamp: parsed.timestamp, scheme: "v1" };
@@ -974,4 +1011,7 @@ module.exports = {
974
1011
  // v0.11.25 — Stripe-shaped inbound HMAC-SHA-256 verifier + signer.
975
1012
  verify: verify,
976
1013
  sign: sign,
1014
+ // Durable signed-webhook delivery store (sign + persist + deliver + retry +
1015
+ // dead-letter + operator replay). Defined in lib/webhook-dispatcher.js.
1016
+ dispatcher: webhookDispatcher.dispatcher,
977
1017
  };
@@ -60,6 +60,7 @@
60
60
 
61
61
  var lazyRequire = require("./lazy-require");
62
62
  var pubsub = require("./pubsub");
63
+ var boundedMap = require("./bounded-map");
63
64
  var validateOpts = require("./validate-opts");
64
65
  var { defineClass } = require("./framework-error");
65
66
 
@@ -187,17 +188,18 @@ function create(opts) {
187
188
  if (typeof channel !== "string" || channel.length === 0) {
188
189
  throw _err("INVALID_CHANNEL", "subscribe: channel must be a non-empty string");
189
190
  }
190
- if (!connToChannels.has(conn)) {
191
+ boundedMap.requirePresent(connToChannels, conn, function () {
191
192
  throw _err("NOT_ATTACHED", "subscribe: connection must be attach()-ed first");
192
- }
193
- if (!channelToConns.has(channel)) {
194
- channelToConns.set(channel, new Set());
193
+ });
194
+ var subs = boundedMap.getOrInsert(channelToConns, channel, function () {
195
195
  // First local listener for this channel — open a pubsub
196
- // subscription so cross-node fan-out reaches us.
196
+ // subscription so cross-node fan-out reaches us, recording the
197
+ // token under the same key the new Set is stored at.
197
198
  var token = ps.subscribe(channel, _onPubsubMessage);
198
199
  channelToToken.set(channel, token);
199
- }
200
- channelToConns.get(channel).add(conn);
200
+ return new Set();
201
+ });
202
+ subs.add(conn);
201
203
  connToChannels.get(conn).add(channel);
202
204
  }
203
205
 
package/lib/websocket.js CHANGED
@@ -82,6 +82,7 @@ var C = require("./constants");
82
82
  var requestHelpers = require("./request-helpers");
83
83
  var safeAsync = require("./safe-async");
84
84
  var safeBuffer = require("./safe-buffer");
85
+ var pick = require("./pick");
85
86
  var structuredFields = require("./structured-fields");
86
87
  var { FrameworkError } = require("./framework-error");
87
88
  var { boot } = require("./log");
@@ -543,7 +544,7 @@ function _parseExtensionHeader(header) {
543
544
  var kv = parts[j].split("=");
544
545
  var k = kv[0].trim().toLowerCase();
545
546
  if (!k) continue;
546
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
547
+ if (pick.isPoisonedKey(k)) continue;
547
548
  var v = kv.length > 1 ? kv.slice(1).join("=").trim() : true;
548
549
  // Strip surrounding quotes per the token-or-quoted-string grammar.
549
550
  if (typeof v === "string") {
@@ -572,7 +573,7 @@ function _negotiatePermessageDeflate(reqHeader) {
572
573
  "server_max_window_bits": true, "client_max_window_bits": true,
573
574
  };
574
575
  var ok = true;
575
- for (var k in p) { if (Object.prototype.hasOwnProperty.call(p, k) && !KNOWN[k]) { ok = false; break; } }
576
+ for (var k in p) { if (Object.prototype.hasOwnProperty.call(p, k) && !Object.prototype.hasOwnProperty.call(KNOWN, k)) { ok = false; break; } }
576
577
  if (!ok) continue;
577
578
  // Always negotiate WITH no_context_takeover in BOTH directions, so
578
579
  // every message uses a fresh zlib state. Echo any client window-
@@ -1128,7 +1129,7 @@ class WebSocketConnection extends EventEmitter {
1128
1129
  return this._abort(CLOSE_INVALID_PAYLOAD,
1129
1130
  "permessage-deflate inflate failed: " + ((e && e.message) || String(e)));
1130
1131
  }
1131
- if (data.length > this.maxMessageBytes) {
1132
+ if (safeBuffer.byteLengthOf(data) > this.maxMessageBytes) {
1132
1133
  return this._abort(CLOSE_MESSAGE_TOO_BIG,
1133
1134
  "decompressed message exceeds maxMessageBytes");
1134
1135
  }
@@ -1241,8 +1242,11 @@ class WebSocketConnection extends EventEmitter {
1241
1242
  } else if (Buffer.isBuffer(data)) {
1242
1243
  this._sendDataFrame(OPCODE_BINARY, data);
1243
1244
  } else {
1245
+ // WebSocketError's constructor is (code, message, closeCode) — code
1246
+ // first — so the (message, code) errorClass path would swap the two.
1247
+ // errorFactory hands toBuffer a (code, message) constructor instead.
1244
1248
  data = safeBuffer.toBuffer(data, {
1245
- errorClass: WebSocketError,
1249
+ errorFactory: function (code, message) { return new WebSocketError(code, message); },
1246
1250
  typeCode: "ws/invalid-payload",
1247
1251
  typeMessage: "send() requires Buffer, Uint8Array, or string",
1248
1252
  });
package/lib/worm.js CHANGED
@@ -102,7 +102,7 @@ function create(opts) {
102
102
  opts = opts || {};
103
103
  validateOpts(opts, ["store", "mode", "defaultRetentionMs", "clock"], "worm.create");
104
104
  var mode = opts.mode || "compliance";
105
- if (!MODES[mode]) throw new WormError("worm/bad-mode", "worm.create: mode must be 'compliance' or 'governance'");
105
+ if (!Object.prototype.hasOwnProperty.call(MODES, mode)) throw new WormError("worm/bad-mode", "worm.create: mode must be 'compliance' or 'governance'");
106
106
  var store = opts.store || _memStore();
107
107
  ["get", "set", "delete", "has", "keys"].forEach(function (m) {
108
108
  if (typeof store[m] !== "function") throw new WormError("worm/bad-store", "worm.create: store adapter must implement " + m + "()");
@@ -113,10 +113,9 @@ function create(opts) {
113
113
  throw new WormError("worm/bad-opt", "worm.create: defaultRetentionMs must be a non-negative finite number");
114
114
  }
115
115
 
116
+ var _baseAudit = audit().namespaced("worm");
116
117
  function _emit(action, outcome, id, metadata) {
117
- try {
118
- audit().safeEmit({ action: "worm." + action, actor: { type: "system" }, outcome: outcome, metadata: Object.assign({ id: id, mode: mode }, metadata || {}) });
119
- } catch (_e) { /* audit is drop-silent by design */ }
118
+ _baseAudit(action, outcome, Object.assign({ id: id, mode: mode }, metadata || {}), { actor: { type: "system" } });
120
119
  }
121
120
 
122
121
  function _resolveRetainUntil(now, putOpts) {
package/lib/ws-client.js CHANGED
@@ -60,6 +60,7 @@ var audit = lazyRequire(function () { return require("./audit"); });
60
60
  var networkTls = lazyRequire(function () { return require("./network-tls"); });
61
61
  var safeJson = lazyRequire(function () { return require("./safe-json"); });
62
62
  var ssrfGuard = require("./ssrf-guard");
63
+ var structuredFields = require("./structured-fields");
63
64
  var C = require("./constants");
64
65
  var { defineClass } = require("./framework-error");
65
66
 
@@ -247,17 +248,13 @@ function connect(target, opts) {
247
248
  });
248
249
  // SSRF gate — refuse private / loopback / link-local / cloud-metadata /
249
250
  // reserved IP destinations by default. Symmetric to b.httpClient. The
250
- // returned `ips` are pinned through tls.connect / net.connect so the
251
+ // validated `ips` are pinned through tls.connect / net.connect so the
251
252
  // actual TCP connect targets the validated address (closes the DNS-
252
253
  // rebinding TOCTOU window). Cloud-metadata IPs are unconditional
253
- // hard-deny — `allowInternal: true` does not bypass them.
254
- var hostnameForUrl = parsed.protocol === "wss:" ? "https:" : "http:";
255
- var probeUrl = new nodeUrl.URL(hostnameForUrl + "//" + parsed.host + parsed.pathname + parsed.search);
256
- ssrfGuard.checkUrl(probeUrl, {
257
- allowInternal: opts.allowInternal,
258
- errorClass: WsClientError,
259
- }).then(function (result) {
260
- client._ssrfPinnedIps = result && result.ips;
254
+ // hard-deny — `allowInternal: true` does not bypass them. _prepareDial
255
+ // performs the (async) check + pinning before the dial and reruns it on
256
+ // every reconnect, so a urlFor-swapped target is validated too.
257
+ client._prepareDial().then(function () {
261
258
  client._dial();
262
259
  }).catch(function (e) {
263
260
  setImmediate(function () { client._handleSocketError(e); });
@@ -314,51 +311,50 @@ class WsClient extends EventEmitter {
314
311
  get subprotocol() { return this._negotiatedSubprotocol; }
315
312
  get url() { return this._opts.target; }
316
313
 
317
- _dial() {
318
- var self = this;
314
+ // Resolve the dial target and re-validate it BEFORE any socket opens.
315
+ // urlFor may swap the URL and tlsOptsFor may rotate TLS material every
316
+ // dial (including reconnects). The resolved target is re-checked through
317
+ // ssrfGuard — AWAITED, because checkUrl is async: the prior code called it
318
+ // synchronously inside _dial and discarded the Promise, so a urlFor that
319
+ // pointed at a private / cloud-metadata address mid-reconnect was connected
320
+ // anyway (the SSRF rejection surfaced only as an unhandled rejection) and
321
+ // the connect was never pinned. Returns a Promise; sync throws from
322
+ // urlFor / tlsOptsFor become rejections (async fn) handled by the caller.
323
+ async _prepareDial() {
319
324
  var opts = this._opts;
320
- this._readyState = "connecting";
321
-
322
- // Per-dial overrides — urlFor swaps the target URL, tlsOptsFor
323
- // overrides TLS material. Both fire every dial including reconnects
324
- // so callers can rotate state. urlFor's result is re-validated
325
- // through ssrfGuard so a hostile upstream can't direct the client
326
- // at a private address mid-reconnect.
327
325
  var attempt = this._reconnectAttempt || 0;
328
- var dialTarget = opts.target;
329
326
  var dialParsed = opts.parsedUrl;
330
327
  if (typeof opts.urlFor === "function") {
331
- try {
332
- var nextTarget = opts.urlFor(attempt);
333
- if (typeof nextTarget === "string" && nextTarget.length > 0 && nextTarget !== dialTarget) {
334
- dialParsed = _parseUrl(nextTarget);
335
- dialTarget = nextTarget;
336
- var probeProto = dialParsed.protocol === "wss:" ? "https:" : "http:";
337
- var probeUrl = new nodeUrl.URL(probeProto + "//" + dialParsed.host + dialParsed.pathname + dialParsed.search);
338
- var probe = ssrfGuard.checkUrl(probeUrl, {
339
- allowInternal: opts.allowInternal,
340
- errorClass: WsClientError,
341
- });
342
- this._ssrfPinnedIps = probe && probe.ips ? probe.ips : null;
343
- }
344
- } catch (e) {
345
- return self._handleSocketError(e);
328
+ var nextTarget = opts.urlFor(attempt);
329
+ if (typeof nextTarget === "string" && nextTarget.length > 0 && nextTarget !== opts.target) {
330
+ dialParsed = _parseUrl(nextTarget);
346
331
  }
347
332
  }
348
-
349
333
  var dialTlsOpts = opts.tlsOpts;
350
334
  if (typeof opts.tlsOptsFor === "function") {
351
- try {
352
- var override = opts.tlsOptsFor(attempt);
353
- if (override && typeof override === "object") {
354
- dialTlsOpts = Object.assign({}, opts.tlsOpts || {}, override);
355
- }
356
- } catch (e) {
357
- return self._handleSocketError(e);
335
+ var override = opts.tlsOptsFor(attempt);
336
+ if (override && typeof override === "object") {
337
+ dialTlsOpts = Object.assign({}, opts.tlsOpts || {}, override);
358
338
  }
359
339
  }
340
+ var probeProto = dialParsed.protocol === "wss:" ? "https:" : "http:";
341
+ var probeUrl = new nodeUrl.URL(probeProto + "//" + dialParsed.host + dialParsed.pathname + dialParsed.search);
342
+ var probe = await ssrfGuard.checkUrl(probeUrl, {
343
+ allowInternal: opts.allowInternal,
344
+ errorClass: WsClientError,
345
+ });
346
+ this._ssrfPinnedIps = probe && probe.ips ? probe.ips : null;
347
+ this._dialParsed = dialParsed;
348
+ this._dialTlsOpts = dialTlsOpts;
349
+ }
350
+
351
+ _dial() {
352
+ var self = this;
353
+ this._readyState = "connecting";
360
354
 
361
- var parsed = dialParsed;
355
+ // Target + TLS material were resolved and SSRF-validated by _prepareDial.
356
+ var parsed = this._dialParsed || this._opts.parsedUrl;
357
+ var dialTlsOpts = this._dialTlsOpts || this._opts.tlsOpts;
362
358
  var port = parsed.port ? parseInt(parsed.port, 10) :
363
359
  (parsed.protocol === "wss:" ? 443 : 80); // TLS / HTTP default port
364
360
  var host = parsed.hostname;
@@ -421,8 +417,8 @@ class WsClient extends EventEmitter {
421
417
 
422
418
  this._handshakeTimer = setTimeout(function () {
423
419
  self._handleSocketError(new WsClientError("ws-client/handshake-timeout",
424
- "Handshake exceeded " + opts.handshakeTimeoutMs + "ms"));
425
- }, opts.handshakeTimeoutMs);
420
+ "Handshake exceeded " + self._opts.handshakeTimeoutMs + "ms"));
421
+ }, self._opts.handshakeTimeoutMs);
426
422
  if (typeof this._handshakeTimer.unref === "function") this._handshakeTimer.unref();
427
423
  }
428
424
 
@@ -492,7 +488,7 @@ class WsClient extends EventEmitter {
492
488
  this._handshakeBuf = Buffer.concat([this._handshakeBuf, chunk]);
493
489
  var headerEnd = this._handshakeBuf.indexOf("\r\n\r\n");
494
490
  if (headerEnd === -1) {
495
- if (this._handshakeBuf.length > C.BYTES.kib(64)) {
491
+ if (safeBuffer.byteLengthOf(this._handshakeBuf) > C.BYTES.kib(64)) {
496
492
  this._handleSocketError(new WsClientError("ws-client/handshake-too-large",
497
493
  "handshake response exceeded 64 KiB before CRLFCRLF"));
498
494
  }
@@ -526,13 +522,10 @@ class WsClient extends EventEmitter {
526
522
  }
527
523
 
528
524
  var headers = Object.create(null);
529
- for (var i = 1; i < lines.length; i += 1) {
530
- var idx = lines[i].indexOf(":");
531
- if (idx === -1) continue;
532
- var hkey = lines[i].slice(0, idx).trim().toLowerCase();
533
- var hval = lines[i].slice(idx + 1).trim();
534
- headers[hkey] = hval;
535
- }
525
+ var hkvps = structuredFields.parseKeyValuePieces(lines, 1, ":");
526
+ structuredFields.forEachKeyValue(hkvps, function (key, value) {
527
+ headers[key] = value;
528
+ });
536
529
 
537
530
  if ((headers["upgrade"] || "").toLowerCase() !== "websocket" ||
538
531
  (headers["connection"] || "").toLowerCase().indexOf("upgrade") === -1) {
@@ -646,6 +639,17 @@ class WsClient extends EventEmitter {
646
639
  return;
647
640
  }
648
641
  }
642
+ // Fail the connection on any opcode outside the six RFC 6455 §5.2-defined
643
+ // values (CONT/TEXT/BINARY/CLOSE/PING/PONG). Without this, a reserved
644
+ // opcode (0x3-0x7, 0xB-0xF) from a malicious server fell through every
645
+ // branch to the FIN block and emitted a (stale/empty) message — a
646
+ // fragmented-message desync / frame-injection lever.
647
+ if (frame.opcode !== OPCODE_CONT && frame.opcode !== OPCODE_TEXT &&
648
+ frame.opcode !== OPCODE_BINARY && !isControl) {
649
+ this._handleSocketError(new WsClientError("ws-client/reserved-opcode",
650
+ "reserved/unknown WebSocket opcode 0x" + frame.opcode.toString(16) + " (RFC 6455 §5.2)"));
651
+ return;
652
+ }
649
653
  // Continuation frames MUST NOT carry rsv1 (RFC 7692 §6.1) — only
650
654
  // the first frame of a compressed message sets rsv1.
651
655
  if (frame.opcode === OPCODE_CONT && frame.rsv1) {
@@ -698,7 +702,7 @@ class WsClient extends EventEmitter {
698
702
  }
699
703
  if (frame.fin) {
700
704
  var fullPayload = Buffer.concat(this._fragmentChunks); // allow:handrolled-buffer-collect — bounded by maxMessageBytes below
701
- if (fullPayload.length > this._opts.maxMessageBytes) {
705
+ if (safeBuffer.byteLengthOf(fullPayload) > this._opts.maxMessageBytes) {
702
706
  this._handleSocketError(new WsClientError("ws-client/message-too-big",
703
707
  "incoming message exceeds maxMessageBytes (" + this._opts.maxMessageBytes + ")"));
704
708
  return;
@@ -797,7 +801,7 @@ class WsClient extends EventEmitter {
797
801
  } else {
798
802
  payload = Buffer.from(JSON.stringify(data), "utf8");
799
803
  }
800
- if (payload.length > this._opts.maxMessageBytes) {
804
+ if (safeBuffer.byteLengthOf(payload) > this._opts.maxMessageBytes) {
801
805
  throw new WsClientError("ws-client/payload-too-big",
802
806
  "send: payload exceeds maxMessageBytes (" + this._opts.maxMessageBytes + ")");
803
807
  }
@@ -928,7 +932,12 @@ class WsClient extends EventEmitter {
928
932
  this._reconnectTimer = setTimeout(function () {
929
933
  self._reconnectTimer = null;
930
934
  if (self._closed) return; // operator-cancelled in flight
931
- self._dial();
935
+ // Re-resolve + re-validate the target (urlFor swap, DNS rebind) before
936
+ // reconnecting — awaited, so a now-private/metadata address is refused.
937
+ self._prepareDial().then(function () {
938
+ if (self._closed) return;
939
+ self._dial();
940
+ }).catch(function (e) { self._handleSocketError(e); });
932
941
  }, delay);
933
942
  if (typeof this._reconnectTimer.unref === "function") this._reconnectTimer.unref();
934
943
  this.emit("reconnecting", { attempt: this._reconnectAttempt, delayMs: delay });
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ // Internal X.509 path-validation helpers shared by the framework's
4
+ // certificate-chain walkers (b.tsa.verifyToken, b.mail.bimi VMC/CMC,
5
+ // b.mail.crypto.smime.verify). They exist because node:crypto's
6
+ // X509Certificate.checkIssued() validates the issuer/subject DN match,
7
+ // the AKI/SKI linkage, and — when a keyUsage extension is present —
8
+ // keyCertSign, but it does NOT enforce basicConstraints cA:TRUE. A
9
+ // leaf / end-entity certificate (cA:FALSE) that omits keyUsage is
10
+ // therefore wrongly accepted as a signing CA for the next cert in the
11
+ // chain — the classic basicConstraints bypass (CVE-2002-0862 class).
12
+ // Every chain walker routes its issuer test through these helpers so the
13
+ // cA enforcement can never be forgotten in one walker but present in
14
+ // another.
15
+
16
+ // True only when `cert` asserts basicConstraints cA:TRUE. node's
17
+ // X509Certificate exposes `.ca` (a boolean); a cert with no
18
+ // basicConstraints extension or with cA:FALSE returns false. A missing
19
+ // cert or a non-boolean `.ca` (parse failure / unsupported runtime)
20
+ // fails closed.
21
+ function isCaCert(cert) {
22
+ return !!cert && cert.ca === true;
23
+ }
24
+
25
+ // True when `issuer` validly issued `subject` AND is itself a CA: the
26
+ // DN / AKI-SKI / keyUsage linkage (checkIssued), the cryptographic
27
+ // signature (verify), and basicConstraints cA:TRUE (isCaCert). The cA
28
+ // check runs first so a non-CA cert is rejected before the expensive
29
+ // signature verification. Any exception (malformed cert, unsupported
30
+ // key type) fails closed to false.
31
+ function issuerValidlyIssued(issuer, subject) {
32
+ try {
33
+ return isCaCert(issuer) &&
34
+ subject.checkIssued(issuer) &&
35
+ subject.verify(issuer.publicKey);
36
+ } catch (_e) {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ module.exports = {
42
+ isCaCert: isCaCert,
43
+ issuerValidlyIssued: issuerValidlyIssued,
44
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.15.12",
3
+ "version": "0.15.14",
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:caf34b84-1358-4d28-a517-e360819dc35a",
5
+ "serialNumber": "urn:uuid:81a205f2-938f-47e5-9426-3245e2ac5859",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-06-14T16:52:10.733Z",
8
+ "timestamp": "2026-06-21T13:24:58.132Z",
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.15.12",
22
+ "bom-ref": "@blamejs/core@0.15.14",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.15.12",
25
+ "version": "0.15.14",
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.15.12",
29
+ "purl": "pkg:npm/%40blamejs/core@0.15.14",
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.15.12",
57
+ "ref": "@blamejs/core@0.15.14",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]