@blamejs/core 0.8.42 → 0.8.49

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 (222) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +10 -10
  3. package/index.js +52 -0
  4. package/lib/a2a.js +159 -34
  5. package/lib/acme.js +762 -0
  6. package/lib/ai-pref.js +166 -43
  7. package/lib/api-key.js +108 -47
  8. package/lib/api-snapshot.js +157 -40
  9. package/lib/app-shutdown.js +113 -77
  10. package/lib/archive.js +337 -40
  11. package/lib/arg-parser.js +697 -0
  12. package/lib/asyncapi.js +99 -55
  13. package/lib/atomic-file.js +465 -104
  14. package/lib/audit-chain.js +123 -34
  15. package/lib/audit-daily-review.js +389 -0
  16. package/lib/audit-sign.js +302 -56
  17. package/lib/audit-tools.js +412 -63
  18. package/lib/audit.js +656 -35
  19. package/lib/auth/jwt-external.js +17 -0
  20. package/lib/auth/oauth.js +7 -0
  21. package/lib/auth-bot-challenge.js +505 -0
  22. package/lib/auth-header.js +92 -25
  23. package/lib/backup/bundle.js +26 -0
  24. package/lib/backup/index.js +512 -89
  25. package/lib/backup/manifest.js +168 -7
  26. package/lib/break-glass.js +415 -39
  27. package/lib/budr.js +103 -30
  28. package/lib/bundler.js +86 -66
  29. package/lib/cache.js +192 -72
  30. package/lib/chain-writer.js +65 -40
  31. package/lib/circuit-breaker.js +56 -33
  32. package/lib/cli-helpers.js +106 -75
  33. package/lib/cli.js +6 -30
  34. package/lib/cloud-events.js +99 -32
  35. package/lib/cluster-storage.js +162 -37
  36. package/lib/cluster.js +340 -49
  37. package/lib/codepoint-class.js +66 -0
  38. package/lib/compliance.js +424 -24
  39. package/lib/config-drift.js +111 -46
  40. package/lib/config.js +94 -40
  41. package/lib/consent.js +165 -18
  42. package/lib/constants.js +1 -0
  43. package/lib/content-credentials.js +153 -48
  44. package/lib/cookies.js +154 -62
  45. package/lib/credential-hash.js +133 -61
  46. package/lib/crypto-field.js +702 -18
  47. package/lib/crypto-hpke.js +256 -0
  48. package/lib/crypto.js +744 -22
  49. package/lib/csv.js +178 -35
  50. package/lib/daemon.js +456 -0
  51. package/lib/dark-patterns.js +186 -55
  52. package/lib/db-query.js +79 -2
  53. package/lib/db.js +1431 -60
  54. package/lib/ddl-change-control.js +523 -0
  55. package/lib/deprecate.js +195 -40
  56. package/lib/dev.js +82 -39
  57. package/lib/dora.js +67 -48
  58. package/lib/dr-runbook.js +368 -0
  59. package/lib/dsr.js +142 -11
  60. package/lib/dual-control.js +91 -56
  61. package/lib/events.js +120 -41
  62. package/lib/external-db-migrate.js +192 -2
  63. package/lib/external-db.js +795 -50
  64. package/lib/fapi2.js +122 -1
  65. package/lib/fda-21cfr11.js +395 -0
  66. package/lib/fdx.js +132 -2
  67. package/lib/file-type.js +87 -0
  68. package/lib/file-upload.js +93 -0
  69. package/lib/flag.js +82 -20
  70. package/lib/forms.js +132 -29
  71. package/lib/framework-error.js +169 -0
  72. package/lib/framework-schema.js +163 -35
  73. package/lib/gate-contract.js +849 -175
  74. package/lib/graphql-federation.js +68 -7
  75. package/lib/guard-all.js +172 -55
  76. package/lib/guard-archive.js +286 -124
  77. package/lib/guard-auth.js +194 -21
  78. package/lib/guard-cidr.js +190 -28
  79. package/lib/guard-csv.js +397 -51
  80. package/lib/guard-domain.js +213 -91
  81. package/lib/guard-email.js +236 -29
  82. package/lib/guard-filename.js +307 -75
  83. package/lib/guard-graphql.js +263 -30
  84. package/lib/guard-html.js +310 -116
  85. package/lib/guard-image.js +243 -30
  86. package/lib/guard-json.js +260 -54
  87. package/lib/guard-jsonpath.js +235 -23
  88. package/lib/guard-jwt.js +284 -30
  89. package/lib/guard-markdown.js +204 -22
  90. package/lib/guard-mime.js +190 -26
  91. package/lib/guard-oauth.js +277 -28
  92. package/lib/guard-pdf.js +251 -27
  93. package/lib/guard-regex.js +226 -18
  94. package/lib/guard-shell.js +229 -26
  95. package/lib/guard-svg.js +177 -10
  96. package/lib/guard-template.js +232 -21
  97. package/lib/guard-time.js +195 -29
  98. package/lib/guard-uuid.js +189 -30
  99. package/lib/guard-xml.js +259 -36
  100. package/lib/guard-yaml.js +241 -44
  101. package/lib/honeytoken.js +63 -27
  102. package/lib/html-balance.js +83 -0
  103. package/lib/http-client.js +486 -59
  104. package/lib/http-message-signature.js +582 -0
  105. package/lib/i18n.js +102 -49
  106. package/lib/iab-mspa.js +112 -32
  107. package/lib/iab-tcf.js +107 -2
  108. package/lib/inbox.js +90 -52
  109. package/lib/keychain.js +865 -0
  110. package/lib/legal-hold.js +374 -0
  111. package/lib/local-db-thin.js +320 -0
  112. package/lib/log-stream.js +281 -51
  113. package/lib/log.js +184 -86
  114. package/lib/mail-bounce.js +107 -62
  115. package/lib/mail.js +295 -58
  116. package/lib/mcp.js +108 -27
  117. package/lib/metrics.js +98 -89
  118. package/lib/middleware/age-gate.js +36 -0
  119. package/lib/middleware/ai-act-disclosure.js +37 -0
  120. package/lib/middleware/api-encrypt.js +45 -0
  121. package/lib/middleware/assetlinks.js +40 -0
  122. package/lib/middleware/asyncapi-serve.js +35 -0
  123. package/lib/middleware/attach-user.js +40 -0
  124. package/lib/middleware/bearer-auth.js +40 -0
  125. package/lib/middleware/body-parser.js +230 -0
  126. package/lib/middleware/bot-disclose.js +34 -0
  127. package/lib/middleware/bot-guard.js +39 -0
  128. package/lib/middleware/compression.js +37 -0
  129. package/lib/middleware/cookies.js +32 -0
  130. package/lib/middleware/cors.js +40 -0
  131. package/lib/middleware/csp-nonce.js +40 -0
  132. package/lib/middleware/csp-report.js +34 -0
  133. package/lib/middleware/csrf-protect.js +43 -0
  134. package/lib/middleware/daily-byte-quota.js +53 -85
  135. package/lib/middleware/db-role-for.js +40 -0
  136. package/lib/middleware/dpop.js +40 -0
  137. package/lib/middleware/error-handler.js +37 -14
  138. package/lib/middleware/fetch-metadata.js +39 -0
  139. package/lib/middleware/flag-context.js +34 -0
  140. package/lib/middleware/gpc.js +33 -0
  141. package/lib/middleware/headers.js +35 -0
  142. package/lib/middleware/health.js +46 -0
  143. package/lib/middleware/host-allowlist.js +30 -0
  144. package/lib/middleware/network-allowlist.js +38 -0
  145. package/lib/middleware/openapi-serve.js +34 -0
  146. package/lib/middleware/rate-limit.js +160 -18
  147. package/lib/middleware/request-id.js +36 -18
  148. package/lib/middleware/request-log.js +37 -0
  149. package/lib/middleware/require-aal.js +29 -0
  150. package/lib/middleware/require-auth.js +32 -0
  151. package/lib/middleware/require-bound-key.js +41 -0
  152. package/lib/middleware/require-content-type.js +32 -0
  153. package/lib/middleware/require-methods.js +27 -0
  154. package/lib/middleware/require-mtls.js +33 -0
  155. package/lib/middleware/require-step-up.js +37 -0
  156. package/lib/middleware/security-headers.js +44 -0
  157. package/lib/middleware/security-txt.js +38 -0
  158. package/lib/middleware/span-http-server.js +37 -0
  159. package/lib/middleware/sse.js +36 -0
  160. package/lib/middleware/trace-log-correlation.js +33 -0
  161. package/lib/middleware/trace-propagate.js +32 -0
  162. package/lib/middleware/tus-upload.js +90 -0
  163. package/lib/middleware/web-app-manifest.js +53 -0
  164. package/lib/mtls-ca.js +100 -70
  165. package/lib/network-byte-quota.js +308 -0
  166. package/lib/network-heartbeat.js +135 -0
  167. package/lib/network-tls.js +534 -4
  168. package/lib/network.js +103 -0
  169. package/lib/notify.js +114 -43
  170. package/lib/ntp-check.js +192 -51
  171. package/lib/observability.js +145 -47
  172. package/lib/openapi.js +90 -44
  173. package/lib/outbox.js +99 -1
  174. package/lib/pagination.js +168 -86
  175. package/lib/parsers/index.js +16 -5
  176. package/lib/permissions.js +93 -40
  177. package/lib/pqc-agent.js +84 -8
  178. package/lib/pqc-software.js +94 -60
  179. package/lib/process-spawn.js +95 -21
  180. package/lib/pubsub.js +96 -66
  181. package/lib/queue.js +375 -54
  182. package/lib/redact.js +793 -21
  183. package/lib/render.js +139 -47
  184. package/lib/request-helpers.js +485 -121
  185. package/lib/restore-bundle.js +142 -39
  186. package/lib/restore-rollback.js +136 -45
  187. package/lib/retention.js +178 -50
  188. package/lib/retry.js +116 -33
  189. package/lib/router.js +475 -23
  190. package/lib/safe-async.js +543 -94
  191. package/lib/safe-buffer.js +337 -41
  192. package/lib/safe-json.js +467 -62
  193. package/lib/safe-jsonpath.js +285 -0
  194. package/lib/safe-schema.js +631 -87
  195. package/lib/safe-sql.js +221 -59
  196. package/lib/safe-url.js +278 -46
  197. package/lib/sandbox-worker.js +135 -0
  198. package/lib/sandbox.js +358 -0
  199. package/lib/scheduler.js +135 -70
  200. package/lib/self-update.js +647 -0
  201. package/lib/session-device-binding.js +431 -0
  202. package/lib/session.js +259 -49
  203. package/lib/slug.js +138 -26
  204. package/lib/ssrf-guard.js +316 -56
  205. package/lib/storage.js +433 -70
  206. package/lib/subject.js +405 -23
  207. package/lib/template.js +148 -8
  208. package/lib/tenant-quota.js +545 -0
  209. package/lib/testing.js +440 -53
  210. package/lib/time.js +291 -23
  211. package/lib/tls-exporter.js +239 -0
  212. package/lib/tracing.js +90 -74
  213. package/lib/uuid.js +97 -22
  214. package/lib/vault/index.js +284 -22
  215. package/lib/vault/seal-pem-file.js +66 -0
  216. package/lib/watcher.js +368 -0
  217. package/lib/webhook.js +196 -63
  218. package/lib/websocket.js +393 -68
  219. package/lib/wiki-concepts.js +338 -0
  220. package/lib/worker-pool.js +464 -0
  221. package/package.json +3 -3
  222. package/sbom.cyclonedx.json +7 -7
