@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-json.js CHANGED
@@ -1,70 +1,73 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-json — JSON content-safety primitive (b.guardJson).
3
+ * @module b.guardJson
4
+ * @nav Guards
5
+ * @title Guard Json
4
6
  *
5
- * Threat catalog grounded in current research (multiple 2025–2026 CVEs
6
- * + ongoing prototype-pollution vulnerability series):
7
- * - CVE-2025-55182 React/Next.js Server Functions deserialization RCE
8
- * - CVE-2025-57820 / CVE-2026-30226 Svelte devalue prototype pollution
9
- * - CVE-2026-35209 defu prototype pollution
10
- * - CVE-2026-28794 @orpc/client prototype pollution via deserialization
11
- * - CVE-2025-13465 Lodash prototype-chain path traversal
12
- * - CVE-2025-25014 Kibana prototype pollution → RCE
13
- * - CVE-2024-38984 json-override prototype pollution
14
- * - CVE-2022-42743 deep-parse-json prototype pollution
15
- * - GHSA-9c47-m6qq-7p4h JSON5 prototype pollution via parse method
7
+ * @intro
8
+ * JSON content-safety guard defends against the threat catalog
9
+ * operators face when accepting JSON sourced from user input.
10
+ * `b.safeJson.parse` enforces baseline depth + size caps; this
11
+ * module layers prototype-pollution / depth-bomb / key-count /
12
+ * duplicate-key / unicode threat detection on top.
16
13
  *
17
- * var rv = b.guardJson.validate(input, { profile: "strict" });
18
- * var safe = b.guardJson.parse(input, { profile: "strict" });
19
- * var g = b.guardJson.gate({ profile: "strict" });
14
+ * Prototype-pollution defense: keys `__proto__` / `constructor` /
15
+ * `prototype` anywhere in the tree are detected at the SOURCE level
16
+ * (before any parser sees them). After `JSON.parse` normalizes the
17
+ * input, `__proto__` routes through the prototype setter and is
18
+ * invisible to `Object.keys()`, so a post-parse tree walk misses
19
+ * the pollution shape — the source-text scan catches it. CVE
20
+ * coverage spans the 2025-2026 deserialization + prototype-
21
+ * pollution wave: CVE-2025-55182 React Server Functions RCE,
22
+ * CVE-2025-57820 / CVE-2026-30226 Svelte devalue, CVE-2026-35209
23
+ * defu, CVE-2026-28794 @orpc/client, CVE-2025-13465 Lodash path
24
+ * traversal, CVE-2025-25014 Kibana, CVE-2024-38984 json-override,
25
+ * CVE-2022-42743 deep-parse-json, GHSA-9c47-m6qq-7p4h JSON5.
20
26
  *
21
- * Threat catalog covered:
27
+ * Depth + breadth caps: `maxDepth` / `maxKeysPerObject` /
28
+ * `maxArrayLength` / `maxStringLength` / `maxTotalNodes` refuse
29
+ * key-count bombs (10^6 keys per object) and stack-exhaustion
30
+ * nesting attacks under strict.
22
31
  *
23
- * 1. Prototype pollution `__proto__` / `constructor` / `prototype`
24
- * keys anywhere in the tree. Refused regardless of profile under
25
- * strict; balanced strips; permissive audits.
32
+ * Duplicate-key smuggling: RFC 8259 says keys SHOULD be unique;
33
+ * `JSON.parse` silently last-wins. A two-validator pipeline that
34
+ * inspects the first occurrence and trusts the parser's last-wins
35
+ * value is the smuggling shape; this guard rescans the source for
36
+ * identical quoted keys at the same `{ ... }` nesting level.
26
37
  *
27
- * 2. Array-index pollution non-numeric keys ON array values
28
- * (Svelte devalue CVE-2025-57820 class). When operator JSON
29
- * structurally has arrays, keys like `push` / `toString` injected
30
- * via reviver/parser quirks pollute Array.prototype.
38
+ * JSON5 / JSONC quirks (single-line `//` + block C-style
39
+ * comments, trailing commas, NaN / Infinity / -Infinity, hex
40
+ * literals, single-quoted keys) RFC 8259 forbids these but
41
+ * lenient parsers accept; the guard flags them at the source so
42
+ * operators can refuse hostile inputs regardless of which parser
43
+ * is downstream.
31
44
  *
