@blamejs/core 0.8.43 → 0.8.50

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
package/lib/guard-pdf.js CHANGED
@@ -1,31 +1,71 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-pdf — PDF identifier-safety primitive (b.guardPdf).
4
- *
5
- * Validates PDF inputs without vendoring a full parser. Operators
6
- * bring their own PDF library (pdf-lib, pdfjs-dist, vendored mupdf)
7
- * and feed structural metadata to the guard for policy enforcement.
8
- * KIND="metadata"consumes `ctx.metadata` shape: `{ bytes?,
9
- * hasJavaScript?, hasOpenAction?, hasEmbeddedFiles?, hasLaunchAction?,
10
- * isEncrypted?, pageCount?, embeddedFileCount? }`.
11
- *
12
- * Threat catalog:
13
- * - Magic-byte missing or wrong — `%PDF-` header check.
14
- * - JavaScript action — `/JS` / `/JavaScript` annotation triggers
15
- * RCE in vulnerable readers (CVE class — Adobe / Foxit / nitro).
16
- * - OpenAction trigger `/OpenAction` runs on document open;
17
- * when paired with JavaScript or LaunchAction it's a drive-by.
18
- * - LaunchAction `/Launch` action invokes external program;
19
- * refused at every profile.
20
- * - Embedded files `/EmbeddedFile` may carry executable payloads.
21
- * - Encrypted PDF refuse many AV / sandbox tools can't scan;
22
- * operators may want to refuse encrypted PDFs.
23
- * - Oversized bytes / page count / embedded-file count.
24
- * - Polyglot buffer carries non-PDF magic-byte signatures
25
- * (operator-supplied via `polyglotDetected: true`).
26
- *
27
- * var rv = b.guardPdf.validate(metadata, { profile: "strict" });
28
- * var g = b.guardPdf.gate({ profile: "strict" });
3
+ * @module b.guardPdf
4
+ * @nav Guards
5
+ * @title Guard Pdf
6
+ *
7
+ * @intro
8
+ * PDF content-safety guard refuses RCE-class PDF features without
9
+ * vendoring a parser. Operators bring their own PDF library
10
+ * (pdf-lib, pdfjs-dist, vendored mupdf) and feed structural metadata
11
+ * to the guard. `KIND="metadata"` — consumes `ctx.metadata` shape
12
+ * `{ bytes?, hasJavaScript?, hasOpenAction?, hasEmbeddedFiles?,
13
+ * hasLaunchAction?, isEncrypted?, pageCount?, embeddedFileCount?,
14
+ * polyglotDetected? }`.
15
+ *
16
+ * JavaScript exec refusal: `/JS` and `/JavaScript` annotations
17
+ * trigger RCE in vulnerable readers (the Adobe / Foxit / nitro CVE
18
+ * class). `metadata.hasJavaScript === true` is refused under every
19
+ * profile (`javascriptPolicy: "reject"` in strict / balanced /
20
+ * permissive). The framework refuses to negotiate on this — there
21
+ * is no audit-only path for executable JavaScript inside a PDF.
22
+ *
23
+ * Embedded files refusal: `/EmbeddedFile` entries may smuggle
24
+ * executable payloads inside an otherwise-benign-looking PDF.
25
+ * `strict` refuses any embedded file (`maxEmbeddedFileCount: 0`);
26
+ * `balanced` audits up to 10; `permissive` audits up to 100.
27
+ *
28
+ * OpenAction refusal: `/OpenAction` runs on document open. Standalone
29
+ * it's a navigation hint; paired with JavaScript or LaunchAction it's
30
+ * a drive-by trigger. `strict` refuses; `balanced` / `permissive`
31
+ * audit. JavaScript / LaunchAction are refused independently so the
32
+ * pairing can't slip through.
33
+ *
34
+ * GoTo / Launch refusal: `/Launch` actions invoke an external
35
+ * program (the historical "open this .exe attached to the PDF"
36
+ * class). Refused under every profile (`launchActionPolicy:
37
+ * "reject"`). The framework keeps the exec surface closed.
38
+ *
39
+ * Stream / object caps: `maxPageCount` (strict 500, balanced 5 000,
40
+ * permissive 50 000), `maxBytes` (strict 64 MiB, balanced 128 MiB,
41
+ * permissive 512 MiB), `maxEmbeddedFileCount` (strict 0, balanced
42
+ * 10, permissive 100). Operator-supplied — the operator's parser
43
+ * reports the structural counts; the guard refuses on excess.
44
+ *
45
+ * Magic-byte check: `%PDF-` header (5 bytes `25 50 44 46 2D`).
46
+ * Missing magic flagged under `strict` / `balanced` (the operator
47
+ * may be feeding non-PDF bytes through the wrong gate).
48
+ *
49
+ * Polyglot rejection: when the operator's parser flags the buffer
50
+ * as polyglot (`polyglotDetected: true`), the guard refuses under
51
+ * every profile (`polyglotPolicy: "reject"`).
52
+ *
53
+ * Encrypted-PDF posture: many AV / sandbox tools can't scan
54
+ * encrypted documents. `strict` refuses; `balanced` audits;
55
+ * `permissive` allows.
56
+ *
57
+ * Operator-feeds-metadata pattern: the gate trusts the metadata
58
+ * object the operator's parser reports. The framework's no-deps
59
+ * stance argues against shipping a vendored PDF parser; the
60
+ * operator's parser is the ground truth and the guard enforces the
61
+ * policy boundary.
62
+ *
63
+ * Profiles `strict` / `balanced` / `permissive` and compliance
64
+ * postures `hipaa` / `pci-dss` / `gdpr` / `soc2` overlay on the
65
+ * profile baseline.
66
+ *
67
+ * @card
68
+ * PDF content-safety guard — refuses RCE-class PDF features without vendoring a parser.
29
69
  */