@@ -1,59 +1,39 @@
1
1
  "use strict";
2
2
  /**
3
- * b.pqcSoftware — pure-JS post-quantum primitives sourced from the
4
- * vendored @noble/post-quantum bundle (lib/vendor/noble-post-quantum.cjs).
3
+ * @module b.pqcSoftware
4
+ * @nav Crypto
5
+ * @title PQC Software
5
6
  *
6
- * Usable server-side and client-side. Ciphertexts are FIPS 203
7
- * conformant in both directions encapsulating with Node's WebCrypto
8
- * ML-KEM-1024 (used by b.crypto.encrypt / b.middleware.apiEncrypt)
9
- * decapsulates with b.pqcSoftware.ml_kem_1024 and vice versa.
7
+ * @intro
8
+ * Pure-JS post-quantum cryptography wrapper around the vendored
9
+ * `@noble/post-quantum` bundle (`lib/vendor/noble-post-quantum.cjs`).
10
+ * Ships the FIPS-203 ML-KEM family, FIPS-204 ML-DSA family, and
11
+ * FIPS-205 SLH-DSA family (both SHAKE and SHA-2 hash variants) as
12
+ * first-class accessors on `b.pqcSoftware.*`.
10
13
  *
11
- * Operator wiring:
14
+ * Defaults pin to the highest category-5 parameter set per family:
15
+ * `DEFAULT_KEM` = ML-KEM-1024, `DEFAULT_LATTICE_SIG` = ML-DSA-87,
16
+ * `DEFAULT_HASH_SIG` = SLH-DSA-SHAKE-256f. Ciphertexts are FIPS-203
17
+ * conformant in both directions — output produced by Node's
18
+ * WebCrypto ML-KEM-1024 (used by `b.crypto.encrypt` and
19
+ * `b.middleware.apiEncrypt`) decapsulates here, and vice versa,
20
+ * making this the reference-implementation path for interop tests
21
+ * against Node WebCrypto or a hardware HSM.
12
22
  *
13
- * - Server-side: import b.pqcSoftware directly. Use it as the
14
- * primary PQC path on Node releases without the experimental
15
- * WebCrypto ML-KEM extension, or for reference-implementation
16
- * interop testing against Node WebCrypto / hardware HSMs.
23
+ * Each KEM exposes `keygen()` / `encapsulate()` / `decapsulate()`;
24
+ * each signature object exposes `keygen()` / `sign()` / `verify()`
25
+ * both shapes match the upstream `@noble/post-quantum` API
26
+ * directly, so the module is also re-bundlable into a browser
27
+ * build that ships `b.middleware.apiEncrypt.client`.
17
28
  *
18
- * - Client-side: re-bundle this module or import @noble/post-quantum
19
- * directly into the build that ships b.middleware.apiEncrypt.client.
29
+ * The vendored bundle is a build artifact. In deployments that
30
+ * stripped `lib/vendor/`, `isAvailable()` returns `false` and every
31
+ * accessor returns a stub that throws `PqcError` on call —
32
+ * operators in that posture fall back to Node WebCrypto via
33
+ * `b.crypto.encrypt` / `b.crypto.decrypt`.
20
34
  *
21
- * Defaults pin to the highest cat-5 level:
22
- *
23
- * - DEFAULT_KEM = ML-KEM-1024 (FIPS 203)
24
- * - DEFAULT_LATTICE_SIG = ML-DSA-87 (FIPS 204)
25
- * - DEFAULT_HASH_SIG = SLH-DSA-SHAKE-256f (FIPS 205)
26
- *
27
- * Public surface (b.pqcSoftware.*):
28
- *
29
- * .ml_kem_1024 / .ml_kem_768 / .ml_kem_512 — FIPS 203 KEM objects
30
- * .ml_dsa_87 / .ml_dsa_65 / .ml_dsa_44 — FIPS 204 lattice sig
31
- * .slh_dsa_shake_256f / 192f / 128f — FIPS 205 (SHAKE)
32
- * .slh_dsa_sha2_256f / 192f / 128f — FIPS 205 (SHA-2)
33
- *
34
- * .DEFAULT_KEM — alias to ml_kem_1024
35
- * .DEFAULT_LATTICE_SIG — alias to ml_dsa_87
36
- * .DEFAULT_HASH_SIG — alias to slh_dsa_shake_256f
37
- *
38
- * .isAvailable() — boolean: is the vendored bundle loadable?
39
- * .listAlgorithms() — string[] of algorithm names
40
- *
41
- * Each KEM / signature object exposes `keygen()` / `encapsulate()` /
42
- * `decapsulate()` (KEMs) or `keygen()` / `sign()` / `verify()`
43
- * (signatures), matching the @noble/post-quantum API directly.
44
- *
45
- * Operators chaining this into other primitives:
46
- *
47
- * var pqc = b.pqcSoftware;
48
- * var kp = pqc.DEFAULT_KEM.keygen();
49
- * var enc = pqc.DEFAULT_KEM.encapsulate(kp.publicKey);
50
- * // enc.cipherText / enc.sharedSecret
51
- *
52
- * Note on availability: the bundle is a build artifact in
53
- * lib/vendor/noble-post-quantum.cjs. In tightly-locked deployments
54
- * where operators stripped the vendor directory, .isAvailable()
55
- * returns false and the module exposes a stub that throws on every
56
- * primitive call.
35
+ * @card
36
+ * Pure-JS post-quantum cryptography wrapper around the vendored `@noble/post-quantum` bundle (`lib/vendor/noble-post-quantum.cjs`).
57
37
  */
