@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-jwt.js CHANGED
@@ -1,36 +1,74 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-jwt — JWT identifier-safety primitive (b.guardJwt).
3
+ * @module b.guardJwt
4
+ * @nav Guards
5
+ * @title Guard Jwt
4
6
  *
5
- * Validates user-supplied JWT compact-serialization strings against
6
- * the canonical CVE-class refuse list before hand-off to a verifier.
7
- * KIND="identifier" consumes ctx.identifier (or ctx.token).
7
+ * @intro
8
+ * JWT identifier-safety guard validates user-supplied JWT
9
+ * compact-serialization strings against the canonical CVE-class
10
+ * refuse list BEFORE hand-off to a signature verifier. KIND is
11
+ * `identifier`; the gate consumes `ctx.identifier` (or
12
+ * `ctx.token` / `ctx.jwt`). Pair with `b.auth.jwt.verifyExternal`
13
+ * for cryptographic verification — this layer is the shape /
14
+ * header / claims contract that runs before any HMAC or signature
15
+ * work.
8
16
  *
9
- * Threat catalog:
10
- * - Shape malformation not 3 dot-separated base64url segments
11
- * (RFC 7515 §3 / RFC 7519 §3 compact serialization).
12
- * - alg=none — RFC 7518 §3.6 explicit "no signature" universally
13
- * refused; the canonical alg-confusion CVE class
14
- * (CVE-2015-9235 jsonwebtoken; CVE-2018-0114 java-jwt).
15
- * - alg algorithm-confusion operator's verifier may treat HS256
16
- * with an RSA public key as HMAC, allowing forgery; flag any
17
- * unexpected alg.
18
- * - kid path traversal — kid header used by some operators to
19
- * resolve key files; `..` / `/` / null-byte in kid would escape
20
- * the keystore directory.
21
- * - typ confusion — typ != "jwt" / "JWT" / "JWS" indicates a non-
22
- * JWT token coerced into the slot.
23
- * - Oversized header / payload / signature — defense against
24
- * decompression bombs and parser DoS.
25
- * - exp / nbf / iat sanity — exp in the past, nbf in the far
26
- * future, iat way in the future all indicate replay or clock-
27
- * skew issues.
28
- * - Unknown crit fields — RFC 7515 §4.1.11 — operator MUST refuse
29
- * tokens carrying crit headers it doesn't understand.
30
- * - BIDI / null / control / zero-width universal refuse.
17
+ * Algorithm-confusion defense: `alg=none` is universally refused
18
+ * at every profile (RFC 7518 §3.6 explicit-no-signature, the
19
+ * canonical CVE-2015-9235 jsonwebtoken / CVE-2018-0114 java-jwt
20
+ * class). The operator-supplied `allowedAlgs` allowlist defaults
21
+ * to the framework's PQC-first set (ML-DSA-87 / ML-DSA-65 /
22
+ * ML-DSA-44 / SLH-DSA-SHAKE-256{f,s} / SLH-DSA-SHA2-256{f,s} /
23
+ * EdDSA / ES* / RS* / PS*) so HS256-against-RSA-public-key
24
+ * forgery is blocked before the verifier sees the token.
31
25
  *