30
70
 
31
71
  var lazyRequire = require("./lazy-require");
@@ -248,6 +288,57 @@ function _detectIssues(metadata, opts) {
248
288
  return issues;
249
289
  }
250
290
 
291
+ /**
292
+ * @primitive b.guardPdf.validate
293
+ * @signature b.guardPdf.validate(input, opts)
294
+ * @since 0.7.13
295
+ * @status stable
296
+ * @compliance hipaa, pci-dss, gdpr, soc2
297
+ * @related b.guardPdf.sanitize, b.guardPdf.gate, b.guardPdf.inspectMagic
298
+ *
299
+ * Inspect a PDF metadata bag `{ bytes?, hasJavaScript?, hasOpenAction?,
300
+ * hasLaunchAction?, hasEmbeddedFiles?, isEncrypted?, pageCount?,
301
+ * embeddedFileCount?, polyglotDetected? }` and return `{ ok, issues }`.
302
+ * Detected: `magic-missing` (no `%PDF-` header), `polyglot` (operator-
303
+ * flagged), `javascript-action` (RCE class — universally refused),
304
+ * `launch-action` (universally refused), `open-action` (drive-by
305
+ * class), `embedded-file` / `embedded-file-count`, `encrypted`,
306
+ * `page-count`, `pdf-cap`. Pure inspection — never mutates input or
307
+ * throws on hostile metadata.
308
+ *
309
+ * @opts
310
+ * profile: "strict"|"balanced"|"permissive",
311
+ * compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
312
+ * magicPolicy: "reject"|"audit"|"allow",
313
+ * javascriptPolicy: "reject"|"audit"|"allow", // strict refused — RCE class
314
+ * openActionPolicy: "reject"|"audit"|"allow",
315
+ * launchActionPolicy: "reject"|"audit"|"allow", // strict refused — RCE class
316
+ * embeddedFilePolicy: "reject"|"audit"|"allow",
317
+ * encryptedPolicy: "reject"|"audit"|"allow",
318
+ * polyglotPolicy: "reject"|"audit"|"allow",
319
+ * pageCountPolicy: "reject"|"audit"|"allow",
320
+ * embeddedFileCountPolicy: "reject"|"audit"|"allow",
321
+ * maxPageCount: number, // strict 500, balanced 5000, permissive 50000
322
+ * maxEmbeddedFileCount: number, // strict 0, balanced 10, permissive 100
323
+ * maxBytes: number, // strict 64 MiB, balanced 128 MiB, permissive 512 MiB
324
+ *
325
+ * @example
326
+ * var rv = b.guardPdf.validate({
327
+ * bytes: Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x37]),
328
+ * hasJavaScript: true, pageCount: 1,
329
+ * }, { profile: "strict" });
330
+ * rv.ok; // → false
331
+ * rv.issues[0].kind; // → "javascript-action"
332
+ * rv.issues[0].severity; // → "critical"
333
+ *
334
+ * // LaunchAction — universally refused.
335
+ * var launch = b.guardPdf.validate({
336
+ * bytes: Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x37]),
337
+ * hasLaunchAction: true,
338
+ * }, { profile: "permissive" });
339
+ * launch.issues.some(function (i) { return i.kind === "launch-action"; });
340
+ * // → true
341
+ */
251
342
  function validate(input, opts) {
252
343
  opts = _resolveOpts(opts);
253
344
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -258,6 +349,35 @@ function validate(input, opts) {
258
349
  return gateContract.aggregateIssues(_detectIssues(input, opts));
259
350
  }
260
351
 
352
+ /**
353
+ * @primitive b.guardPdf.sanitize
354
+ * @signature b.guardPdf.sanitize(input, opts)
355
+ * @since 0.7.13
356
+ * @status stable
357
+ * @related b.guardPdf.validate, b.guardPdf.gate
358
+ *
359
+ * Best-effort metadata pass-through. PDF byte sanitization
360
+ * (stripping JavaScript actions, embedded files, OpenActions) is
361
+ * the operator parser's responsibility — the guard cannot rewrite
362
+ * the byte stream without a vendored PDF library. `sanitize`
363
+ * validates the metadata against the active profile and re-throws
364
+ * `GuardPdfError` when any issue is `critical` or `high`. Returns
365
+ * the input unchanged when every issue is `warn` or below.
366
+ *
367
+ * @opts
368
+ * profile: "strict"|"balanced"|"permissive",
369
+ * compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
370
+ *
371
+ * @example
372
+ * try {
373
+ * b.guardPdf.sanitize({
374
+ * bytes: Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D]),
375
+ * hasJavaScript: true,
376
+ * }, { profile: "strict" });
377
+ * } catch (e) {
378
+ * e.code; // → "pdf.javascript-action"
379
+ * }
380
+ */
261
381
  function sanitize(input, opts) {
262
382
  opts = _resolveOpts(opts);
263
383
  if (!input || typeof input !== "object") {
@@ -273,6 +393,39 @@ function sanitize(input, opts) {
273
393
  return input;
274
394
  }
275
395
 
396
+ /**
397
+ * @primitive b.guardPdf.gate
398
+ * @signature b.guardPdf.gate(opts)
399
+ * @since 0.7.13
400
+ * @status stable
401
+ * @compliance hipaa, pci-dss, gdpr, soc2
402
+ * @related b.guardPdf.validate, b.guardPdf.sanitize, b.fileUpload, b.staticServe
403
+ *
404
+ * Build a `b.gateContract` gate suitable for `b.fileUpload({ contentSafety:
405
+ * { "application/pdf": gate } })` or `b.staticServe`. Operators pass
406
+ * `ctx.metadata` (the parser's structural report) plus the original
407
+ * `bytes`. Action chain: `serve` (no issues) → `audit-only`
408
+ * (warn-only) → `refuse` (any critical / high). No `sanitize` action
409
+ * — PDF byte streams can't be rewritten without a vendored parser.
410
+ *
411
+ * @opts
412
+ * profile: "strict"|"balanced"|"permissive",
413
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
414
+ * name: string,
415
+ * ...: any validate opt
416
+ *
417
+ * @example
418
+ * var pdfGate = b.guardPdf.gate({ profile: "strict" });
419
+ *
420
+ * var verdict = await pdfGate.check({
421
+ * metadata: {
422
+ * bytes: Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x37]),
423
+ * hasJavaScript: true, pageCount: 1,
424
+ * },
425
+ * });
426
+ * verdict.action; // → "refuse"
427
+ * verdict.issues[0].kind; // → "javascript-action"
428
+ */
276
429
  function gate(opts) {
277
430
  opts = _resolveOpts(opts);
278
431
  return gateContract.buildGuardGate(
@@ -296,17 +449,88 @@ function gate(opts) {
296
449
  });
297
450
  }
298
451
 
452
+ /**
453
+ * @primitive b.guardPdf.buildProfile
454
+ * @signature b.guardPdf.buildProfile(opts)
455
+ * @since 0.7.13
456
+ * @status stable
457
+ * @related b.guardPdf.compliancePosture, b.guardPdf.gate
458
+ *
459
+ * Resolve a named profile against the guard's PROFILES catalog and
460
+ * return the merged options bag. Throws
461
+ * `GuardPdfError("pdf.bad-profile")` on unknown name.
462
+ *
463
+ * @opts
464
+ * profile: "strict"|"balanced"|"permissive",
465
+ *
466
+ * @example
467
+ * var resolved = b.guardPdf.buildProfile({ profile: "strict" });
468
+ * resolved.javascriptPolicy; // → "reject"
469
+ * resolved.maxPageCount; // → 500
470
+ */
299
471
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
300
472
 
473
+ /**
474
+ * @primitive b.guardPdf.compliancePosture
475
+ * @signature b.guardPdf.compliancePosture(name)
476
+ * @since 0.7.13
477
+ * @status stable
478
+ * @compliance hipaa, pci-dss, gdpr, soc2
479
+ * @related b.guardPdf.gate, b.guardPdf.buildProfile
480
+ *
481
+ * Return the option overlay for a named compliance posture
482
+ * (`"hipaa"` / `"pci-dss"` / `"gdpr"` / `"soc2"`). Throws
483
+ * `GuardPdfError("pdf.bad-posture")` on unknown name.
484
+ *
485
+ * @example
486
+ * var posture = b.guardPdf.compliancePosture("hipaa");
487
+ * posture.javascriptPolicy; // → "reject"
488
+ * posture.forensicSnippetBytes; // → 512
489
+ */
301
490
  function compliancePosture(name) {
302
491
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
303
492
  _err, "pdf");
304
493
  }
305
494
 
306
495
  var _pdfRulePacks = gateContract.makeRulePackLoader(GuardPdfError, "pdf");
496
+ /**
497
+ * @primitive b.guardPdf.loadRulePack
498
+ * @signature b.guardPdf.loadRulePack(pack)
499
+ * @since 0.7.13
500
+ * @status stable
501
+ * @related b.guardPdf.gate
502
+ *
503
+ * Register an operator-supplied rule pack with the guard-pdf
504
+ * registry. Throws `GuardPdfError("pdf.bad-opt")` when `pack` is
505
+ * missing or `pack.id` is not a non-empty string.
506
+ *
507
+ * @example
508
+ * var pack = b.guardPdf.loadRulePack({
509
+ * id: "kb-2026-pdf",
510
+ * extraMaxPageCount: 200,
511
+ * });
512
+ * pack.id; // → "kb-2026-pdf"
513
+ */
307
514
  var loadRulePack = _pdfRulePacks.load;
308
515
 
309
- // Operator helper: confirm bytes carry the PDF magic header.
516
+ /**
517
+ * @primitive b.guardPdf.inspectMagic
518
+ * @signature b.guardPdf.inspectMagic(bytes)
519
+ * @since 0.7.13
520
+ * @status stable
521
+ * @related b.guardPdf.validate, b.guardPdf.gate
522
+ *
523
+ * Return `true` when `bytes` starts with the PDF magic header
524
+ * (`%PDF-`, the 5 bytes `25 50 44 46 2D`); `false` otherwise. Pure
525
+ * inspection — never mutates input or throws.
526
+ *
527
+ * @example
528
+ * var pdfBytes = Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x37]);
529
+ * b.guardPdf.inspectMagic(pdfBytes); // → true
530
+ *
531
+ * b.guardPdf.inspectMagic(Buffer.from([0x00, 0x01, 0x02]));
532
+ * // → false
533
+ */
310
534
  function inspectMagic(bytes) {
311
535
  return _hasPdfMagic(bytes);
312
536
  }
@@ -1,26 +1,48 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-regex — Regex pattern identifier-safety primitive
4
- * (b.guardRegex).
3
+ * @module b.guardRegex
4
+ * @nav Guards
5
+ * @title Guard Regex
5
6
  *
6
- * Validates user-supplied regex pattern strings for catastrophic-
7
- * backtracking (ReDoS) shapes BEFORE compilation. KIND="identifier" —
8
- * consumes ctx.identifier (or ctx.pattern).
7
+ * @intro
8
+ * Regex-pattern content-safety guard refuses user-supplied
9
+ * pattern strings that exhibit catastrophic-backtracking (ReDoS)
10
+ * shapes BEFORE the framework compiles them with `new RegExp(...)`.
11
+ * Operator-untrusted patterns flow into search filters, allow-lists,
12
+ * route matchers, and form validators; this primitive screens them
13
+ * so a hostile input can't pin a CPU at 100% inside the regex
14
+ * engine. KIND=`identifier`; the gate consumes `ctx.identifier`
15
+ * (or `ctx.pattern`) and refuses on hostile shapes. Composes with
16
+ * framework parsers (`b.safeJson` / `b.safeBuffer` / route helpers)
17
+ * so any operator-fed pattern hits the guard first.
9
18
  *
10
- * Threat catalog:
11
- * - Nested quantifiers `(a+)+`, `(a*)+`, `(.+)+`. The classic
12
- * ReDoS shape. CVE-2024-21538 (cross-spawn) and CVE-2022-25929
13
- * (chartjs-adapter-luxon) are recent prominent examples.
14
- * - Quantifier-after-grouped-quantifier — `(a|b)+\w*` style strings.
15
- * - Alternation overlap with quantifier `(\d|\d{2})*`.
16
- * - Bounded quantifiers with very large counts operator-tunable
17
- * via maxBoundedRepeat.
18
- * - Excessive pattern length defense against parser DoS.
19
- * - Lookbehind / lookahead with quantifiers inside.
20
- * - BIDI / null / control / zero-width universal refuse.
19
+ * Threat catalog: nested quantifiers (`(a+)+`, `(a*)+`, `(.+)+` —
20
+ * the canonical ReDoS class, e.g. CVE-2024-21538 cross-spawn and
21
+ * CVE-2022-25929 chartjs-adapter-luxon); alternation-with-
22
+ * quantifier (`(a|b)+`, `(\d|\d{2})*`) where alternation overlap
23
+ * amplifies search paths; quantifier-inside-lookaround
24
+ * (`(?=.*+)`, `(?!a*)`) catastrophic in some engines; bounded
25
+ * repetition with a large upper bound (gated by
26
+ * `maxBoundedRepeat`); per-pattern byte cap to defend against
27
+ * parser-stage DoS; BIDI override / zero-width / C0 control /
28
+ * null-byte universal refuse.
21
29
  *
22
- * var rv = b.guardRegex.validate("(a+)+b", { profile: "strict" });
23
- * var g = b.guardRegex.gate({ profile: "strict" });
30
+ * Profiles: `strict` / `balanced` / `permissive`. Compliance
31
+ * postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. Operators
32
+ * select via `{ profile: "strict" }` or
33
+ * `{ compliance: "hipaa" }`; postures overlay on top of the
34
+ * profile baseline. Nested-quantifier rejection holds at every
35
+ * profile — the catastrophic class is never an operator opt-in.
36
+ *
37
+ * Pattern strings can't be repaired safely — `sanitize` either
38
+ * passes through clean input or throws `GuardRegexError`; the
39
+ * gate returns `serve` / `audit-only` / `refuse` (no `sanitize`
40
+ * action). Detector regexes themselves are length-bounded by
41
+ * `maxPatternBytes` so the screener can't be DoS'd by its own
42
+ * inputs.
43
+ *
44
+ * @card
45
+ * Regex-pattern content-safety guard — refuses user-supplied pattern strings that exhibit catastrophic-backtracking (ReDoS) shapes BEFORE the framework compiles them with `new RegExp(...)`.
24
46
  */
25
47
 
26
48
  var codepointClass = require("./codepoint-class");
@@ -204,6 +226,45 @@ function _detectIssues(input, opts) {
204
226
  return issues;
205
227
  }
206
228
 
229
+ /**
230
+ * @primitive b.guardRegex.validate
231
+ * @signature b.guardRegex.validate(input, opts)
232
+ * @since 0.7.13
233
+ * @status stable
234
+ * @compliance hipaa, pci-dss, gdpr, soc2
235
+ * @related b.guardRegex.gate, b.guardRegex.sanitize
236
+ *
237
+ * Inspect a user-supplied regex pattern string and return an
238
+ * aggregated issue list. Pure inspection — never throws on hostile
239
+ * patterns; caller decides what to do with the issues. The `ok`
240
+ * flag is `true` only when zero `critical` / `high` issues fire.
241
+ * Throws `GuardRegexError("regex.bad-opt")` when a numeric opt is
242
+ * non-finite / negative (config-time mistake by the operator).
243
+ *
244
+ * @opts
245
+ * profile: "strict"|"balanced"|"permissive",
246
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
247
+ * bidiPolicy: "reject"|"audit"|"allow",
248
+ * controlPolicy: "reject"|"audit"|"allow",
249
+ * nullBytePolicy: "reject"|"audit"|"allow",
250
+ * zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
251
+ * nestedQuantPolicy: "reject"|"audit"|"allow",
252
+ * alternationQuantPolicy: "reject"|"audit"|"allow",
253
+ * boundedRepeatPolicy: "reject"|"audit"|"allow",
254
+ * lookaroundQuantPolicy: "reject"|"audit"|"allow",
255
+ * maxBoundedRepeat: number,
256
+ * maxPatternBytes: number,
257
+ * maxBytes: number,
258
+ * maxRuntimeMs: number,
259
+ *
260
+ * @example
261
+ * var clean = b.guardRegex.validate("^[a-z]+$", { profile: "strict" });
262
+ * clean.ok; // → true
263
+ *
264
+ * var hostile = b.guardRegex.validate("(a+)+b", { profile: "strict" });
265
+ * hostile.ok; // → false
266
+ * hostile.issues.some(function (i) { return i.kind === "nested-quantifier"; }); // → true
267
+ */
207
268
  function validate(input, opts) {
208
269
  opts = _resolveOpts(opts);
209
270
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -212,6 +273,44 @@ function validate(input, opts) {
212
273
  return gateContract.aggregateIssues(_detectIssues(input, opts));
213
274
  }
214
275
 
276
+ /**
277
+ * @primitive b.guardRegex.sanitize
278
+ * @signature b.guardRegex.sanitize(input, opts)
279
+ * @since 0.7.13
280
+ * @status stable
281
+ * @compliance hipaa, pci-dss, gdpr, soc2
282
+ * @related b.guardRegex.validate, b.guardRegex.gate
283
+ *
284
+ * Pass-through-or-throw. Regex patterns cannot be safely repaired
285
+ * (stripping a `+` from a quantifier silently changes match
286
+ * semantics); this primitive returns the input unchanged when no
287
+ * `critical` or `high` issue fires, otherwise throws
288
+ * `GuardRegexError` with the offending rule id (e.g.
289
+ * `regex.nested-quantifier`, `regex.lookaround-quantifier`,
290
+ * `regex.bounded-repeat-cap`). Operators that need a "best-effort
291
+ * cleanup" semantic should reject the input at the boundary
292
+ * instead.
293
+ *
294
+ * @opts
295
+ * profile: "strict"|"balanced"|"permissive",
296
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
297
+ * nestedQuantPolicy: "reject"|"audit"|"allow",
298
+ * alternationQuantPolicy: "reject"|"audit"|"allow",
299
+ * boundedRepeatPolicy: "reject"|"audit"|"allow",
300
+ * lookaroundQuantPolicy: "reject"|"audit"|"allow",
301
+ * maxBoundedRepeat: number,
302
+ * maxPatternBytes: number,
303
+ *
304
+ * @example
305
+ * var safe = b.guardRegex.sanitize("^[a-z]+$", { profile: "strict" });
306
+ * safe; // → "^[a-z]+$"
307
+ *
308
+ * try {
309
+ * b.guardRegex.sanitize("(a+)+b", { profile: "strict" });
310
+ * } catch (e) {
311
+ * e.code; // → "regex.nested-quantifier"
312
+ * }
313
+ */
215
314
  function sanitize(input, opts) {
216
315
  opts = _resolveOpts(opts);
217
316
  if (typeof input !== "string") {
@@ -227,6 +326,45 @@ function sanitize(input, opts) {
227
326
  return input;
228
327
  }
229
328
 
329
+ /**
330
+ * @primitive b.guardRegex.gate
331
+ * @signature b.guardRegex.gate(opts)
332
+ * @since 0.7.13
333
+ * @status stable
334
+ * @compliance hipaa, pci-dss, gdpr, soc2
335
+ * @related b.guardRegex.validate, b.guardRegex.sanitize
336
+ *
337
+ * Build a `b.gateContract` gate that screens `ctx.identifier` (or
338
+ * `ctx.pattern`) before any compilation step. Action chain:
339
+ * `serve` (no issues) → `audit-only` (warn-only) → `refuse` (any
340
+ * `critical` or `high`). No `sanitize` action — pattern strings
341
+ * cannot be repaired. Compose into framework parsers / form
342
+ * validators / route matchers so operator-fed patterns hit the
343
+ * guard before reaching `new RegExp()`.
344
+ *
345
+ * @opts
346
+ * profile: "strict"|"balanced"|"permissive",
347
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
348
+ * name: string, // override gate name in audit emissions
349
+ * nestedQuantPolicy: "reject"|"audit"|"allow",
350
+ * alternationQuantPolicy: "reject"|"audit"|"allow",
351
+ * boundedRepeatPolicy: "reject"|"audit"|"allow",
352
+ * lookaroundQuantPolicy: "reject"|"audit"|"allow",
353
+ * maxBoundedRepeat: number,
354
+ * maxPatternBytes: number,
355
+ *
356
+ * @example
357
+ * var gate = b.guardRegex.gate({ profile: "strict" });
358
+ *
359
+ * gate({ identifier: "(a+)+b" }).then(function (rv) {
360
+ * rv.ok; // → false
361
+ * rv.action; // → "refuse"
362
+ * });
363
+ *
364
+ * gate({ identifier: "^[a-z]+$" }).then(function (rv) {
365
+ * rv.action; // → "serve"
366
+ * });
367
+ */
230
368
  function gate(opts) {
231
369
  opts = _resolveOpts(opts);
232
370
  return gateContract.buildGuardGate(
@@ -252,14 +390,84 @@ function gate(opts) {
252
390
  });
253
391
  }
254
392
 
393
+ /**
394
+ * @primitive b.guardRegex.buildProfile
395
+ * @signature b.guardRegex.buildProfile(opts)
396
+ * @since 0.7.13
397
+ * @status stable
398
+ * @related b.guardRegex.gate, b.guardRegex.compliancePosture
399
+ *
400
+ * Compose a derived guardRegex profile from one or more named
401
+ * bases plus inline overrides. `opts.extends` is a profile name
402
+ * (`"strict"` / `"balanced"` / `"permissive"`) or an array of
403
+ * names; later entries shadow earlier ones. Inline `opts` keys win
404
+ * last. Used to keep operator-defined profiles traceable to a
405
+ * baseline rather than re-typing every key.
406
+ *
407
+ * @opts
408
+ * extends: string|string[], // base profile name(s) to compose
409
+ * ...: any guardRegex key, // inline override of resolved keys
410
+ *
411
+ * @example
412
+ * var custom = b.guardRegex.buildProfile({
413
+ * extends: "balanced",
414
+ * maxBoundedRepeat: 50,
415
+ * boundedRepeatPolicy: "reject",
416
+ * });
417
+ * custom.maxBoundedRepeat; // → 50
418
+ * custom.nestedQuantPolicy; // → "reject"
419
+ */
255
420
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
256
421
 
422
+ /**
423
+ * @primitive b.guardRegex.compliancePosture
424
+ * @signature b.guardRegex.compliancePosture(name)
425
+ * @since 0.7.13
426
+ * @status stable
427
+ * @compliance hipaa, pci-dss, gdpr, soc2
428
+ * @related b.guardRegex.gate, b.guardRegex.buildProfile
429
+ *
430
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
431
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
432
+ * the posture object — the caller may mutate freely. Throws
433
+ * `GuardRegexError("regex.bad-posture")` on unknown name.
434
+ *
435
+ * @example
436
+ * var posture = b.guardRegex.compliancePosture("hipaa");
437
+ * posture.nestedQuantPolicy; // → "reject"
438
+ * posture.forensicSnippetBytes; // → 256
439
+ */
257
440
  function compliancePosture(name) {
258
441
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
259
442
  _err, "regex");
260
443
  }
261
444
 
262
445
  var _regexRulePacks = gateContract.makeRulePackLoader(GuardRegexError, "regex");
446
+ /**
447
+ * @primitive b.guardRegex.loadRulePack
448
+ * @signature b.guardRegex.loadRulePack(pack)
449
+ * @since 0.7.13
450
+ * @status stable
451
+ * @related b.guardRegex.gate
452
+ *
453
+ * Register an operator-supplied rule pack with the guardRegex
454
+ * registry. The pack is identified by `pack.id` (non-empty string)
455
+ * and stored for later inspection / dispatch by gates that opt in
456
+ * via `opts.rulePackId`. Returns the pack object unchanged on
457
+ * success; throws `GuardRegexError("regex.bad-opt")` when `pack`
458
+ * is missing or `pack.id` is not a non-empty string.
459
+ *
460
+ * @example
461
+ * var pack = b.guardRegex.loadRulePack({
462
+ * id: "no-empty-alternation",
463
+ * rules: [
464
+ * { id: "empty-alt", severity: "high",
465
+ * detect: function (pattern) { return /\(\|/.test(pattern); },
466
+ * reason: "alternation with empty branch" },
467
+ * ],
468
+ * });
469
+ * pack.id; // → "no-empty-alternation"
470
+ */
263
471
  var loadRulePack = _regexRulePacks.load;
264
472
 
265
473
  module.exports = {