58
38
 
59
39
  var { defineClass } = require("./framework-error");
@@ -107,10 +87,50 @@ function _accessor(name) {
107
87
  return algo;
108
88
  }
109
89
 
90
+ /**
91
+ * @primitive b.pqcSoftware.isAvailable
92
+ * @signature b.pqcSoftware.isAvailable()
93
+ * @since 0.7.28
94
+ * @status stable
95
+ * @related b.pqcSoftware.listAlgorithms, b.pqcSoftware.runKnownAnswerTest
96
+ *
97
+ * Returns `true` when the vendored `@noble/post-quantum` bundle loaded
98
+ * successfully and its KEM / signature objects are wired into the
99
+ * accessors. Returns `false` when `lib/vendor/noble-post-quantum.cjs`
100
+ * is missing or threw at require time — every accessor in that
101
+ * posture returns a stub whose primitive calls throw `PqcError`.
102
+ *
103
+ * @example
104
+ * var b = require("blamejs").create();
105
+ * if (b.pqcSoftware.isAvailable()) {
106
+ * var ss = b.pqcSoftware.DEFAULT_KEM.keygen();
107
+ * ss.publicKey.length;
108
+ * // → 1568 (ML-KEM-1024 public key, FIPS 203 §8 |pk| = 1568)
109
+ * }
110
+ */
110
111
  function isAvailable() {
111
112
  return _load() !== null;
112
113
  }