32
- * var rv = b.guardJwt.validate(jwtString, { profile: "strict" });
33
- * var g = b.guardJwt.gate({ profile: "strict" });
26
+ * `kid` path-traversal defense: the gate refuses any header `kid`
27
+ * that contains `..`, `/`, `\`, or percent-encoded variants —
28
+ * operators that resolve `kid` to a filesystem path can't escape
29
+ * the keystore directory. The standalone `b.guardJwt.kidSafe(kid)`
30
+ * helper throws on the same indicators and is the contract every
31
+ * `keyResolver` implementation must enforce before reading a key
32
+ * file.
33
+ *
34
+ * Bounded shape: header / payload / signature segments each have
35
+ * their own byte cap (`maxHeaderBytes` / `maxPayloadBytes` /
36
+ * `maxSignatureBytes`) and the total token is bounded by
37
+ * `maxBytes`. Decompression-bomb-shaped tokens fail at the cap
38
+ * check before any base64url decode. Header JSON is parsed
39
+ * through `b.safeJson.parse({ rejectProto: true })` so prototype
40
+ * pollution can't ride a forged header.
41
+ *
42
+ * Claim sanity: `exp` in the past, `nbf` more than
43
+ * `nbfFutureSlackMs` in the future, and `iat` more than
44
+ * `iatFutureSlackMs` in the future all surface as issues —
45
+ * replay / clock-skew detection that doesn't require pulling in
46
+ * a verifier. Required-claims (`iss` / `exp` / `iat` at strict;
47
+ * `iss` / `exp` at balanced) are enforced before the verifier
48
+ * so missing-claim refusals fail fast.
49
+ *
50
+ * `typ` confusion: any `typ` outside `jwt` / `jws` / `at+jwt` /
51
+ * `id_token` flags as suspect — non-JWT tokens coerced into a
52
+ * JWT slot are refused under strict, audited under balanced.
53
+ *
54
+ * `crit` discipline: RFC 7515 §4.1.11 mandates refusing tokens
55
+ * that carry `crit` headers the verifier doesn't understand. The
56
+ * gate's `knownCrit` allowlist is empty by default — every
57
+ * `crit` field is unknown unless the operator opts a name in.
58
+ *
59
+ * Audience verification is the operator's responsibility (the
60
+ * verifier handles it); the guard's required-claims list ensures
61
+ * the operator can't forget to populate `aud` in their verifier
62
+ * config because the claim must be present at validate time.
63
+ *
64
+ * Profiles: `strict` / `balanced` / `permissive`. Compliance
65
+ * postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. BIDI / null /
66
+ * control / zero-width universal-refuse applies on the raw input
67
+ * string at every profile so trojan-source codepoints can't ride
68
+ * inside a base64url segment.
69
+ *
70
+ * @card
71
+ * JWT identifier-safety guard — validates user-supplied JWT compact-serialization strings against the canonical CVE-class refuse list BEFORE hand-off to a signature verifier.
34
72
  */
35
73
 
36
74
  var codepointClass = require("./codepoint-class");
@@ -393,6 +431,64 @@ function _detectIssues(input, opts) {
393
431
  return issues;
394
432
  }
395
433
 
434
+ /**
435
+ * @primitive b.guardJwt.validate
436
+ * @signature b.guardJwt.validate(input, opts?)
437
+ * @since 0.7.49
438
+ * @status stable
439
+ * @compliance hipaa, pci-dss, gdpr, soc2
440
+ * @related b.guardJwt.sanitize, b.guardJwt.gate, b.auth.jwt.verifyExternal
441
+ *
442
+ * Apply the full guard-jwt threat catalog to a JWT compact-
443
+ * serialization string. Returns `{ ok, issues, refusal? }` per
444
+ * `gateContract.aggregateIssues`. Detected classes include
445
+ * `alg-none` (always critical), `kid-traversal` (always critical),
446
+ * `alg-not-allowed`, `typ-confusion`, `crit-unknown`, `exp-past`,
447
+ * `nbf-far-future`, `iat-far-future`, `claim-missing`, plus the
448
+ * shape (`jwt-shape`) / segment-cap (`header-cap` / `payload-cap`
449
+ * / `signature-cap`) / total-cap (`jwt-cap`) / codepoint-class
450
+ * issues. Header JSON is decoded through
451
+ * `b.safeJson.parse({ rejectProto: true })` so prototype-pollution
452
+ * keys are refused before any policy check runs. Operator-supplied
453
+ * opts are bounds-checked; bad opts throw
454
+ * `GuardJwtError("jwt.bad-opt")`.
455
+ *
456
+ * @opts
457
+ * profile: "strict"|"balanced"|"permissive",
458
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
459
+ * allowedAlgs: string[],
460
+ * requiredClaims: string[],
461
+ * knownCrit: string[],
462
+ * algNonePolicy: "reject"|"audit"|"allow",
463
+ * algAllowlistPolicy: "reject"|"audit"|"allow",
464
+ * kidTraversalPolicy: "reject"|"audit"|"allow",
465
+ * typConfusionPolicy: "reject"|"audit"|"allow",
466
+ * expSanityPolicy: "reject"|"audit"|"allow",
467
+ * nbfSanityPolicy: "reject"|"audit"|"allow",
468
+ * iatSanityPolicy: "reject"|"audit"|"allow",
469
+ * critUnknownPolicy: "reject"|"audit"|"allow",
470
+ * nbfFutureSlackMs: number,
471
+ * iatFutureSlackMs: number,
472
+ * maxHeaderBytes: number,
473
+ * maxPayloadBytes: number,
474
+ * maxSignatureBytes: number,
475
+ * maxBytes: number,
476
+ *
477
+ * @example
478
+ * var algNoneToken =
479
+ * "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
480
+ * "eyJzdWIiOiJhdHRhY2tlciJ9.";
481
+ * var rv = b.guardJwt.validate(algNoneToken, { profile: "strict" });
482
+ * rv.ok; // → false
483
+ * rv.issues[0].ruleId; // → "jwt.alg-none"
484
+ *
485
+ * var benign =
486
+ * "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." +
487
+ * "eyJpc3MiOiJleGFtcGxlIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE3MDAwMDAwMDB9." +
488
+ * "sig";
489
+ * var ok = b.guardJwt.validate(benign, { profile: "strict" });
490
+ * ok.ok; // → true
491
+ */
396
492
  function validate(input, opts) {
397
493
  opts = _resolveOpts(opts);
398
494
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -410,6 +506,36 @@ function validate(input, opts) {
410
506
  return gateContract.aggregateIssues(_detectIssues(input, opts));
411
507
  }
412
508
 
509
+ /**
510
+ * @primitive b.guardJwt.sanitize
511
+ * @signature b.guardJwt.sanitize(input, opts?)
512
+ * @since 0.7.49
513
+ * @status stable
514
+ * @related b.guardJwt.validate, b.guardJwt.gate
515
+ *
516
+ * Pass-through-or-throw form of `validate`. JWT compact
517
+ * serialization can't be repaired (every byte feeds the signature)
518
+ * so sanitize either returns the input unchanged when the issue
519
+ * list contains no `critical` / `high` entries, or throws
520
+ * `GuardJwtError` carrying the offending `ruleId`. Use this when
521
+ * the caller wants a single try/catch boundary instead of an
522
+ * issue-list switch.
523
+ *
524
+ * @opts
525
+ * profile: "strict"|"balanced"|"permissive",
526
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
527
+ * ...: every guardJwt.validate opt is honored,
528
+ *
529
+ * @example
530
+ * var algNoneToken =
531
+ * "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
532
+ * "eyJzdWIiOiJhdHRhY2tlciJ9.";
533
+ * try {
534
+ * b.guardJwt.sanitize(algNoneToken, { profile: "strict" });
535
+ * } catch (e) {
536
+ * e.code; // → "jwt.alg-none"
537
+ * }
538
+ */
413
539
  function sanitize(input, opts) {
414
540
  opts = _resolveOpts(opts);
415
541
  if (typeof input !== "string") {
@@ -427,6 +553,39 @@ function sanitize(input, opts) {
427
553
  return input;
428
554
  }
429
555
 
556
+ /**
557
+ * @primitive b.guardJwt.gate
558
+ * @signature b.guardJwt.gate(opts?)
559
+ * @since 0.7.49
560
+ * @status stable
561
+ * @compliance hipaa, pci-dss, gdpr, soc2
562
+ * @related b.guardJwt.validate, b.guardJwt.sanitize, b.middleware.bearerAuth
563
+ *
564
+ * Build a `gateContract.buildGuardGate`-shaped gate that pulls
565
+ * `ctx.identifier` (or `ctx.token` / `ctx.jwt`) and dispatches to
566
+ * `validate`. Returns `{ ok: true, action: "serve" }` when the
567
+ * issue list is empty, `{ ok: true, action: "audit-only", issues }`
568
+ * when only low-severity issues fire, and `{ ok: false, action:
569
+ * "refuse", issues }` on any `critical` / `high` issue. Compose
570
+ * into auth pipelines via `b.middleware.bearerAuth` so every
571
+ * bearer token is shape-checked before signature verification.
572
+ *
573
+ * @opts
574
+ * profile: "strict"|"balanced"|"permissive",
575
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
576
+ * name: string, // gate label for audit trails
577
+ * ...: every guardJwt.validate opt is honored,
578
+ *
579
+ * @example
580
+ * var jwtGate = b.guardJwt.gate({ profile: "strict" });
581
+ * var rv = await jwtGate.run({
582
+ * identifier:
583
+ * "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
584
+ * "eyJzdWIiOiJhdHRhY2tlciJ9.",
585
+ * });
586
+ * rv.action; // → "refuse"
587
+ * rv.issues[0].ruleId; // → "jwt.alg-none"
588
+ */
430
589
  function gate(opts) {
431
590
  opts = _resolveOpts(opts);
432
591
  return gateContract.buildGuardGate(
@@ -450,18 +609,113 @@ function gate(opts) {
450
609
  });
451
610
  }
452
611
 
612
+ /**
613
+ * @primitive b.guardJwt.buildProfile
614
+ * @signature b.guardJwt.buildProfile(opts)
615
+ * @since 0.7.49
616
+ * @status stable
617
+ * @related b.guardJwt.gate, b.guardJwt.compliancePosture
618
+ *
619
+ * Compose a derived profile from one or more named bases plus
620
+ * inline overrides. `opts.extends` is a profile name (`"strict"` /
621
+ * `"balanced"` / `"permissive"`) or an array of names; later
622
+ * entries shadow earlier ones, and inline `opts` keys win last.
623
+ * Operators stage profile overlays here so the final shape is
624
+ * traceable to a baseline rather than a hand-typed dictionary.
625
+ *
626
+ * @opts
627
+ * extends: string|string[], // base profile name(s) to compose
628
+ * ...: any guardJwt key, // inline override of resolved keys
629
+ *
630
+ * @example
631
+ * var custom = b.guardJwt.buildProfile({
632
+ * extends: "balanced",
633
+ * algAllowlistPolicy: "reject",
634
+ * allowedAlgs: ["ES256", "EdDSA"],
635
+ * });
636
+ * custom.algAllowlistPolicy; // → "reject"
637
+ * custom.allowedAlgs.indexOf("ES256"); // → 0
638
+ */
453
639
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
454
640
 
641
+ /**
642
+ * @primitive b.guardJwt.compliancePosture
643
+ * @signature b.guardJwt.compliancePosture(name)
644
+ * @since 0.7.49
645
+ * @status stable
646
+ * @compliance hipaa, pci-dss, gdpr, soc2
647
+ * @related b.guardJwt.gate, b.guardJwt.buildProfile
648
+ *
649
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
650
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
651
+ * the posture object — the caller may mutate freely. Throws
652
+ * `GuardJwtError("jwt.bad-posture")` on unknown name. Postures
653
+ * extend the strict profile (or balanced for `gdpr`) with a
654
+ * `forensicSnippetBytes` cap appropriate to the regime.
655
+ *
656
+ * @example
657
+ * var posture = b.guardJwt.compliancePosture("hipaa");
658
+ * posture.algNonePolicy; // → "reject"
659
+ * posture.forensicSnippetBytes; // → 256
660
+ */
455
661
  function compliancePosture(name) {
456
662
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
457
663
  _err, "jwt");
458
664
  }
459
665
 
460
666
  var _jwtRulePacks = gateContract.makeRulePackLoader(GuardJwtError, "jwt");
667
+ /**
668
+ * @primitive b.guardJwt.loadRulePack
669
+ * @signature b.guardJwt.loadRulePack(pack)
670
+ * @since 0.7.49
671
+ * @status stable
672
+ * @related b.guardJwt.gate
673
+ *
674
+ * Register an operator-supplied rule pack with the guard-jwt
675
+ * registry. The pack is identified by `pack.id` (non-empty
676
+ * string) and stored for later inspection / dispatch by gates
677
+ * that opt in via `opts.rulePackId`. Returns the pack object
678
+ * unchanged on success; throws `GuardJwtError("jwt.bad-opt")`
679
+ * when `pack` is missing or `pack.id` is not a non-empty string.
680
+ *
681
+ * @example
682
+ * var pack = b.guardJwt.loadRulePack({
683
+ * id: "tenant-issuer-pin",
684
+ * rules: [
685
+ * { id: "iss-pin", severity: "high",
686
+ * detect: function (claims) { return claims.iss !== "https://idp.example/"; },
687
+ * reason: "tenant pins iss to a single IdP" },
688
+ * ],
689
+ * });
690
+ * pack.id; // → "tenant-issuer-pin"
691
+ */
461
692
  var loadRulePack = _jwtRulePacks.load;
462
693
 
463
- // Operator helper — `kidSafe(kid)` throws on traversal indicators.
464
- // Documented as the contract for keyResolver implementations.
694
+ /**
695
+ * @primitive b.guardJwt.kidSafe
696
+ * @signature b.guardJwt.kidSafe(kid)
697
+ * @since 0.7.49
698
+ * @status stable
699
+ * @compliance hipaa, pci-dss, gdpr, soc2
700
+ * @related b.guardJwt.validate, b.auth.jwt.verifyExternal
701
+ *
702
+ * Throw on any `kid` value that contains path-traversal indicators
703
+ * (`..`, `/`, `\`, percent-encoded variants) or non-printable
704
+ * control bytes. Returns the input unchanged on success. This is
705
+ * the contract every operator `keyResolver` MUST run before
706
+ * resolving `kid` to a filesystem path or KMS key handle —
707
+ * without it, a forged token's `kid` can escape the keystore
708
+ * directory.
709
+ *
710
+ * @example
711
+ * b.guardJwt.kidSafe("tenant-1-2026-05"); // → "tenant-1-2026-05"
712
+ *
713
+ * try {
714
+ * b.guardJwt.kidSafe("../../etc/passwd");
715
+ * } catch (e) {
716
+ * e.code; // → "jwt.kid-traversal"
717
+ * }
718
+ */
465
719
  function kidSafe(kid) {
466
720
  if (typeof kid !== "string" || kid.length === 0) {
467
721
  throw _err("jwt.kid-empty", "kid must be a non-empty string");
@@ -1,30 +1,41 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-markdown — Markdown content-safety primitive (b.guardMarkdown).
3
+ * @module b.guardMarkdown
4
+ * @nav Guards
5
+ * @title Guard Markdown
4
6
  *
5
- * Threat catalog grounded in current research:
6
- * - CVE-2026-30838 CommonMark DisallowedRawHtml whitespace-tag bypass
7
- * (`<script\n>` / `<script\t>` evades naive `<script>` matchers; the
8
- * browser still treats the tag as a script element).
9
- * - CVE-2025-9540 Markup Markdown stored XSS via link with javascript:.
10
- * - CVE-2025-7969 markdown-it ReDoS class.
11
- * - CVE-2025-6493 — CodeMirror Markdown Mode catastrophic backtracking.
12
- * - CVE-2025-24981 — MDC Markdown XSS via unsanitized autolinks.
13
- * - CVE-2026-33500 — AVideo Parsedown link XSS via inlineLink/inlineUrlTag
14
- * bypass (ParsedownSafeWithLinks didn't override link emitters).
15
- * - GHSA-gwjh-c548-f787 — NuGetGallery autolink XSS.
16
- * - Joplin GHSA-hff8-hjwv-j9q7 — RCE via untrusted markdown link.
7
+ * @intro
8
+ * CommonMark validator + sanitizer for user-supplied markdown.
9
+ * Refuses raw HTML by default, applies a URL-scheme allowlist on
10
+ * inline links / autolinks / images / reference defs, and caps
11
+ * image dimensions and structural depth to defang renderer DoS.
12
+ * KIND="content"the gate consumes `ctx.bytes` /
13
+ * `ctx.bodyText`.
17
14
  *
18
- * The primitive is a SOURCE-LEVEL gate: it inspects raw markdown text
19
- * BEFORE any downstream renderer (marked / markdown-it / commonmark /
20
- * remark / parsedown) sees it. Source-level discipline matters because
21
- * the most dangerous shapes — `__proto__` in JSON, `<script\n>` in
22
- * markdown exploit specific parser internals; sanitizing on the
23
- * post-parse tree is too late.
15
+ * The primitive is a SOURCE-LEVEL gate: it inspects raw markdown
16
+ * text BEFORE any downstream renderer (marked / markdown-it /
17
+ * commonmark / remark / parsedown) sees it. Source-level
18
+ * discipline matters because the most dangerous shapes —
19
+ * `__proto__` in JSON, `<script\n>` in markdown exploit
20
+ * specific parser internals; sanitizing on the post-parse tree
21
+ * is too late.
24
22
  *
25
- * var rv = b.guardMarkdown.validate(input, { profile: "strict" });
26
- * var safe = b.guardMarkdown.sanitize(input, { profile: "balanced" });
27
- * var g = b.guardMarkdown.gate({ profile: "strict" });
23
+ * Threat catalog grounded in current CVE research:
24
+ * CVE-2026-30838 (CommonMark DisallowedRawHtml whitespace-tag
25
+ * bypass `<script\n>` / `<script\t>` evades naive `<script>`
26
+ * matchers); CVE-2025-9540 (Markup Markdown stored XSS via
27
+ * `javascript:` link); CVE-2025-7969 (markdown-it ReDoS class);
28
+ * CVE-2025-6493 (CodeMirror Markdown Mode catastrophic
29
+ * backtracking); CVE-2025-24981 (MDC autolink XSS);
30
+ * CVE-2026-33500 (AVideo Parsedown inlineLink/inlineUrlTag
31
+ * bypass); GHSA-gwjh-c548-f787 (NuGetGallery autolink XSS);
32
+ * Joplin GHSA-hff8-hjwv-j9q7 (RCE via untrusted markdown link).
33
+ *
34
+ * Profiles: `strict` / `balanced` / `permissive`. Compliance
35
+ * postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`.
36
+ *
37
+ * @card
38
+ * CommonMark validator + sanitizer for user-supplied markdown.
28
39
  */