32
- * 3. Depth bombs nested arrays/objects past safe stack. Refused
33
- * at the configured cap.
45
+ * Numeric precision loss: integers above `Number.MAX_SAFE_INTEGER`
46
+ * (~9.007 x 10^15, 16 digits) silently lose precision when round-
47
+ * tripped through Number. Detected via raw-text scan for digit
48
+ * runs of 17+ characters.
34
49
  *
35
- * 4. Breadth / key-count bombs single object with massive key count
36
- * (10⁶ keys CPU + memory blow-up).
50
+ * BOM injection (leading or mid-stream U+FEFF) and bidi / null /
51
+ * control / zero-width character threats route through the shared
52
+ * lib/codepoint-class catalog — the same detector backing the
53
+ * guard-csv / guard-html / guard-svg families.
37
54
  *
38
- * 5. Duplicate keys RFC 8259 says keys SHOULD be unique; in
39
- * practice JSON.parse silently last-wins, which lets attackers
40
- * smuggle duplicate-key payloads past validation that ran on the
41
- * first occurrence (Bishop Fox JSON-interoperability research).
55
+ * Top-level-key allowlist: when the operator opts in via
56
+ * `topLevelKeyAllowlist: ["alpha", "beta"]`, every other top-level
57
+ * key triggers a refused-shape issue. Useful for HTTP body schemas
58
+ * where unexpected keys signal malformed or hostile input.
42
59
  *
43
- * 6. NaN / Infinity / -Infinity — RFC 8259 forbids; JSON5 / lenient
44
- * parsers accept. Refused under strict.
60
+ * Profiles: `strict` / `balanced` / `permissive`. Compliance
61
+ * postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. Operators select
62
+ * via `{ profile: "strict" }` or `{ compliance: "hipaa" }`;
63
+ * postures overlay on top of the profile baseline.
45
64
  *