113
114
 
115
+ /**
116
+ * @primitive b.pqcSoftware.listAlgorithms
117
+ * @signature b.pqcSoftware.listAlgorithms()
118
+ * @since 0.7.28
119
+ * @status stable
120
+ * @related b.pqcSoftware.isAvailable, b.pqcSoftware.runKnownAnswerTest
121
+ *
122
+ * Returns the names of every PQC algorithm exposed on the
123
+ * `b.pqcSoftware` surface — the three ML-KEM parameter sets, the
124
+ * three ML-DSA parameter sets, and six SLH-DSA parameter sets (three
125
+ * SHAKE + three SHA-2). Returns an empty array when the vendored
126
+ * bundle is unavailable.
127
+ *
128
+ * @example
129
+ * var b = require("blamejs").create();
130
+ * var names = b.pqcSoftware.listAlgorithms();
131
+ * names.indexOf("ml_kem_1024") >= 0;
132
+ * // → true (when the vendored bundle is present)
133
+ */
114
134
  function listAlgorithms() {
115
135
  if (!isAvailable()) return [];
116
136
  return [
@@ -193,17 +213,31 @@ Object.defineProperty(pqc, "DEFAULT_HASH_SIG", {
193
213
  get: function () { return _accessor("slh_dsa_shake_256f"); },
194
214
  });
195
215
 
196
- // runKnownAnswerTest — round-trip the vendored ML-KEM-1024 against
197
- // itself with a self-generated keypair. This is NOT the FIPS 203
198
- // Appendix A KAT vector (those are 800 KB of test data the framework
199
- // chooses not to vendor); it's a self-consistency check that the
200
- // vendored bundle's keygen / encapsulate / decapsulate survives a
201
- // full cycle and produces a 32-byte shared secret. The fallback
202
- // path becomes load-bearing if Node strips the WebCrypto ML-KEM
203
- // extension; this gate fails fast at boot rather than mid-request.
204
- //
205
- // var result = b.pqcSoftware.runKnownAnswerTest();
206
- // if (!result.ok) throw new Error("PQC KAT failed: " + result.reason);
216
+ /**
217
+ * @primitive b.pqcSoftware.runKnownAnswerTest
218
+ * @signature b.pqcSoftware.runKnownAnswerTest()
219
+ * @since 0.7.28
220
+ * @status stable
221
+ * @related b.pqcSoftware.isAvailable, b.pqcSoftware.listAlgorithms
222
+ *
223
+ * Round-trips ML-KEM-1024 against itself with a self-generated
224
+ * keypair: `keygen` → `encapsulate` → `decapsulate`, then a
225
+ * constant-time compare of the two shared secrets. This is a self-
226
+ * consistency gate, not the FIPS 203 Appendix A KAT vectors (those
227
+ * ~800 KB of test data are intentionally not vendored). The check
228
+ * fails fast at boot if the vendored bundle is broken, rather than
229
+ * mid-request when an envelope decrypt aborts.
230
+ *
231
+ * Returns `{ ok, reason?, sharedSecretLength? }`. `ok: true` means
232
+ * keygen / encapsulate / decapsulate cycled cleanly and the two
233
+ * shared secrets are byte-identical (32 bytes per FIPS 203 §1).
234
+ *
235
+ * @example
236
+ * var b = require("blamejs").create();
237
+ * var result = b.pqcSoftware.runKnownAnswerTest();
238
+ * result.ok;
239
+ * // → true (or { ok: false, reason: "<diagnostic>" } when broken)
240
+ */
207
241
  function runKnownAnswerTest() {
208
242
  if (!isAvailable()) {
209
243
  return { ok: false, reason: "vendored @noble/post-quantum bundle not loadable" };
@@ -1,29 +1,37 @@
1
1
  "use strict";
2
2
  /**
3
- * b.processSpawn — child-process launcher that strips connection-string
4
- * secrets from the environment before exec. Operators reaching for
5
- * `child_process.spawn` directly inherit `process.env` by default —
6
- * which means a child (jq, postgres CLI, an unzipper) sees
7
- * `DATABASE_URL`, `PG*`, `REDIS_URL`, `S3_*`, `AWS_*`. OWASP-1 closes
8
- * that class: every spawn through this primitive uses a filtered env
9
- * by default; operators opt in to specific secret env vars when the
10
- * child genuinely needs them.
3
+ * @module b.processSpawn
4
+ * @nav Production
5
+ * @title Process Spawn
11
6
  *
12
- * var child = b.processSpawn.spawn("jq", [".name"], {
13
- * stdio: "pipe",
14
- * // env: { ... } // optional override; defaults to filtered
15
- * // allowEnv: ["AWS_REGION"] // explicit pass-through whitelist
16
- * });
7
+ * @intro
8
+ * Secret-safe `child_process.spawn` wrapper — argv allowlist via
9
+ * the operator's caller, no `shell:true` reliance, env scrubbing of
10
+ * connection strings and credential variables, and output redaction
11
+ * in the audit metadata (env-var NAMES are recorded, never values).
12
+ *
13
+ * Operators reaching for `child_process.spawn` directly inherit
14
+ * `process.env` by default — which means a child (`jq`, the
15
+ * postgres CLI, an unzipper) sees `DATABASE_URL`, `PG*`, `REDIS_URL`,
16
+ * `S3_*`, `AWS_*`. OWASP-1 closes that class: every spawn through
17
+ * `b.processSpawn` uses a filtered env by default; operators opt in
18
+ * to specific secret env vars via `opts.allowEnv` when the child
19
+ * genuinely needs them.
17
20
  *
18
- * Filter list (case-insensitive — matches Windows env var names):
19
- * DATABASE_URL, PG*, POSTGRES*, MYSQL*, REDIS_URL, MONGO_URL,
20
- * AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN,
21
- * S3_*, AZURE_*, GCP_*, GOOGLE_APPLICATION_CREDENTIALS,
22
- * *_TOKEN, *_SECRET, *_PASSWORD, *_API_KEY, *_PRIVATE_KEY.
21
+ * Filter patterns (case-insensitive — matches Windows env-var
22
+ * capitalization too): `DATABASE_URL`, `PG*`, `POSTGRES*`, `MYSQL*`,
23
+ * `REDIS_URL`, `MONGO*`, `AWS_(ACCESS_KEY_ID|SECRET_ACCESS_KEY|
24
+ * SESSION_TOKEN)`, `S3_*`, `AZURE_*`, `GCP_*`,
25
+ * `GOOGLE_APPLICATION_CREDENTIALS`, suffixes `*_TOKEN`, `*_SECRET`,
26
+ * `*_PASSWORD`, `*_API_KEY`, `*_PRIVATE_KEY`, `*_PASSPHRASE`. The
27
+ * frozen pattern list is exposed as
28
+ * `b.processSpawn.FILTER_PATTERNS` for operator inspection.
23
29
  *
24
- * Audit: `process.spawn` (success) — metadata carries command + arg
25
- * count + which env vars were filtered out (NOT their values). On
26
- * exec failure: `process.spawn.failed` with the error code.
30
+ * Audit: `process.spawn` (success) — metadata carries command, arg
31
+ * count, and the redacted list of env-var names that were stripped.
32
+ *
33
+ * @card
34
+ * Secret-safe `child_process.spawn` wrapper — argv allowlist via the operator's caller, no `shell:true` reliance, env scrubbing of connection strings and credential variables, and output redaction in the audit metadata (env-var NAMES are recorded, never values).
27
35
  */
28
36
 
29
37
  var lazyRequire = require("./lazy-require");
@@ -62,6 +70,37 @@ function _shouldFilter(name) {
62
70
  return false;
63
71
  }
64
72
 
73
+ /**
74
+ * @primitive b.processSpawn.filteredEnv
75
+ * @signature b.processSpawn.filteredEnv(source, allowEnv)
76
+ * @since 0.8.42
77
+ * @status stable
78
+ * @related b.processSpawn.spawn
79
+ *
80
+ * Pure helper that returns `{ env, filtered }` — `env` is `source`
81
+ * with every variable matching `FILTER_PATTERNS` removed, except for
82
+ * names listed in `allowEnv` (explicit pass-through). `filtered` is
83
+ * the array of stripped variable names; values are never returned or
84
+ * logged.
85
+ *
86
+ * `source` defaults to `process.env` when omitted. Useful for
87
+ * pre-flight inspection (which secrets would the spawn drop?) without
88
+ * actually launching a child.
89
+ *
90
+ * @example
91
+ * var report = b.processSpawn.filteredEnv({
92
+ * PATH: "/usr/bin",
93
+ * AWS_ACCESS_KEY_ID: "AKIA...",
94
+ * AWS_SECRET_ACCESS_KEY: "wJalr...",
95
+ * AWS_REGION: "us-east-1",
96
+ * DATABASE_URL: "postgres://...",
97
+ * }, ["AWS_REGION"]);
98
+ * report.env.PATH; // → "/usr/bin"
99
+ * report.env.AWS_REGION; // → "us-east-1"
100
+ * report.env.DATABASE_URL; // → undefined
101
+ * report.filtered.indexOf("AWS_ACCESS_KEY_ID") !== -1; // → true
102
+ * report.filtered.indexOf("DATABASE_URL") !== -1; // → true
103
+ */
65
104
  function filteredEnv(source, allowEnv) {
66
105
  var src = source || process.env;
67
106
  var allowSet = {};
@@ -81,6 +120,41 @@ function filteredEnv(source, allowEnv) {
81
120
  return { env: out, filtered: filtered };
82
121
  }
83
122
 
123
+ /**
124
+ * @primitive b.processSpawn.spawn
125
+ * @signature b.processSpawn.spawn(command, args, opts)
126
+ * @since 0.8.42
127
+ * @status stable
128
+ * @related b.processSpawn.filteredEnv, b.daemon.start, b.audit.safeEmit
129
+ *
130
+ * Spawn a child process with the connection-string filter applied to
131
+ * `process.env` before exec. Returns the underlying
132
+ * `child_process.ChildProcess` so operators can attach the usual
133
+ * `stdout` / `stderr` / `close` listeners. Emits one `process.spawn`
134
+ * audit row carrying the command, arg count, and the names (never
135
+ * values) of the env vars that were stripped.
136
+ *
137
+ * Throws `ProcessSpawnError("process-spawn/bad-command")` when
138
+ * `command` is not a non-empty string. `opts.env`, when supplied, is
139
+ * trusted verbatim — operators that pass an explicit env take full
140
+ * responsibility for what reaches the child.
141
+ *
142
+ * @opts
143
+ * stdio: string | Array, // forwarded to child_process.spawn
144
+ * cwd: string, // forwarded to child_process.spawn
145
+ * detached: boolean, // forwarded to child_process.spawn
146
+ * env: object, // explicit override; bypasses filter
147
+ * allowEnv: string[], // pass-through whitelist applied to process.env
148
+ * ... // every other Node spawn opt is forwarded
149
+ *
150
+ * @example
151
+ * var child = b.processSpawn.spawn(process.execPath, ["-e", "process.exit(0)"], {
152
+ * stdio: "ignore",
153
+ * allowEnv: ["AWS_REGION"],
154
+ * });
155
+ * typeof child.pid; // → "number"
156
+ * child.kill();
157
+ */
84
158
  function spawn(command, args, opts) {
85
159
  if (typeof command !== "string" || command.length === 0) {
86
160
  throw new ProcessSpawnError("process-spawn/bad-command",
package/lib/pubsub.js CHANGED
@@ -1,76 +1,41 @@
1
1
  "use strict";
2
2
  /**
3
- * pubsub — distributed pub/sub primitive.
3
+ * @module b.pubsub
4
+ * @nav Communication
5
+ * @title Pubsub
4
6
  *
5
- * Generalizes the cluster-table fan-out pattern that previously lived
6
- * inline in `lib/websocket-channels.js` and the `NOT_SUPPORTED` cache
7
- * cluster `invalidateTag` path. Three backends:
7
+ * @intro
8
+ * Cluster-aware pub/sub channel for in-process and cross-replica
9
+ * messaging. Three backends share one operator API:
8
10
  *
9
- * local — in-process Map<channel, Set<handler>>; publish dispatches
10
- * SYNCHRONOUSLY before returning. Single-node deploys pay
11
- * zero coordination overhead.
12
- * cluster — shared `_blamejs_pubsub_messages` table polled at
13
- * pollIntervalMs; publish writes a row + dispatches locally;
14
- * other nodes pick up rows via `id > lastSeenId AND
15
- * publishedBy <> selfNodeId`. Default cluster mode for any
16
- * `b.cluster`-aware deploy.
17
- * redis — Redis PUB/SUB on the bespoke `lib/redis-client.js`. One
18
- * connection per pubsub instance enters subscribe mode
19
- * (demultiplexed via `setOnPushMessage`); publish goes
20
- * through a separate command-mode connection.
11
+ * local — in-process `Map<channel, Set<handler>>`; publish
12
+ * dispatches synchronously before returning. Single-node
13
+ * deploys pay zero coordination overhead.
14
+ * cluster — shared `_blamejs_pubsub_messages` table polled at
15
+ * `pollIntervalMs`; publish writes a row + dispatches
16
+ * locally; other nodes pick up rows via
17
+ * `id > lastSeenId AND publishedBy <> selfNodeId`. The
18
+ * default for any `b.cluster`-aware deploy.
19
+ * redis — Redis PUB/SUB on `lib/redis-client.js`. One connection
20
+ * enters subscribe mode (demultiplexed via
21
+ * `setOnPushMessage`); publish goes through a separate
22
+ * command-mode connection.
21
23
  *
22
- * Operator API:
24
+ * Pattern subscribe accepts glob-style topic matchers (`*` matches one
25
+ * `.`-delimited segment, `**` matches any suffix). Channel names cap
26
+ * at 1 KiB to defeat pathological matcher inputs. Subscribe /
27
+ * unsubscribe is ref-counted across local handlers so the remote
28
+ * backend only carries one subscription per scoped channel.
23
29
  *
24
- * var ps = b.pubsub.create({
25
- * backend: 'local' | 'cluster' | 'redis' | { custom },
26
- * // cluster opts
27
- * cluster: clusterInstance,
28
- * pollIntervalMs: C.TIME.ms? — default 100ms
29
- * retentionMs: C.TIME.ms? — default 60s
30
- * pruneEveryMs: C.TIME.ms? — default 5min
31
- * // redis opts
32
- * redisUrl: string — required for redis backend
33
- * redisPassword: string?
34
- * redisUsername: string?
35
- * redisTls: boolean?
36
- * redisCa: string|Buffer?
37
- * redisServername: string?
38
- * // common
39
- * topicPrefix: string? — every publish/subscribe channel
40
- * scoped to `<topicPrefix>:<channel>`
41
- * so independent pubsub instances
42
- * sharing a backend don't collide.
43
- * audit: boolean? — default false. When true emits
44
- * `system.pubsub.publish` per call.
45
- * });
46
- *
47
- * var token = ps.subscribe(channel, function (payload, ev) {
48
- * // payload is whatever publish() received (objects survive JSON
49
- * // round-trip on remote backends; on the local backend the
50
- * // reference is passed through). ev = { channel, source: 'local'
51
- * // | 'remote', publishedBy?, publishedAt? }.
52
- * });
53
- * ps.unsubscribe(token);
54
- *
55
- * await ps.publish(channel, payload); // returns { local, remote? }
56
- *
57
- * await ps.close(); // tears down backend
30
+ * Local dispatch always happens BEFORE `publish()` resolves regardless
31
+ * of backend same-node subscribers see the payload with near-zero
32
+ * latency. Handler errors are caught and logged via the boot logger;
33
+ * they never abort dispatch to siblings. Bad-shape remote payloads
34
+ * drop silently after a logged warning so one malformed cross-node
35
+ * message can't poison the local handler chain.
58
36
  *
59
- * Local dispatch always happens BEFORE the publish() promise resolves,
60
- * regardless of backend — same-node subscribers see the payload with
61
- * near-zero latency. The remote write is awaited so the caller knows
62
- * the cross-node fan-out completed.
63
- *
64
- * Subscription handler errors are caught and logged via the framework's
65
- * boot logger; they never abort dispatch to other handlers on the same
66
- * channel.
67
- *
68
- * Channel naming is operator-defined — pubsub treats names as opaque
69
- * strings (with the optional topicPrefix prepended). Pattern subscribe
70
- * (Redis-style `news.*`) is exposed via `subscribePattern(pattern,
71
- * handler)`; not every backend supports it (cluster-table backend
72
- * matches client-side, redis backend uses PSUBSCRIBE, local backend
73
- * matches client-side too).
37
+ * @card
38
+ * Cluster-aware pub/sub channel for in-process and cross-replica messaging.
74
39
  */
75
40
  var C = require("./constants");
76
41
  var lazyRequire = require("./lazy-require");
@@ -196,6 +161,71 @@ function _matchPattern(pattern, channel) {
196
161
  return false;
197
162
  }
198
163
 
164
+ /**
165
+ * @primitive b.pubsub.create
166
+ * @signature b.pubsub.create(opts)
167
+ * @since 0.6.34
168
+ * @status stable
169
+ * @related b.cluster, b.websocketChannels
170
+ *
171
+ * Build a pub/sub instance bound to one of the supported backends.
172
+ * Returned object exposes `subscribe(channel, handler)` /
173
+ * `subscribePattern(pattern, handler)` for receive,
174
+ * `unsubscribe(token)` for cleanup, `publish(channel, payload)` for
175
+ * fan-out, and `close()` for teardown. Tokens returned by subscribe
176
+ * are opaque records; pass them back to `unsubscribe` verbatim.
177
+ *
178
+ * Throws `PubsubError("UNKNOWN_BACKEND")` when `opts.backend` is not
179
+ * one of `"local"`, `"cluster"`, `"redis"`, or a custom backend object
180
+ * implementing `{ publishRemote, start, stop }`. Throws
181
+ * `PubsubError("BAD_BACKEND")` when a custom backend object is missing
182
+ * those methods.
183
+ *
184
+ * @opts
185
+ * backend: "local" | "cluster" | "redis" | object // default "local"
186
+ * cluster: object, // required for backend "cluster"
187
+ * pollIntervalMs: number, // cluster poll cadence; default 100ms
188
+ * retentionMs: number, // cluster row retention; default 60_000
189
+ * pruneEveryMs: number, // cluster prune cadence; default 300_000
190
+ * redisUrl: string, // required for backend "redis"
191
+ * redisPassword: string,
192
+ * redisUsername: string,
193
+ * redisTls: boolean,
194
+ * redisCa: string | Buffer,
195
+ * redisServername: string,
196
+ * topicPrefix: string, // scopes every channel as `<prefix>:<channel>`
197
+ * audit: boolean, // default false; emit system.pubsub.publish
198
+ *
199
+ * @example
200
+ * var ps = b.pubsub.create({ backend: "local" });
201
+ *
202
+ * var token = ps.subscribe("user.created", function (payload, ev) {
203
+ * console.log(ev.channel, ev.source, payload.id);
204
+ * // → user.created local 42
205
+ * });
206
+ *
207
+ * await ps.publish("user.created", { id: 42 });
208
+ * ps.unsubscribe(token);
209
+ * await ps.close();
210
+ *
211
+ * @example
212
+ * // Glob-style topic matchers: '*' matches one segment, '**' any suffix.
213
+ * var ps = b.pubsub.create({ backend: "local" });
214
+ *
215
+ * ps.subscribePattern("orders.*.created", function (payload, ev) {
216
+ * console.log(ev.channel);
217
+ * // → orders.eu.created
218
+ * });
219
+ *
220
+ * ps.subscribePattern("audit.**", function (payload, ev) {
221
+ * console.log(ev.channel);
222
+ * // → audit.security.login.failed
223
+ * });
224
+ *
225
+ * await ps.publish("orders.eu.created", { orderId: "ord_1" });
226
+ * await ps.publish("audit.security.login.failed", { userId: "u_7" });
227
+ * await ps.close();
228
+ */
199
229
  function create(opts) {
200
230
  opts = opts || {};
201
231
  validateOpts(opts, [