29
40
 
30
41
  var codepointClass = require("./codepoint-class");
@@ -477,6 +488,55 @@ function _detectIssues(input, opts) {
477
488
 
478
489
  // ---- Public surface ----
479
490
 
491
+ /**
492
+ * @primitive b.guardMarkdown.validate
493
+ * @signature b.guardMarkdown.validate(input, opts?)
494
+ * @since 0.7.16
495
+ * @status stable
496
+ * @compliance hipaa, pci-dss, gdpr, soc2
497
+ * @related b.guardMarkdown.sanitize, b.guardMarkdown.gate
498
+ *
499
+ * Inspect raw markdown source against the resolved profile and
500
+ * return `{ ok, issues }`. Each issue carries `kind` / `severity`
501
+ * (`critical` | `high` | `medium` | `low`) / `ruleId` / `snippet`.
502
+ * Non-string input returns a single `markdown.bad-input` issue
503
+ * rather than throwing — callers that prefer an exception use
504
+ * `b.guardMarkdown.sanitize`.
505
+ *
506
+ * @opts
507
+ * profile: "strict"|"balanced"|"permissive",
508
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
509
+ * bidiPolicy: "reject"|"strip"|"audit"|"allow",
510
+ * controlPolicy: "reject"|"strip"|"allow",
511
+ * nullBytePolicy: "reject"|"strip"|"allow",
512
+ * zeroWidthPolicy: "reject"|"strip"|"allow",
513
+ * dangerousTagPolicy: "reject"|"strip"|"audit"|"allow",
514
+ * dangerousSchemePolicy: "reject"|"strip"|"audit"|"allow",
515
+ * imageSchemePolicy: "reject"|"strip"|"audit"|"allow",
516
+ * autolinkSchemePolicy: "reject"|"strip"|"audit"|"allow",
517
+ * referenceLinkPolicy: "reject"|"strip"|"audit"|"allow",
518
+ * codeFenceLangPolicy: "reject"|"strip"|"audit"|"allow",
519
+ * doctypePolicy: "reject"|"strip"|"audit"|"allow",
520
+ * schemeAllowlist: string[], // default ["http","https","mailto"]
521
+ * maxBytes: number,
522
+ * maxLines: number,
523
+ * maxLinks: number,
524
+ * maxImages: number,
525
+ * maxAutolinks: number,
526
+ * maxRefDefs: number,
527
+ * maxListDepth: number,
528
+ * maxBlockquoteDepth: number,
529
+ *
530
+ * @example
531
+ * var rv = b.guardMarkdown.validate("# hello\n\n[link](https://example.com)",
532
+ * { profile: "strict" });
533
+ * rv.ok; // → true
534
+ *
535
+ * var bad = b.guardMarkdown.validate("[click](javascript:alert(1))",
536
+ * { profile: "strict" });
537
+ * bad.ok; // → false
538
+ * bad.issues[0].ruleId; // → "markdown.dangerous-scheme"
539
+ */
480
540
  function validate(input, opts) {
481
541
  opts = _resolveOpts(opts);
482
542
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -493,6 +553,36 @@ function validate(input, opts) {
493
553
  return gateContract.aggregateIssues(_detectIssues(input, opts));
494
554
  }
495
555
 
556
+ /**
557
+ * @primitive b.guardMarkdown.sanitize
558
+ * @signature b.guardMarkdown.sanitize(input, opts?)
559
+ * @since 0.7.16
560
+ * @status stable
561
+ * @related b.guardMarkdown.validate, b.guardMarkdown.gate
562
+ *
563
+ * Strip BIDI / zero-width / control / null-byte codepoints under
564
+ * their resolved policies and return the cleaned markdown source.
565
+ * Throws `GuardMarkdownError` when any `critical` issue fires
566
+ * (raw `<script>`, `javascript:` link, doctype injection). Use
567
+ * `validate` to inspect issues without throwing.
568
+ *
569
+ * @opts
570
+ * profile: "strict"|"balanced"|"permissive",
571
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
572
+ * ...: same shape as b.guardMarkdown.validate opts,
573
+ *
574
+ * @example
575
+ * var clean = b.guardMarkdown.sanitize("hello\u200Bworld",
576
+ * { profile: "balanced" });
577
+ * clean; // → "helloworld"
578
+ *
579
+ * try {
580
+ * b.guardMarkdown.sanitize("<script>alert(1)</script>",
581
+ * { profile: "strict" });
582
+ * } catch (e) {
583
+ * e.code; // → "markdown.dangerous-tag"
584
+ * }
585
+ */
496
586
  function sanitize(input, opts) {
497
587
  opts = _resolveOpts(opts);
498
588
  if (typeof input !== "string") {
@@ -508,6 +598,36 @@ function sanitize(input, opts) {
508
598
  return codepointClass.applyCharStripPolicies(input, opts);
509
599
  }
510
600
 
601
+ /**
602
+ * @primitive b.guardMarkdown.gate
603
+ * @signature b.guardMarkdown.gate(opts?)
604
+ * @since 0.7.16
605
+ * @status stable
606
+ * @compliance hipaa, pci-dss, gdpr, soc2
607
+ * @related b.guardMarkdown.validate, b.guardMarkdown.sanitize, b.guardAll.gate, b.staticServe.create
608
+ *
609
+ * Build an async gate `(ctx) -> { ok, action, issues }` consumable
610
+ * by `b.guardAll`, `b.staticServe`, `b.fileUpload`, and any host
611
+ * that ingests user-supplied markdown. The gate decodes
612
+ * `ctx.bytes` / `ctx.bodyText`, runs `validate`, and maps
613
+ * severity to action: zero issues `serve`; only low/medium
614
+ * `audit-only`; sanitizable issues `sanitize` (returning the
615
+ * cleaned bytes); any unfixable critical `refuse`.
616
+ *
617
+ * @opts
618
+ * name: string, // gate label for audit / observability
619
+ * profile: "strict"|"balanced"|"permissive",
620
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
621
+ * ...: same shape as b.guardMarkdown.validate opts,
622
+ *
623
+ * @example
624
+ * var g = b.guardMarkdown.gate({ profile: "strict" });
625
+ * var rv = await g({ bytes: Buffer.from("# hello\n", "utf8") });
626
+ * rv.action; // → "serve"
627
+ *
628
+ * var bad = await g({ bytes: Buffer.from("[x](javascript:1)", "utf8") });
629
+ * bad.action; // → "refuse"
630
+ */
511
631
  function gate(opts) {
512
632
  opts = _resolveOpts(opts);
513
633
  return gateContract.buildGuardGate(
@@ -547,13 +667,75 @@ function gate(opts) {
547
667
  });
548
668
  }
549
669
 
670
+ /**
671
+ * @primitive b.guardMarkdown.buildProfile
672
+ * @signature b.guardMarkdown.buildProfile(opts)
673
+ * @since 0.7.16
674
+ * @status stable
675
+ * @related b.guardMarkdown.gate, b.guardMarkdown.compliancePosture
676
+ *
677
+ * Compose a derived profile from one or more named bases plus
678
+ * inline overrides. `opts.extends` is a profile name or array of
679
+ * names (later entries shadow earlier ones); inline keys win last.
680
+ *
681
+ * @opts
682
+ * extends: string|string[], // base profile name(s) to compose
683
+ * ...: any guard-markdown key, // inline override of resolved keys
684
+ *
685
+ * @example
686
+ * var custom = b.guardMarkdown.buildProfile({
687
+ * extends: "balanced",
688
+ * dangerousTagPolicy: "strip",
689
+ * schemeAllowlist: ["http", "https"],
690
+ * });
691
+ * custom.dangerousTagPolicy; // → "strip"
692
+ * custom.schemeAllowlist.indexOf("mailto"); // → -1
693
+ */
550
694
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
551
695
 
696
+ /**
697
+ * @primitive b.guardMarkdown.compliancePosture
698
+ * @signature b.guardMarkdown.compliancePosture(name)
699
+ * @since 0.7.16
700
+ * @status stable
701
+ * @compliance hipaa, pci-dss, gdpr, soc2
702
+ * @related b.guardMarkdown.gate, b.guardMarkdown.buildProfile
703
+ *
704
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
705
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
706
+ * the posture object — the caller may mutate freely. Throws
707
+ * `GuardMarkdownError("markdown.bad-posture")` on unknown name.
708
+ *
709
+ * @example
710
+ * var posture = b.guardMarkdown.compliancePosture("hipaa");
711
+ * posture.dangerousTagPolicy; // → "reject"
712
+ */
552
713
  function compliancePosture(name) {
553
714
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "markdown");
554
715
  }
555
716
 
556
717
  var _markdownRulePacks = gateContract.makeRulePackLoader(GuardMarkdownError, "markdown");
718
+ /**
719
+ * @primitive b.guardMarkdown.loadRulePack
720
+ * @signature b.guardMarkdown.loadRulePack(pack)
721
+ * @since 0.7.16
722
+ * @status stable
723
+ * @related b.guardMarkdown.gate
724
+ *
725
+ * Register an operator-supplied rule pack with the guard-markdown
726
+ * registry. The pack is identified by `pack.id` (non-empty
727
+ * string) and stored for later inspection / dispatch by gates
728
+ * that opt in via `opts.rulePackId`. Throws
729
+ * `GuardMarkdownError("markdown.bad-opt")` when `pack` is missing
730
+ * or `pack.id` is not a non-empty string.
731
+ *
732
+ * @example
733
+ * var pack = b.guardMarkdown.loadRulePack({
734
+ * id: "wiki-internal",
735
+ * extraSchemeAllowlist: ["wiki"],
736
+ * });
737
+ * pack.id; // → "wiki-internal"
738
+ */
557
739
  var loadRulePack = _markdownRulePacks.load;
558
740
 
559
741
  module.exports = {