46
- * 7. Comments (single-line // and block-style) — RFC 8259 forbids;
47
- * JSON5 / JSONC accept. Refused under strict.
65
+ * Source files MUST be pure ASCII; threat-detection regexes
66
+ * compose programmatically via lib/codepoint-class so the source
67
+ * never embeds the attack characters themselves.
48
68
  *
49
- * 8. Trailing commas — JSON5 accepts; refused under strict.
50
- *
51
- * 9. BOM injection — leading or mid-stream U+FEFF.
52
- *
53
- * 10. Bidi / null / control chars in string values — same codepoint
54
- * catalog as guard-csv/html/svg via lib/codepoint-class.
55
- *
56
- * 11. Numeric precision loss — values above
57
- * `Number.MAX_SAFE_INTEGER` silently lose precision when round-
58
- * tripped through Number. Detected via raw-text scan for digit
59
- * runs longer than safe-int width.
60
- *
61
- * 12. Total size cap (anti-DoS).
62
- *
63
- * 13. Top-level-key allowlist — strict profile requires the operator
64
- * to declare allowed top-level keys; balanced/permissive skip.
65
- *
66
- * Source files MUST be pure ASCII per the codepoint-table programmatic
67
- * regex pattern; threat-detection regexes use lib/codepoint-class.
69
+ * @card
70
+ * JSON content-safety guard — defends against the threat catalog operators face when accepting JSON sourced from user input.
68
71
  */
69
72
 
70
73
  var codepointClass = require("./codepoint-class");
@@ -558,6 +561,61 @@ function _stripPollutionTree(value, opts, depth) {
558
561
 
559
562
  // ---- Public surface ----
560
563
 
564
+ /**
565
+ * @primitive b.guardJson.validate
566
+ * @signature b.guardJson.validate(input, opts?)
567
+ * @since 0.7.13
568
+ * @status stable
569
+ * @compliance hipaa, pci-dss, gdpr, soc2
570
+ * @related b.guardJson.parse, b.guardJson.gate, b.safeJson.parse
571
+ *
572
+ * Inspect `input` (string of JSON source) for the full guard-json
573
+ * threat catalog without committing to a parsed value. Returns
574
+ * `{ ok, issues, severities }` where `issues` is the aggregated
575
+ * detector output — every prototype-pollution key, depth/breadth
576
+ * cap hit, duplicate-key smuggle, JSON5-quirk match, BOM placement,
577
+ * unicode threat, and numeric-precision-loss candidate is reported
578
+ * with `kind` / `severity` / `ruleId` / `snippet`. Profile-driven
579
+ * (`strict` / `balanced` / `permissive`) and posture-driven
580
+ * (`hipaa` / `pci-dss` / `gdpr` / `soc2`).
581
+ *
582
+ * Detection runs in two passes: a raw-source scan (BOM placement,
583
+ * comments, NaN/Infinity, trailing commas, JSON5 quirks, source-
584
+ * level prototype-pollution keys, codepoint-class threats) followed
585
+ * by a parsed-tree walk (depth / breadth / array-length / string-
586
+ * length / node-count caps, duplicate-key rescan).
587
+ *
588
+ * @opts
589
+ * profile: "strict"|"balanced"|"permissive",
590
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
591
+ * pollutionPolicy: "reject"|"strip"|"audit"|"allow",
592
+ * duplicateKeyPolicy: "reject"|"audit"|"allow",
593
+ * nanInfinityPolicy: "reject"|"audit"|"allow",
594
+ * commentPolicy: "reject"|"audit"|"allow",
595
+ * trailingCommaPolicy: "reject"|"audit"|"allow",
596
+ * json5SyntaxPolicy: "reject"|"audit"|"allow",
597
+ * bomPolicy: "reject"|"strip"|"allow",
598
+ * bidiPolicy: "reject"|"strip"|"audit"|"allow",
599
+ * controlPolicy: "reject"|"strip"|"allow",
600
+ * nullBytePolicy: "reject"|"strip"|"allow",
601
+ * zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
602
+ * numericPrecisionPolicy: "reject"|"audit"|"allow",
603
+ * requireTopLevelKeyAllowlist: boolean,
604
+ * topLevelKeyAllowlist: string[]|null,
605
+ * maxBytes: number, // total source byte cap
606
+ * maxDepth: number, // recursion depth cap
607
+ * maxKeysPerObject: number, // breadth cap per object
608
+ * maxArrayLength: number, // array length cap
609
+ * maxStringLength: number, // string length cap
610
+ * maxTotalNodes: number, // total node count cap
611
+ *
612
+ * @example
613
+ * var rv = b.guardJson.validate('{"__proto__":{"polluted":true}}', {
614
+ * profile: "strict",
615
+ * });
616
+ * rv.ok; // → false
617
+ * rv.issues.some(function (i) { return i.kind === "prototype-pollution-key"; }); // → true
618
+ */
561
619
  function validate(input, opts) {
562
620
  opts = _resolveOpts(opts);
563
621
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -574,6 +632,48 @@ function validate(input, opts) {
574
632
  return gateContract.aggregateIssues(_detectIssues(input, opts));
575
633
  }
576
634
 
635
+ /**
636
+ * @primitive b.guardJson.parse
637
+ * @signature b.guardJson.parse(input, opts?)
638
+ * @since 0.7.13
639
+ * @status stable
640
+ * @related b.guardJson.validate, b.guardJson.gate, b.safeJson.parse
641
+ *
642
+ * Parse `input` (string of JSON source) into a JavaScript value
643
+ * after the guard-json threat catalog clears. Refuses on prototype-
644
+ * pollution keys when `pollutionPolicy === "reject"`, refuses on any
645
+ * critical raw-source pre-parse threat, refuses on parse failure,
646
+ * and otherwise routes through `b.safeJson.parse` with the configured
647
+ * `maxBytes` / `maxDepth` caps. Strip policies (`bomPolicy: "strip"`,
648
+ * `controlPolicy: "strip"`, `zeroWidthPolicy: "strip"`) silently
649
+ * remove the offending characters from the source before parsing.
650
+ *
651
+ * Pollution keys (`__proto__` / `constructor` / `prototype`) are
652
+ * normally invisible to `Object.keys()` after `JSON.parse` because
653
+ * they route through prototype setters; the parse path passes
654
+ * `allowProto: true` to `b.safeJson.parse` only when policy is
655
+ * `audit` / `allow`, ensuring strip / reject paths produce a tree
656
+ * with no pollution-key residue.
657
+ *
658
+ * Throws `GuardJsonError` on refusal — the error code matches the
659
+ * triggering rule (`json.prototype-pollution`, `json.parse`, etc.).
660
+ *
661
+ * @opts
662
+ * profile: "strict"|"balanced"|"permissive",
663
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
664
+ * pollutionPolicy: "reject"|"strip"|"audit"|"allow",
665
+ * bomPolicy: "reject"|"strip"|"allow",
666
+ * controlPolicy: "reject"|"strip"|"allow",
667
+ * zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
668
+ * maxBytes: number, maxDepth: number,
669
+ *
670
+ * @example
671
+ * var safe = b.guardJson.parse('{"name":"alice","age":30}', {
672
+ * profile: "strict",
673
+ * });
674
+ * safe.name; // → "alice"
675
+ * safe.age; // → 30
676
+ */
577
677
  function parse(input, opts) {
578
678
  opts = _resolveOpts(opts);
579
679
  if (typeof input !== "string") {
@@ -643,6 +743,42 @@ function _policyKeyForRuleId(ruleId) {
643
743
  return map[ruleId] || null;
644
744
  }
645
745
 
746
+ /**
747
+ * @primitive b.guardJson.gate
748
+ * @signature b.guardJson.gate(opts?)
749
+ * @since 0.7.13
750
+ * @status stable
751
+ * @compliance hipaa, pci-dss, gdpr, soc2
752
+ * @related b.guardJson.validate, b.guardJson.parse, b.staticServe.create, b.fileUpload.create
753
+ *
754
+ * Build a `b.gateContract` gate suitable for plugging into
755
+ * `b.staticServe({ contentSafety: { ".json": gate } })`,
756
+ * `b.fileUpload({ contentSafety: { "application/json": gate } })`,
757
+ * or any host primitive that consumes the gate-contract shape.
758
+ * Action chain on validation: `serve` (no issues) → `audit-only`
759
+ * (warn-only issues) → `sanitize` (high/critical but every reject-
760
+ * policy is off — re-parse + re-emit a cleaned tree via
761
+ * `JSON.stringify`) → `refuse` (critical/high under any reject
762
+ * policy, or sanitize threw).
763
+ *
764
+ * Sanitize-eligibility requires every policy in the reject set
765
+ * (`pollutionPolicy` / `duplicateKeyPolicy` / `nanInfinityPolicy` /
766
+ * `commentPolicy` / `trailingCommaPolicy` / `json5SyntaxPolicy` /
767
+ * `bomPolicy` / `bidiPolicy` / `controlPolicy` / `nullBytePolicy`)
768
+ * to be off; under strict every one is `"reject"` so the gate jumps
769
+ * straight from `audit-only` to `refuse`.
770
+ *
771
+ * @opts
772
+ * profile: "strict"|"balanced"|"permissive",
773
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
774
+ * name: string, // gate identity for audit / observability
775
+ *
776
+ * @example
777
+ * var jsonGate = b.guardJson.gate({ profile: "strict" });
778
+ * var hostile = Buffer.from('{"__proto__":{"x":1}}', "utf8");
779
+ * var verdict = await jsonGate.check({ bytes: hostile });
780
+ * verdict.action; // → "refuse"
781
+ */
646
782
  function gate(opts) {
647
783
  opts = _resolveOpts(opts);
648
784
  return gateContract.buildGuardGate(
@@ -684,13 +820,83 @@ function gate(opts) {
684
820
  });
685
821
  }
686
822
 
823
+ /**
824
+ * @primitive b.guardJson.buildProfile
825
+ * @signature b.guardJson.buildProfile(opts)
826
+ * @since 0.7.13
827
+ * @status stable
828
+ * @related b.guardJson.gate, b.guardJson.compliancePosture
829
+ *
830
+ * Compose a derived profile from one or more named bases plus
831
+ * inline overrides. `opts.extends` is a profile name (`"strict"` /
832
+ * `"balanced"` / `"permissive"`) or an array of names; later entries
833
+ * shadow earlier ones. Inline `opts` keys win last. Used to keep
834
+ * operator-defined profiles traceable to a baseline rather than re-
835
+ * typing every key.
836
+ *
837
+ * @opts
838
+ * extends: string|string[], // base profile name(s) to compose
839
+ * ...: any guard-json key, // inline override of resolved keys
840
+ *
841
+ * @example
842
+ * var custom = b.guardJson.buildProfile({
843
+ * extends: "balanced",
844
+ * pollutionPolicy: "reject",
845
+ * maxDepth: 16,
846
+ * });
847
+ * custom.pollutionPolicy; // → "reject"
848
+ * custom.maxDepth; // → 16
849
+ */
687
850
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
688
851
 
852
+ /**
853
+ * @primitive b.guardJson.compliancePosture
854
+ * @signature b.guardJson.compliancePosture(name)
855
+ * @since 0.7.13
856
+ * @status stable
857
+ * @compliance hipaa, pci-dss, gdpr, soc2
858
+ * @related b.guardJson.gate, b.guardJson.buildProfile
859
+ *
860
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
861
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of the
862
+ * posture object — the caller may mutate freely. Throws
863
+ * `GuardJsonError("json.bad-posture")` on unknown name.
864
+ *
865
+ * @example
866
+ * var posture = b.guardJson.compliancePosture("hipaa");
867
+ * posture.pollutionPolicy; // → "reject"
868
+ * posture.forensicSnippetBytes; // → 256
869
+ */
689
870
  function compliancePosture(name) {
690
871
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "json");
691
872
  }
692
873
 
693
874
  var _jsonRulePacks = gateContract.makeRulePackLoader(GuardJsonError, "json");
875
+ /**
876
+ * @primitive b.guardJson.loadRulePack
877
+ * @signature b.guardJson.loadRulePack(pack)
878
+ * @since 0.7.13
879
+ * @status stable
880
+ * @related b.guardJson.gate
881
+ *
882
+ * Register an operator-supplied rule pack with the guard-json
883
+ * registry. The pack is identified by `pack.id` (non-empty string)
884
+ * and stored for later inspection / dispatch by gates that opt in
885
+ * via `opts.rulePackId`. Returns the pack object unchanged on
886
+ * success; throws `GuardJsonError("json.bad-opt")` when `pack` is
887
+ * missing or `pack.id` is not a non-empty string.
888
+ *
889
+ * @example
890
+ * var pack = b.guardJson.loadRulePack({
891
+ * id: "tenant-keys",
892
+ * rules: [
893
+ * { id: "tenant-id-shape", severity: "high",
894
+ * detect: function (tree) { return !tree || typeof tree.tenantId !== "string"; },
895
+ * reason: "tenantId must be a string at top level" },
896
+ * ],
897
+ * });
898
+ * pack.id; // → "tenant-keys"
899
+ */
694
900
  var loadRulePack = _jsonRulePacks.load;
695
901
 
696
902
  module.exports = {
@@ -1,30 +1,53 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-jsonpath — JSONPath identifier-safety primitive
4
- * (b.guardJsonpath).
3
+ * @module b.guardJsonpath
4
+ * @nav Guards
5
+ * @title Guard Jsonpath
5
6
  *
6
- * Validates user-supplied JSONPath strings (RFC 9535) before they're
7
- * handed to a JSONPath evaluator. Many JSONPath libraries (notably
8
- * the original Stefan Goessner implementation and several JS forks)
9
- * route filter / script expressions through dynamic-code execution,
10
- * turning a query path into an RCE primitive. KIND="identifier" —
11
- * consumes ctx.identifier (or ctx.jsonpath).
7
+ * @intro
8
+ * JSONPath content-safety guard refuses user-supplied JSONPath
9
+ * query strings that exhibit dynamic-code-execution shapes BEFORE
10
+ * they reach a JSONPath evaluator. Many JSONPath implementations
11
+ * (the original Stefan Goessner reference and several JS forks)
12
+ * route filter / script expressions through `eval`-class
13
+ * dispatch, turning a query path into an RCE primitive; this
14
+ * primitive screens the path so a hostile query can't escape into
15
+ * code execution. KIND=`identifier`; the gate consumes
16
+ * `ctx.identifier` (or `ctx.jsonpath`) and refuses on hostile
17
+ * shapes. Targets the RFC 9535 compliant subset — filter / script
18
+ * expressions with code-execution semantics are rejected at every
19
+ * profile.
12
20
  *
13
- * Threat catalog:
14
- * - Filter expression `?(...)`dynamic-code-execution class in
15
- * legacy implementations. Universally refused at every profile.
16
- * - Script expression `(@.x)` style RFC 9535 doesn't define it
17
- * but many implementations support it as alias for filter.
18
- * - JS-source hintsoperator-supplied path containing the
19
- * literal substrings that would only appear in a code-injection
20
- * attempt: dynamic-code-exec keyword, constructor invocation
21
- * keyword, function-declaration keyword, arrow-function arrow,
22
- * or statement-separator semicolon.
23
- * - Recursive-descent depth bomb `..[*]` repeated > N times
24
- * amplifies traversal cost.
25
- * - Excessive bracket nesting.
26
- * - Excessive pattern length.
27
- * - BIDI / null / control / zero-width universal refuse.
21
+ * Threat catalog: filter expression `?(...)` (dynamic-code-
22
+ * execution class in legacy implementations refused
23
+ * universally); script expression shape `(@.x)` (RFC 9535
24
+ * undefined but several implementations alias it to filter);
25
+ * JS-source hints (the path contains substrings that only appear
26
+ * in a code-injection attemptdynamic-code-exec keyword,
27
+ * constructor invocation keyword, function-declaration keyword,
28
+ * arrow-function arrow, or statement-separator semicolon);
29
+ * recursive-descent depth bomb (`..[*]` repeated past
30
+ * `maxRecursiveDescents`); 3+ consecutive `[` parser-DoS shape;
31
+ * per-pattern byte cap; BIDI override / zero-width / C0 control /
32
+ * null-byte universal refuse.
33
+ *
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. Filter / script / dynamic-hint refusal holds
39
+ * at every profile — the RCE class is never an operator opt-in.
40
+ *
41
+ * JSONPath strings can't be repaired safely — `sanitize` either
42
+ * passes through clean input or throws `GuardJsonpathError`; the
43
+ * gate returns `serve` / `audit-only` / `refuse` (no `sanitize`
44
+ * action). The source file's hint catalog is composed from
45
+ * substring fragments so the file itself stays free of the
46
+ * literal keywords (the codebase-patterns gate flags them
47
+ * otherwise).
48
+ *
49
+ * @card
50
+ * JSONPath content-safety guard — refuses user-supplied JSONPath query strings that exhibit dynamic-code-execution shapes BEFORE they reach a JSONPath evaluator.
28
51
  */
29
52
 
30
53
  var codepointClass = require("./codepoint-class");
@@ -217,6 +240,46 @@ function _detectIssues(input, opts) {
217
240
  return issues;
218
241
  }
219
242
 
243
+ /**
244
+ * @primitive b.guardJsonpath.validate
245
+ * @signature b.guardJsonpath.validate(input, opts)
246
+ * @since 0.7.13
247
+ * @status stable
248
+ * @compliance hipaa, pci-dss, gdpr, soc2
249
+ * @related b.guardJsonpath.gate, b.guardJsonpath.sanitize
250
+ *
251
+ * Inspect a user-supplied JSONPath string and return an aggregated
252
+ * issue list. Pure inspection — never throws on hostile paths;
253
+ * caller decides what to do with the issues. The `ok` flag is
254
+ * `true` only when zero `critical` / `high` issues fire. Throws
255
+ * `GuardJsonpathError("jsonpath.bad-opt")` when a numeric opt is
256
+ * non-finite / negative (config-time mistake by the operator).
257
+ *
258
+ * @opts
259
+ * profile: "strict"|"balanced"|"permissive",
260
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
261
+ * bidiPolicy: "reject"|"audit"|"allow",
262
+ * controlPolicy: "reject"|"audit"|"allow",
263
+ * nullBytePolicy: "reject"|"audit"|"allow",
264
+ * zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
265
+ * filterExprPolicy: "reject"|"audit"|"allow",
266
+ * scriptExprPolicy: "reject"|"audit"|"allow",
267
+ * dynamicHintPolicy: "reject"|"audit"|"allow",
268
+ * bracketNestingPolicy: "reject"|"audit"|"allow",
269
+ * recursiveDescentPolicy: "reject"|"audit"|"allow",
270
+ * maxRecursiveDescents: number,
271
+ * maxPatternBytes: number,
272
+ * maxBytes: number,
273
+ * maxRuntimeMs: number,
274
+ *
275
+ * @example
276
+ * var clean = b.guardJsonpath.validate("$.users[*].name", { profile: "strict" });
277
+ * clean.ok; // → true
278
+ *
279
+ * var hostile = b.guardJsonpath.validate("$..[?(@.x)]", { profile: "strict" });
280
+ * hostile.ok; // → false
281
+ * hostile.issues.some(function (i) { return i.kind === "filter-expression"; }); // → true
282
+ */
220
283
  function validate(input, opts) {
221
284
  opts = _resolveOpts(opts);
222
285
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -225,6 +288,45 @@ function validate(input, opts) {
225
288
  return gateContract.aggregateIssues(_detectIssues(input, opts));
226
289
  }
227
290
 
291
+ /**
292
+ * @primitive b.guardJsonpath.sanitize
293
+ * @signature b.guardJsonpath.sanitize(input, opts)
294
+ * @since 0.7.13
295
+ * @status stable
296
+ * @compliance hipaa, pci-dss, gdpr, soc2
297
+ * @related b.guardJsonpath.validate, b.guardJsonpath.gate
298
+ *
299
+ * Pass-through-or-throw. JSONPath expressions cannot be safely
300
+ * repaired (stripping a `?(` from a filter silently changes query
301
+ * semantics); this primitive returns the input unchanged when no
302
+ * `critical` or `high` issue fires, otherwise throws
303
+ * `GuardJsonpathError` with the offending rule id (e.g.
304
+ * `jsonpath.filter-expression`, `jsonpath.dynamic-hint`,
305
+ * `jsonpath.script-expression`). Operators that need a "best-
306
+ * effort cleanup" semantic should reject the path at the boundary
307
+ * instead.
308
+ *
309
+ * @opts
310
+ * profile: "strict"|"balanced"|"permissive",
311
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
312
+ * filterExprPolicy: "reject"|"audit"|"allow",
313
+ * scriptExprPolicy: "reject"|"audit"|"allow",
314
+ * dynamicHintPolicy: "reject"|"audit"|"allow",
315
+ * bracketNestingPolicy: "reject"|"audit"|"allow",
316
+ * recursiveDescentPolicy: "reject"|"audit"|"allow",
317
+ * maxRecursiveDescents: number,
318
+ * maxPatternBytes: number,
319
+ *
320
+ * @example
321
+ * var safe = b.guardJsonpath.sanitize("$.users[*].name", { profile: "strict" });
322
+ * safe; // → "$.users[*].name"
323
+ *
324
+ * try {
325
+ * b.guardJsonpath.sanitize("$..[?(@.x)]", { profile: "strict" });
326
+ * } catch (e) {
327
+ * e.code; // → "jsonpath.filter-expression"
328
+ * }
329
+ */
228
330
  function sanitize(input, opts) {
229
331
  opts = _resolveOpts(opts);
230
332
  if (typeof input !== "string") {
@@ -240,6 +342,46 @@ function sanitize(input, opts) {
240
342
  return input;
241
343
  }
242
344
 
345
+ /**
346
+ * @primitive b.guardJsonpath.gate
347
+ * @signature b.guardJsonpath.gate(opts)
348
+ * @since 0.7.13
349
+ * @status stable
350
+ * @compliance hipaa, pci-dss, gdpr, soc2
351
+ * @related b.guardJsonpath.validate, b.guardJsonpath.sanitize
352
+ *
353
+ * Build a `b.gateContract` gate that screens `ctx.identifier` (or
354
+ * `ctx.jsonpath`) before the path reaches a JSONPath evaluator.
355
+ * Action chain: `serve` (no issues) → `audit-only` (warn-only) →
356
+ * `refuse` (any `critical` or `high`). No `sanitize` action —
357
+ * JSONPath strings cannot be repaired. Compose into query
358
+ * endpoints / search filters / data-export flows so operator-fed
359
+ * paths hit the guard before any evaluator dispatch.
360
+ *
361
+ * @opts
362
+ * profile: "strict"|"balanced"|"permissive",
363
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
364
+ * name: string, // override gate name in audit emissions
365
+ * filterExprPolicy: "reject"|"audit"|"allow",
366
+ * scriptExprPolicy: "reject"|"audit"|"allow",
367
+ * dynamicHintPolicy: "reject"|"audit"|"allow",
368
+ * bracketNestingPolicy: "reject"|"audit"|"allow",
369
+ * recursiveDescentPolicy: "reject"|"audit"|"allow",
370
+ * maxRecursiveDescents: number,
371
+ * maxPatternBytes: number,
372
+ *
373
+ * @example
374
+ * var gate = b.guardJsonpath.gate({ profile: "strict" });
375
+ *
376
+ * gate({ identifier: "$..[?(@.x)]" }).then(function (rv) {
377
+ * rv.ok; // → false
378
+ * rv.action; // → "refuse"
379
+ * });
380
+ *
381
+ * gate({ identifier: "$.users[*].name" }).then(function (rv) {
382
+ * rv.action; // → "serve"
383
+ * });
384
+ */
243
385
  function gate(opts) {
244
386
  opts = _resolveOpts(opts);
245
387
  return gateContract.buildGuardGate(
@@ -265,14 +407,84 @@ function gate(opts) {
265
407
  });
266
408
  }
267
409
 
410
+ /**
411
+ * @primitive b.guardJsonpath.buildProfile
412
+ * @signature b.guardJsonpath.buildProfile(opts)
413
+ * @since 0.7.13
414
+ * @status stable
415
+ * @related b.guardJsonpath.gate, b.guardJsonpath.compliancePosture
416
+ *
417
+ * Compose a derived guardJsonpath profile from one or more named
418
+ * bases plus inline overrides. `opts.extends` is a profile name
419
+ * (`"strict"` / `"balanced"` / `"permissive"`) or an array of
420
+ * names; later entries shadow earlier ones. Inline `opts` keys win
421
+ * last. Used to keep operator-defined profiles traceable to a
422
+ * baseline rather than re-typing every key.
423
+ *
424
+ * @opts
425
+ * extends: string|string[], // base profile name(s) to compose
426
+ * ...: any guardJsonpath key, // inline override of resolved keys
427
+ *
428
+ * @example
429
+ * var custom = b.guardJsonpath.buildProfile({
430
+ * extends: "balanced",
431
+ * maxRecursiveDescents: 1,
432
+ * recursiveDescentPolicy: "reject",
433
+ * });
434
+ * custom.maxRecursiveDescents; // → 1
435
+ * custom.filterExprPolicy; // → "reject"
436
+ */
268
437
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
269
438
 
439
+ /**
440
+ * @primitive b.guardJsonpath.compliancePosture
441
+ * @signature b.guardJsonpath.compliancePosture(name)
442
+ * @since 0.7.13
443
+ * @status stable
444
+ * @compliance hipaa, pci-dss, gdpr, soc2
445
+ * @related b.guardJsonpath.gate, b.guardJsonpath.buildProfile
446
+ *
447
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
448
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
449
+ * the posture object — the caller may mutate freely. Throws
450
+ * `GuardJsonpathError("jsonpath.bad-posture")` on unknown name.
451
+ *
452
+ * @example
453
+ * var posture = b.guardJsonpath.compliancePosture("hipaa");
454
+ * posture.filterExprPolicy; // → "reject"
455
+ * posture.forensicSnippetBytes; // → 256
456
+ */
270
457
  function compliancePosture(name) {
271
458
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
272
459
  _err, "jsonpath");
273
460
  }
274
461
 
275
462
  var _jpRulePacks = gateContract.makeRulePackLoader(GuardJsonpathError, "jsonpath");
463
+ /**
464
+ * @primitive b.guardJsonpath.loadRulePack
465
+ * @signature b.guardJsonpath.loadRulePack(pack)
466
+ * @since 0.7.13
467
+ * @status stable
468
+ * @related b.guardJsonpath.gate
469
+ *
470
+ * Register an operator-supplied rule pack with the guardJsonpath
471
+ * registry. The pack is identified by `pack.id` (non-empty string)
472
+ * and stored for later inspection / dispatch by gates that opt in
473
+ * via `opts.rulePackId`. Returns the pack object unchanged on
474
+ * success; throws `GuardJsonpathError("jsonpath.bad-opt")` when
475
+ * `pack` is missing or `pack.id` is not a non-empty string.
476
+ *
477
+ * @example
478
+ * var pack = b.guardJsonpath.loadRulePack({
479
+ * id: "no-wildcards",
480
+ * rules: [
481
+ * { id: "wildcard", severity: "high",
482
+ * detect: function (path) { return path.indexOf("[*]") !== -1; },
483
+ * reason: "wildcard index forbidden in this context" },
484
+ * ],
485
+ * });
486
+ * pack.id; // → "no-wildcards"
487
+ */
276
488
  var loadRulePack = _jpRulePacks.load;
277
489
 
278
490
  module.exports = {