@blamejs/core 0.8.43 → 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 +92 -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,29 +1,53 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-template — Template-injection identifier-safety primitive
4
- * (b.guardTemplate).
3
+ * @module b.guardTemplate
4
+ * @nav Guards
5
+ * @title Guard Template
5
6
  *
6
- * Detects Server-Side Template Injection (SSTI) shapes in user-input
7
- * strings before they're rendered through any template engine. Refused
8
- * by default at every profile — operator-untrusted input rarely
9
- * legitimately contains template-engine syntax. KIND="identifier"
10
- * consumes ctx.identifier (or ctx.text).
7
+ * @intro
8
+ * Server-Side Template Injection (SSTI) content-safety guard
9
+ * refuses user-supplied strings that contain template-engine
10
+ * syntax BEFORE they're rendered. Template-injection vulnerable
11
+ * surfaces escape sandboxes through engine helpers (`render`,
12
+ * `lookup`, `with`, `attr_filter`); the safe shape is "logic-less
13
+ * templates only, untrusted strings are data not code". This
14
+ * primitive enforces that boundary by refusing engine syntax in
15
+ * any operator-untrusted input. Pair with logic-less Mustache
16
+ * helpers, Handlebars `noEscape: false`, and Liquid's strict-
17
+ * variables mode so the framework's defense-in-depth holds even
18
+ * when an operator forgets to escape. KIND=`identifier`; the
19
+ * gate consumes `ctx.identifier` (or `ctx.text`) and refuses on
20
+ * hostile shapes.
11
21
  *
12
- * Threat catalog (engine-shape detection):
13
- * - Jinja2 / Django / Twig / Liquid — `{{...}}` and `{%...%}`.
14
- * Recent CVEs: CVE-2024-22195 (Jinja xml_attr filter),
15
- * CVE-2024-26139 (Bottle), CVE-2024-23348 (Pyrogram).
16
- * - Handlebars `{{...}}` (same shape as Jinja; flagged together).
17
- * - ERB / Tornado `<%...%>` and `<%=...%>`.
18
- * - Pug `#{...}` interpolation, `!{...}` raw-HTML interpolation.
19
- * - Mako / Velocity / Tornado — `${...}` interpolation.
20
- * - Velocity directive `#set(...)`, `#if(...)`, `#foreach(...)`.
21
- * - AngularJS `{{...}}` (covered by Jinja shape; legacy).
22
- * - BIDI / null / control / zero-width universal refuse.
22
+ * Threat catalog (engine-shape detection): Jinja2 / Django / Twig
23
+ * / Liquid / Handlebars / Mustache / AngularJS — `{{...}}`
24
+ * expressions and `{%...%}` statements (CVE-2024-22195 Jinja
25
+ * `xml_attr_filter`, CVE-2024-26139 Bottle, CVE-2024-23348
26
+ * Pyrogram); ERB / Tornado `<%...%>` and `<%=...%>`; Pug
27
+ * `#{...}` interpolation and `!{...}` raw-HTML interpolation
28
+ * (prototype-pollution exit when the model is operator-fed);
29
+ * Mako / Velocity / Tornado / JS template-literal — `${...}`
30
+ * interpolation; Velocity directives (`#set`, `#if`, `#foreach`,
31
+ * `#parse`, `#include`); BIDI / null / C0 control / zero-width
32
+ * universal refuse.
23
33
  *
24
- * var rv = b.guardTemplate.validate("Hello {{ name }}",
25
- * { profile: "strict" });
26
- * var g = b.guardTemplate.gate({ profile: "strict" });
34
+ * Profiles: `strict` / `balanced` / `permissive`. Compliance
35
+ * postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. Operators
36
+ * select via `{ profile: "strict" }` or
37
+ * `{ compliance: "hipaa" }`; postures overlay on top of the
38
+ * profile baseline. Jinja / ERB / Pug shape rejection holds at
39
+ * every profile — the SSTI class is never an operator opt-in.
40
+ *
41
+ * Template input cannot be repaired safely (stripping `{{` from
42
+ * `{{name}}` produces a different document); `sanitize` either
43
+ * passes through clean input or throws `GuardTemplateError`; the
44
+ * gate returns `serve` / `audit-only` / `refuse` (no `sanitize`
45
+ * action). The `${...}` and Velocity-directive policies default
46
+ * to `audit` outside `strict` because they overlap with legitimate
47
+ * JS / shell substrings, so operators tune via overrides.
48
+ *
49
+ * @card
50
+ * Server-Side Template Injection (SSTI) content-safety guard — refuses user-supplied strings that contain template-engine syntax BEFORE they're rendered.
27
51
  */
28
52
 
29
53
  var codepointClass = require("./codepoint-class");
@@ -195,6 +219,45 @@ function _detectIssues(input, opts) {
195
219
  return issues;
196
220
  }
197
221
 
222
+ /**
223
+ * @primitive b.guardTemplate.validate
224
+ * @signature b.guardTemplate.validate(input, opts)
225
+ * @since 0.7.13
226
+ * @status stable
227
+ * @compliance hipaa, pci-dss, gdpr, soc2
228
+ * @related b.guardTemplate.gate, b.guardTemplate.sanitize
229
+ *
230
+ * Inspect a user-supplied template-rendering input and return an
231
+ * aggregated issue list. Pure inspection — never throws on
232
+ * hostile input; caller decides what to do with the issues. The
233
+ * `ok` flag is `true` only when zero `critical` / `high` issues
234
+ * fire. Throws `GuardTemplateError("template.bad-opt")` when a
235
+ * numeric opt is non-finite / negative (config-time mistake by
236
+ * the operator).
237
+ *
238
+ * @opts
239
+ * profile: "strict"|"balanced"|"permissive",
240
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
241
+ * bidiPolicy: "reject"|"audit"|"allow",
242
+ * controlPolicy: "reject"|"audit"|"allow",
243
+ * nullBytePolicy: "reject"|"audit"|"allow",
244
+ * zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
245
+ * jinjaPolicy: "reject"|"audit"|"allow",
246
+ * erbPolicy: "reject"|"audit"|"allow",
247
+ * pugPolicy: "reject"|"audit"|"allow",
248
+ * dollarBracePolicy: "reject"|"audit"|"allow",
249
+ * velocityDirectivePolicy: "reject"|"audit"|"allow",
250
+ * maxBytes: number,
251
+ * maxRuntimeMs: number,
252
+ *
253
+ * @example
254
+ * var clean = b.guardTemplate.validate("Hello world", { profile: "strict" });
255
+ * clean.ok; // → true
256
+ *
257
+ * var hostile = b.guardTemplate.validate("Hello {{7*7}}", { profile: "strict" });
258
+ * hostile.ok; // → false
259
+ * hostile.issues.some(function (i) { return i.kind === "jinja-expression"; }); // → true
260
+ */
198
261
  function validate(input, opts) {
199
262
  opts = _resolveOpts(opts);
200
263
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -203,6 +266,45 @@ function validate(input, opts) {
203
266
  return gateContract.aggregateIssues(_detectIssues(input, opts));
204
267
  }
205
268
 
269
+ /**
270
+ * @primitive b.guardTemplate.sanitize
271
+ * @signature b.guardTemplate.sanitize(input, opts)
272
+ * @since 0.7.13
273
+ * @status stable
274
+ * @compliance hipaa, pci-dss, gdpr, soc2
275
+ * @related b.guardTemplate.validate, b.guardTemplate.gate
276
+ *
277
+ * Pass-through-or-throw. Template-input strings cannot be safely
278
+ * repaired (stripping `{{` from `{{name}}` produces a different
279
+ * document and silently changes operator intent); this primitive
280
+ * returns the input unchanged when no `critical` or `high` issue
281
+ * fires, otherwise throws `GuardTemplateError` with the offending
282
+ * rule id (e.g. `template.jinja-expression`,
283
+ * `template.erb-expression`, `template.pug-interpolation`,
284
+ * `template.velocity-directive`). Operators that need a "best-
285
+ * effort cleanup" semantic should pre-escape the input through
286
+ * the rendering engine's own escape helper instead.
287
+ *
288
+ * @opts
289
+ * profile: "strict"|"balanced"|"permissive",
290
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
291
+ * jinjaPolicy: "reject"|"audit"|"allow",
292
+ * erbPolicy: "reject"|"audit"|"allow",
293
+ * pugPolicy: "reject"|"audit"|"allow",
294
+ * dollarBracePolicy: "reject"|"audit"|"allow",
295
+ * velocityDirectivePolicy: "reject"|"audit"|"allow",
296
+ * maxBytes: number,
297
+ *
298
+ * @example
299
+ * var safe = b.guardTemplate.sanitize("Hello world", { profile: "strict" });
300
+ * safe; // → "Hello world"
301
+ *
302
+ * try {
303
+ * b.guardTemplate.sanitize("Hello {{7*7}}", { profile: "strict" });
304
+ * } catch (e) {
305
+ * e.code; // → "template.jinja-expression"
306
+ * }
307
+ */
206
308
  function sanitize(input, opts) {
207
309
  opts = _resolveOpts(opts);
208
310
  if (typeof input !== "string") {
@@ -218,6 +320,46 @@ function sanitize(input, opts) {
218
320
  return input;
219
321
  }
220
322
 
323
+ /**
324
+ * @primitive b.guardTemplate.gate
325
+ * @signature b.guardTemplate.gate(opts)
326
+ * @since 0.7.13
327
+ * @status stable
328
+ * @compliance hipaa, pci-dss, gdpr, soc2
329
+ * @related b.guardTemplate.validate, b.guardTemplate.sanitize
330
+ *
331
+ * Build a `b.gateContract` gate that screens `ctx.identifier` (or
332
+ * `ctx.text`) before any template engine renders the input.
333
+ * Action chain: `serve` (no issues) → `audit-only` (warn-only) →
334
+ * `refuse` (any `critical` or `high`). No `sanitize` action —
335
+ * template input cannot be repaired. Compose into form handlers /
336
+ * comment renderers / model fields fed to Mustache / Handlebars /
337
+ * Liquid so operator-untrusted strings never reach the rendering
338
+ * engine carrying engine syntax.
339
+ *
340
+ * @opts
341
+ * profile: "strict"|"balanced"|"permissive",
342
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
343
+ * name: string, // override gate name in audit emissions
344
+ * jinjaPolicy: "reject"|"audit"|"allow",
345
+ * erbPolicy: "reject"|"audit"|"allow",
346
+ * pugPolicy: "reject"|"audit"|"allow",
347
+ * dollarBracePolicy: "reject"|"audit"|"allow",
348
+ * velocityDirectivePolicy: "reject"|"audit"|"allow",
349
+ * maxBytes: number,
350
+ *
351
+ * @example
352
+ * var gate = b.guardTemplate.gate({ profile: "strict" });
353
+ *
354
+ * gate({ identifier: "Hello {{7*7}}" }).then(function (rv) {
355
+ * rv.ok; // → false
356
+ * rv.action; // → "refuse"
357
+ * });
358
+ *
359
+ * gate({ identifier: "Hello world" }).then(function (rv) {
360
+ * rv.action; // → "serve"
361
+ * });
362
+ */
221
363
  function gate(opts) {
222
364
  opts = _resolveOpts(opts);
223
365
  return gateContract.buildGuardGate(
@@ -243,14 +385,83 @@ function gate(opts) {
243
385
  });
244
386
  }
245
387
 
388
+ /**
389
+ * @primitive b.guardTemplate.buildProfile
390
+ * @signature b.guardTemplate.buildProfile(opts)
391
+ * @since 0.7.13
392
+ * @status stable
393
+ * @related b.guardTemplate.gate, b.guardTemplate.compliancePosture
394
+ *
395
+ * Compose a derived guardTemplate profile from one or more named
396
+ * bases plus inline overrides. `opts.extends` is a profile name
397
+ * (`"strict"` / `"balanced"` / `"permissive"`) or an array of
398
+ * names; later entries shadow earlier ones. Inline `opts` keys win
399
+ * last. Used to keep operator-defined profiles traceable to a
400
+ * baseline rather than re-typing every key.
401
+ *
402
+ * @opts
403
+ * extends: string|string[], // base profile name(s) to compose
404
+ * ...: any guardTemplate key, // inline override of resolved keys
405
+ *
406
+ * @example
407
+ * var custom = b.guardTemplate.buildProfile({
408
+ * extends: "balanced",
409
+ * dollarBracePolicy: "reject",
410
+ * });
411
+ * custom.dollarBracePolicy; // → "reject"
412
+ * custom.jinjaPolicy; // → "reject"
413
+ */
246
414
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
247
415
 
416
+ /**
417
+ * @primitive b.guardTemplate.compliancePosture
418
+ * @signature b.guardTemplate.compliancePosture(name)
419
+ * @since 0.7.13
420
+ * @status stable
421
+ * @compliance hipaa, pci-dss, gdpr, soc2
422
+ * @related b.guardTemplate.gate, b.guardTemplate.buildProfile
423
+ *
424
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
425
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
426
+ * the posture object — the caller may mutate freely. Throws
427
+ * `GuardTemplateError("template.bad-posture")` on unknown name.
428
+ *
429
+ * @example
430
+ * var posture = b.guardTemplate.compliancePosture("hipaa");
431
+ * posture.jinjaPolicy; // → "reject"
432
+ * posture.forensicSnippetBytes; // → 512
433
+ */
248
434
  function compliancePosture(name) {
249
435
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
250
436
  _err, "template");
251
437
  }
252
438
 
253
439
  var _tplRulePacks = gateContract.makeRulePackLoader(GuardTemplateError, "template");
440
+ /**
441
+ * @primitive b.guardTemplate.loadRulePack
442
+ * @signature b.guardTemplate.loadRulePack(pack)
443
+ * @since 0.7.13
444
+ * @status stable
445
+ * @related b.guardTemplate.gate
446
+ *
447
+ * Register an operator-supplied rule pack with the guardTemplate
448
+ * registry. The pack is identified by `pack.id` (non-empty string)
449
+ * and stored for later inspection / dispatch by gates that opt in
450
+ * via `opts.rulePackId`. Returns the pack object unchanged on
451
+ * success; throws `GuardTemplateError("template.bad-opt")` when
452
+ * `pack` is missing or `pack.id` is not a non-empty string.
453
+ *
454
+ * @example
455
+ * var pack = b.guardTemplate.loadRulePack({
456
+ * id: "no-prototype-keys",
457
+ * rules: [
458
+ * { id: "proto-key", severity: "critical",
459
+ * detect: function (text) { return /__proto__|constructor/.test(text); },
460
+ * reason: "input references prototype-pollution sink" },
461
+ * ],
462
+ * });
463
+ * pack.id; // → "no-prototype-keys"
464
+ */
254
465
  var loadRulePack = _tplRulePacks.load;
255
466
 
256
467
  module.exports = {
package/lib/guard-time.js CHANGED
@@ -1,37 +1,40 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-time — RFC 3339 / ISO 8601 datetime identifier-safety primitive
4
- * (b.guardTime).
3
+ * @module b.guardTime
4
+ * @nav Guards
5
+ * @title Guard Time
5
6
  *
6
- * Validates user-supplied datetime strings destined for audit
7
- * timestamps, scheduling, retention windows, query ranges, and
8
- * cross-system event correlation. KIND="identifier" consumes
9
- * ctx.identifier (or ctx.timestamp).
7
+ * @intro
8
+ * ISO 8601 / RFC 3339 datetime identifier-safety guard. Validates
9
+ * user-supplied datetime strings destined for audit timestamps,
10
+ * scheduling, retention windows, query ranges, and cross-system
11
+ * event correlation. KIND="identifier" — the gate consumes
12
+ * `ctx.identifier` / `ctx.timestamp` / `ctx.time`.
10
13
  *
11
- * Threat catalog:
12
- * - Shape malformation not RFC 3339 / ISO 8601 datetime.
13
- * - Pre-epoch / far-futureyear before 1970 or after the
14
- * operator's far-future ceiling (default 9999); often a parsing
15
- * bug or sentinel-leak shape.
16
- * - Naive datetime (no offset) strict refuses; downstream
17
- * interpretation depends on local timezone, breaks
18
- * cross-region equality.
19
- * - Non-UTC offset strict accepts only `Z` / `+00:00`; balanced
20
- * accepts any offset; permissive allows naive too.
21
- * - Leap-second `60` in seconds field RFC 3339 §5.6 explicitly
22
- * valid (`23:59:60Z` is a real wall-clock time); most parsers
23
- * panic. Flagged-by-default with operator policy.
24
- * - Excessive fractional precision — RFC 3339 allows any digits
25
- * after the dot but every consuming system has a cap; flag > 9
26
- * fractional digits.
27
- * - Date-only / time-only — refused for full-datetime contexts.
28
- * - Whitespace / control / null-byte / BIDI universal refuse.
14
+ * Threat catalog: shape malformation (not RFC 3339 datetime
15
+ * grammar); pre-epoch / far-future (year before 1970 or after
16
+ * the operator's ceiling, default 9999 often a parsing bug or
17
+ * sentinel-leak shape); naive datetime with no offset (strict
18
+ * refuses downstream interpretation depends on local timezone,
19
+ * breaks cross-region equality); non-UTC offset (strict accepts
20
+ * only `Z` / `+00:00`; balanced accepts any offset; permissive
21
+ * allows naive too); leap-second `60` in seconds field (RFC 3339
22
+ * §5.6 explicitly valid, most parsers panic flagged-by-default
23
+ * with operator policy); excessive fractional precision (cap at
24
+ * 9 digits = nanosecond floor); date-only / time-only refused for
25
+ * full-datetime contexts; BIDI / zero-width / C0-control /
26
+ * null-byte universal-refuse.
29
27
  *
30
- * var rv = b.guardTime.validate("2026-05-05T12:34:56Z",
31
- * { profile: "strict" });
32
- * var safe = b.guardTime.sanitize("2026-05-05T12:34:56.123+05:30",
33
- * { profile: "balanced" });
34
- * var g = b.guardTime.gate({ profile: "strict" });
28
+ * Far-future / pre-epoch refusal is critical-severity by default:
29
+ * year-2038 wrap shapes, Y10K sentinels, and `0000-01-01` poison
30
+ * pills routinely leak through downstream parsers as silent
31
+ * `NaN` / `0` rows; the guard refuses at the boundary instead.
32
+ *
33
+ * Profiles: `strict` / `balanced` / `permissive`. Compliance
34
+ * postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`.
35
+ *
36
+ * @card
37
+ * ISO 8601 / RFC 3339 datetime identifier-safety guard.
35
38
  */
36
39
 
37
40
  var codepointClass = require("./codepoint-class");
@@ -327,6 +330,47 @@ function _detectIssues(input, opts) {
327
330
  return issues;
328
331
  }
329
332
 
333
+ /**
334
+ * @primitive b.guardTime.validate
335
+ * @signature b.guardTime.validate(input, opts?)
336
+ * @since 0.7.46
337
+ * @status stable
338
+ * @compliance hipaa, pci-dss, gdpr, soc2
339
+ * @related b.guardTime.sanitize, b.guardTime.gate
340
+ *
341
+ * Inspect a datetime string against the resolved profile and return
342
+ * `{ ok, issues }`. Each issue carries `kind` / `severity`
343
+ * (`critical` | `high` | `medium` | `low`) / `ruleId` / `snippet`.
344
+ * Non-string input returns a single `time.bad-input` issue rather
345
+ * than throwing — callers that prefer an exception use
346
+ * `b.guardTime.sanitize`.
347
+ *
348
+ * @opts
349
+ * profile: "strict"|"balanced"|"permissive",
350
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
351
+ * bidiPolicy: "reject"|"strip"|"audit"|"allow",
352
+ * controlPolicy: "reject"|"strip"|"allow",
353
+ * nullBytePolicy: "reject"|"strip"|"allow",
354
+ * zeroWidthPolicy: "reject"|"strip"|"allow",
355
+ * naiveDatetimePolicy: "reject"|"audit"|"allow",
356
+ * nonUtcOffsetPolicy: "reject"|"audit"|"allow",
357
+ * leapSecondPolicy: "reject"|"audit"|"allow",
358
+ * fractionalDigitsPolicy: "reject"|"truncate"|"audit"|"allow",
359
+ * dateOnlyPolicy: "reject"|"audit"|"allow",
360
+ * timeOnlyPolicy: "reject"|"audit"|"allow",
361
+ * minYear: number, // default 1970
362
+ * maxYear: number, // default 9999
363
+ * maxFractionalDigits: number, // default 9 (nanosecond)
364
+ * maxBytes: number, // default 64
365
+ *
366
+ * @example
367
+ * var rv = b.guardTime.validate("2026-05-05T12:34:56Z", { profile: "strict" });
368
+ * rv.ok; // → true
369
+ *
370
+ * var bad = b.guardTime.validate("1969-12-31T23:59:59Z", { profile: "strict" });
371
+ * bad.ok; // → false
372
+ * bad.issues[0].ruleId; // → "time.year-out-of-range"
373
+ */
330
374
  function validate(input, opts) {
331
375
  opts = _resolveOpts(opts);
332
376
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -343,6 +387,36 @@ function validate(input, opts) {
343
387
  return gateContract.aggregateIssues(_detectIssues(input, opts));
344
388
  }
345
389
 
390
+ /**
391
+ * @primitive b.guardTime.sanitize
392
+ * @signature b.guardTime.sanitize(input, opts?)
393
+ * @since 0.7.46
394
+ * @status stable
395
+ * @related b.guardTime.validate, b.guardTime.gate
396
+ *
397
+ * Normalize a datetime string in-place: replace the legacy
398
+ * space-separator with `T`, upper-case the trailing `Z` UTC
399
+ * marker. Throws `GuardTimeError` when any `critical` or `high`
400
+ * issue fires (year out of range, leap-second under reject,
401
+ * naive datetime under reject). Use `validate` to inspect issues
402
+ * without throwing.
403
+ *
404
+ * @opts
405
+ * profile: "strict"|"balanced"|"permissive",
406
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
407
+ * ...: same shape as b.guardTime.validate opts,
408
+ *
409
+ * @example
410
+ * var safe = b.guardTime.sanitize("2026-05-05 12:34:56z",
411
+ * { profile: "balanced" });
412
+ * safe; // → "2026-05-05T12:34:56Z"
413
+ *
414
+ * try {
415
+ * b.guardTime.sanitize("9999-12-31T23:59:60Z", { profile: "strict" });
416
+ * } catch (e) {
417
+ * e.code; // → "time.leap-second"
418
+ * }
419
+ */
346
420
  function sanitize(input, opts) {
347
421
  opts = _resolveOpts(opts);
348
422
  if (typeof input !== "string") {
@@ -360,6 +434,35 @@ function sanitize(input, opts) {
360
434
  return input.replace(/(\d) /, "$1T").replace(/z$/, "Z");
361
435
  }
362
436
 
437
+ /**
438
+ * @primitive b.guardTime.gate
439
+ * @signature b.guardTime.gate(opts?)
440
+ * @since 0.7.46
441
+ * @status stable
442
+ * @compliance hipaa, pci-dss, gdpr, soc2
443
+ * @related b.guardTime.validate, b.guardTime.sanitize, b.guardAll.gate
444
+ *
445
+ * Build an async gate `(ctx) -> { ok, action, issues }` consumable
446
+ * by `b.guardAll`, audit pipelines, scheduling primitives, and
447
+ * retention readers. The gate reads `ctx.identifier` (or
448
+ * `ctx.timestamp` / `ctx.time`), runs `validate`, and maps
449
+ * severity to action: zero issues `serve`; only low/medium
450
+ * `audit-only`; any high/critical `refuse`.
451
+ *
452
+ * @opts
453
+ * name: string, // gate label for audit / observability
454
+ * profile: "strict"|"balanced"|"permissive",
455
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
456
+ * ...: same shape as b.guardTime.validate opts,
457
+ *
458
+ * @example
459
+ * var g = b.guardTime.gate({ profile: "strict" });
460
+ * var rv = await g({ identifier: "2026-05-05T12:34:56Z" });
461
+ * rv.action; // → "serve"
462
+ *
463
+ * var bad = await g({ identifier: "2026-05-05 12:34:56" });
464
+ * bad.action; // → "refuse"
465
+ */
363
466
  function gate(opts) {
364
467
  opts = _resolveOpts(opts);
365
468
  return gateContract.buildGuardGate(
@@ -383,14 +486,77 @@ function gate(opts) {
383
486
  });
384
487
  }
385
488
 
489
+ /**
490
+ * @primitive b.guardTime.buildProfile
491
+ * @signature b.guardTime.buildProfile(opts)
492
+ * @since 0.7.46
493
+ * @status stable
494
+ * @related b.guardTime.gate, b.guardTime.compliancePosture
495
+ *
496
+ * Compose a derived profile from one or more named bases plus
497
+ * inline overrides. `opts.extends` is a profile name or array of
498
+ * names (later entries shadow earlier ones); inline keys win last.
499
+ *
500
+ * @opts
501
+ * extends: string|string[], // base profile name(s) to compose
502
+ * ...: any guard-time key, // inline override of resolved keys
503
+ *
504
+ * @example
505
+ * var custom = b.guardTime.buildProfile({
506
+ * extends: "balanced",
507
+ * leapSecondPolicy: "audit",
508
+ * maxYear: 2200,
509
+ * });
510
+ * custom.naiveDatetimePolicy; // → "audit"
511
+ * custom.maxYear; // → 2200
512
+ */
386
513
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
387
514
 
515
+ /**
516
+ * @primitive b.guardTime.compliancePosture
517
+ * @signature b.guardTime.compliancePosture(name)
518
+ * @since 0.7.46
519
+ * @status stable
520
+ * @compliance hipaa, pci-dss, gdpr, soc2
521
+ * @related b.guardTime.gate, b.guardTime.buildProfile
522
+ *
523
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
524
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
525
+ * the posture object — the caller may mutate freely. Throws
526
+ * `GuardTimeError("time.bad-posture")` on unknown name.
527
+ *
528
+ * @example
529
+ * var posture = b.guardTime.compliancePosture("hipaa");
530
+ * posture.naiveDatetimePolicy; // → "reject"
531
+ */
388
532
  function compliancePosture(name) {
389
533
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
390
534
  _err, "time");
391
535
  }
392
536
 
393
537
  var _timeRulePacks = gateContract.makeRulePackLoader(GuardTimeError, "time");
538
+ /**
539
+ * @primitive b.guardTime.loadRulePack
540
+ * @signature b.guardTime.loadRulePack(pack)
541
+ * @since 0.7.46
542
+ * @status stable
543
+ * @related b.guardTime.gate
544
+ *
545
+ * Register an operator-supplied rule pack with the guard-time
546
+ * registry. The pack is identified by `pack.id` (non-empty
547
+ * string) and stored for later inspection / dispatch by gates
548
+ * that opt in via `opts.rulePackId`. Throws
549
+ * `GuardTimeError("time.bad-opt")` when `pack` is missing or
550
+ * `pack.id` is not a non-empty string.
551
+ *
552
+ * @example
553
+ * var pack = b.guardTime.loadRulePack({
554
+ * id: "audit-window",
555
+ * minYear: 2020,
556
+ * maxYear: 2030,
557
+ * });
558
+ * pack.id; // → "audit-window"
559
+ */
394
560
  var loadRulePack = _timeRulePacks.load;
395
561
 
396
562
  module.exports = {