@blamejs/core 0.8.43 → 0.8.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +10 -10
  3. package/index.js +52 -0
  4. package/lib/a2a.js +159 -34
  5. package/lib/acme.js +762 -0
  6. package/lib/ai-pref.js +166 -43
  7. package/lib/api-key.js +108 -47
  8. package/lib/api-snapshot.js +157 -40
  9. package/lib/app-shutdown.js +113 -77
  10. package/lib/archive.js +337 -40
  11. package/lib/arg-parser.js +697 -0
  12. package/lib/asyncapi.js +99 -55
  13. package/lib/atomic-file.js +465 -104
  14. package/lib/audit-chain.js +123 -34
  15. package/lib/audit-daily-review.js +389 -0
  16. package/lib/audit-sign.js +302 -56
  17. package/lib/audit-tools.js +412 -63
  18. package/lib/audit.js +656 -35
  19. package/lib/auth/jwt-external.js +17 -0
  20. package/lib/auth/oauth.js +7 -0
  21. package/lib/auth-bot-challenge.js +505 -0
  22. package/lib/auth-header.js +92 -25
  23. package/lib/backup/bundle.js +26 -0
  24. package/lib/backup/index.js +512 -89
  25. package/lib/backup/manifest.js +168 -7
  26. package/lib/break-glass.js +415 -39
  27. package/lib/budr.js +103 -30
  28. package/lib/bundler.js +86 -66
  29. package/lib/cache.js +192 -72
  30. package/lib/chain-writer.js +65 -40
  31. package/lib/circuit-breaker.js +56 -33
  32. package/lib/cli-helpers.js +106 -75
  33. package/lib/cli.js +6 -30
  34. package/lib/cloud-events.js +99 -32
  35. package/lib/cluster-storage.js +162 -37
  36. package/lib/cluster.js +340 -49
  37. package/lib/codepoint-class.js +66 -0
  38. package/lib/compliance.js +424 -24
  39. package/lib/config-drift.js +111 -46
  40. package/lib/config.js +94 -40
  41. package/lib/consent.js +165 -18
  42. package/lib/constants.js +1 -0
  43. package/lib/content-credentials.js +153 -48
  44. package/lib/cookies.js +154 -62
  45. package/lib/credential-hash.js +133 -61
  46. package/lib/crypto-field.js +702 -18
  47. package/lib/crypto-hpke.js +256 -0
  48. package/lib/crypto.js +744 -22
  49. package/lib/csv.js +178 -35
  50. package/lib/daemon.js +456 -0
  51. package/lib/dark-patterns.js +186 -55
  52. package/lib/db-query.js +79 -2
  53. package/lib/db.js +1431 -60
  54. package/lib/ddl-change-control.js +523 -0
  55. package/lib/deprecate.js +195 -40
  56. package/lib/dev.js +82 -39
  57. package/lib/dora.js +67 -48
  58. package/lib/dr-runbook.js +368 -0
  59. package/lib/dsr.js +142 -11
  60. package/lib/dual-control.js +91 -56
  61. package/lib/events.js +120 -41
  62. package/lib/external-db-migrate.js +192 -2
  63. package/lib/external-db.js +795 -50
  64. package/lib/fapi2.js +122 -1
  65. package/lib/fda-21cfr11.js +395 -0
  66. package/lib/fdx.js +132 -2
  67. package/lib/file-type.js +87 -0
  68. package/lib/file-upload.js +93 -0
  69. package/lib/flag.js +82 -20
  70. package/lib/forms.js +132 -29
  71. package/lib/framework-error.js +169 -0
  72. package/lib/framework-schema.js +163 -35
  73. package/lib/gate-contract.js +849 -175
  74. package/lib/graphql-federation.js +68 -7
  75. package/lib/guard-all.js +172 -55
  76. package/lib/guard-archive.js +286 -124
  77. package/lib/guard-auth.js +194 -21
  78. package/lib/guard-cidr.js +190 -28
  79. package/lib/guard-csv.js +397 -51
  80. package/lib/guard-domain.js +213 -91
  81. package/lib/guard-email.js +236 -29
  82. package/lib/guard-filename.js +307 -75
  83. package/lib/guard-graphql.js +263 -30
  84. package/lib/guard-html.js +310 -116
  85. package/lib/guard-image.js +243 -30
  86. package/lib/guard-json.js +260 -54
  87. package/lib/guard-jsonpath.js +235 -23
  88. package/lib/guard-jwt.js +284 -30
  89. package/lib/guard-markdown.js +204 -22
  90. package/lib/guard-mime.js +190 -26
  91. package/lib/guard-oauth.js +277 -28
  92. package/lib/guard-pdf.js +251 -27
  93. package/lib/guard-regex.js +226 -18
  94. package/lib/guard-shell.js +229 -26
  95. package/lib/guard-svg.js +177 -10
  96. package/lib/guard-template.js +232 -21
  97. package/lib/guard-time.js +195 -29
  98. package/lib/guard-uuid.js +189 -30
  99. package/lib/guard-xml.js +259 -36
  100. package/lib/guard-yaml.js +241 -44
  101. package/lib/honeytoken.js +63 -27
  102. package/lib/html-balance.js +83 -0
  103. package/lib/http-client.js +486 -59
  104. package/lib/http-message-signature.js +582 -0
  105. package/lib/i18n.js +102 -49
  106. package/lib/iab-mspa.js +112 -32
  107. package/lib/iab-tcf.js +107 -2
  108. package/lib/inbox.js +90 -52
  109. package/lib/keychain.js +865 -0
  110. package/lib/legal-hold.js +374 -0
  111. package/lib/local-db-thin.js +320 -0
  112. package/lib/log-stream.js +281 -51
  113. package/lib/log.js +184 -86
  114. package/lib/mail-bounce.js +107 -62
  115. package/lib/mail.js +295 -58
  116. package/lib/mcp.js +108 -27
  117. package/lib/metrics.js +98 -89
  118. package/lib/middleware/age-gate.js +36 -0
  119. package/lib/middleware/ai-act-disclosure.js +37 -0
  120. package/lib/middleware/api-encrypt.js +45 -0
  121. package/lib/middleware/assetlinks.js +40 -0
  122. package/lib/middleware/asyncapi-serve.js +35 -0
  123. package/lib/middleware/attach-user.js +40 -0
  124. package/lib/middleware/bearer-auth.js +40 -0
  125. package/lib/middleware/body-parser.js +230 -0
  126. package/lib/middleware/bot-disclose.js +34 -0
  127. package/lib/middleware/bot-guard.js +39 -0
  128. package/lib/middleware/compression.js +37 -0
  129. package/lib/middleware/cookies.js +32 -0
  130. package/lib/middleware/cors.js +40 -0
  131. package/lib/middleware/csp-nonce.js +40 -0
  132. package/lib/middleware/csp-report.js +34 -0
  133. package/lib/middleware/csrf-protect.js +43 -0
  134. package/lib/middleware/daily-byte-quota.js +53 -85
  135. package/lib/middleware/db-role-for.js +40 -0
  136. package/lib/middleware/dpop.js +40 -0
  137. package/lib/middleware/error-handler.js +37 -14
  138. package/lib/middleware/fetch-metadata.js +39 -0
  139. package/lib/middleware/flag-context.js +34 -0
  140. package/lib/middleware/gpc.js +33 -0
  141. package/lib/middleware/headers.js +35 -0
  142. package/lib/middleware/health.js +46 -0
  143. package/lib/middleware/host-allowlist.js +30 -0
  144. package/lib/middleware/network-allowlist.js +38 -0
  145. package/lib/middleware/openapi-serve.js +34 -0
  146. package/lib/middleware/rate-limit.js +160 -18
  147. package/lib/middleware/request-id.js +36 -18
  148. package/lib/middleware/request-log.js +37 -0
  149. package/lib/middleware/require-aal.js +29 -0
  150. package/lib/middleware/require-auth.js +32 -0
  151. package/lib/middleware/require-bound-key.js +41 -0
  152. package/lib/middleware/require-content-type.js +32 -0
  153. package/lib/middleware/require-methods.js +27 -0
  154. package/lib/middleware/require-mtls.js +33 -0
  155. package/lib/middleware/require-step-up.js +37 -0
  156. package/lib/middleware/security-headers.js +44 -0
  157. package/lib/middleware/security-txt.js +38 -0
  158. package/lib/middleware/span-http-server.js +37 -0
  159. package/lib/middleware/sse.js +36 -0
  160. package/lib/middleware/trace-log-correlation.js +33 -0
  161. package/lib/middleware/trace-propagate.js +32 -0
  162. package/lib/middleware/tus-upload.js +90 -0
  163. package/lib/middleware/web-app-manifest.js +53 -0
  164. package/lib/mtls-ca.js +100 -70
  165. package/lib/network-byte-quota.js +308 -0
  166. package/lib/network-heartbeat.js +135 -0
  167. package/lib/network-tls.js +534 -4
  168. package/lib/network.js +103 -0
  169. package/lib/notify.js +114 -43
  170. package/lib/ntp-check.js +192 -51
  171. package/lib/observability.js +145 -47
  172. package/lib/openapi.js +90 -44
  173. package/lib/outbox.js +99 -1
  174. package/lib/pagination.js +168 -86
  175. package/lib/parsers/index.js +16 -5
  176. package/lib/permissions.js +93 -40
  177. package/lib/pqc-agent.js +84 -8
  178. package/lib/pqc-software.js +94 -60
  179. package/lib/process-spawn.js +95 -21
  180. package/lib/pubsub.js +96 -66
  181. package/lib/queue.js +375 -54
  182. package/lib/redact.js +793 -21
  183. package/lib/render.js +139 -47
  184. package/lib/request-helpers.js +485 -121
  185. package/lib/restore-bundle.js +142 -39
  186. package/lib/restore-rollback.js +136 -45
  187. package/lib/retention.js +178 -50
  188. package/lib/retry.js +116 -33
  189. package/lib/router.js +475 -23
  190. package/lib/safe-async.js +543 -94
  191. package/lib/safe-buffer.js +337 -41
  192. package/lib/safe-json.js +467 -62
  193. package/lib/safe-jsonpath.js +285 -0
  194. package/lib/safe-schema.js +631 -87
  195. package/lib/safe-sql.js +221 -59
  196. package/lib/safe-url.js +278 -46
  197. package/lib/sandbox-worker.js +135 -0
  198. package/lib/sandbox.js +358 -0
  199. package/lib/scheduler.js +135 -70
  200. package/lib/self-update.js +647 -0
  201. package/lib/session-device-binding.js +431 -0
  202. package/lib/session.js +259 -49
  203. package/lib/slug.js +138 -26
  204. package/lib/ssrf-guard.js +316 -56
  205. package/lib/storage.js +433 -70
  206. package/lib/subject.js +405 -23
  207. package/lib/template.js +148 -8
  208. package/lib/tenant-quota.js +545 -0
  209. package/lib/testing.js +440 -53
  210. package/lib/time.js +291 -23
  211. package/lib/tls-exporter.js +239 -0
  212. package/lib/tracing.js +90 -74
  213. package/lib/uuid.js +97 -22
  214. package/lib/vault/index.js +284 -22
  215. package/lib/vault/seal-pem-file.js +66 -0
  216. package/lib/watcher.js +368 -0
  217. package/lib/webhook.js +196 -63
  218. package/lib/websocket.js +393 -68
  219. package/lib/wiki-concepts.js +338 -0
  220. package/lib/worker-pool.js +464 -0
  221. package/package.json +3 -3
  222. package/sbom.cyclonedx.json +7 -7
package/lib/guard-yaml.js CHANGED
@@ -1,59 +1,68 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-yaml — YAML content-safety primitive (b.guardYaml).
3
+ * @module b.guardYaml
4
+ * @nav Guards
5
+ * @title Guard Yaml
4
6
  *
5
- * Threat catalog grounded in current research (multiple 2025-2026
6
- * deserialization + DoS CVEs in popular YAML libraries):
7
- * - CVE-2026-24009 Docling / PyYAML unsafe load → RCE
8
- * - CVE-2026-27807 MarkUs YAML alias billion-laughs DoS
9
- * - CVE-2025-68664 LangChain deserialization RCE
10
- * - CVE-2025-61301 / CVE-2025-61303 YAML library DoS family
11
- * ("Laughter in the Wild" study across 14 libraries / 10 languages)
12
- * - CVE-2022-1471 SnakeYAML constructor RCE
13
- * - CVE-2020-1747 / CVE-2020-14343 PyYAML FullLoader RCE chain
14
- * - CVE-2017-18342 PyYAML python/object/apply RCE
7
+ * @intro
8
+ * YAML content-safety guard defends against the type-coercion,
9
+ * deserialization, and DoS catalog operators face when accepting
10
+ * YAML sourced from user input. All detection runs at the SOURCE
11
+ * level: the operator's downstream parser may be PyYAML, SnakeYAML,
12
+ * js-yaml, libyaml, or another implementation, and the guard
13
+ * refuses hostile inputs before any parser sees them.
15
14
  *
16
- * var rv = b.guardYaml.validate(input, { profile: "strict" });
17
- * var safe = b.guardYaml.parse(input, { profile: "strict" });
18
- * var g = b.guardYaml.gate({ profile: "strict" });
15
+ * Tag-injection RCE defense: language-specific deserialization tag
16
+ * prefixes (`!!python/` / `!!java.` / `!!ruby/` / `!!perl/` /
17
+ * `!!js/` / `!!cs/` / `!!net/` / `!!system.`) plus the `!!apply` /
18
+ * `!!new` / `!!eval` / `!!exec` family are refused regardless of
19
+ * profile under strict. CVE coverage: CVE-2026-24009 Docling/PyYAML
20
+ * unsafe load, CVE-2025-68664 LangChain deserialization, CVE-2022-
21
+ * 1471 SnakeYAML constructor RCE, CVE-2020-1747 / CVE-2020-14343
22
+ * PyYAML FullLoader, CVE-2017-18342 python/object/apply.
19
23
  *
20
- * Threat catalog covered (all source-level operator's downstream
21
- * parser may be pyyaml/snakeyaml/js-yaml; the guard refuses hostile
22
- * sources before any parser sees them):
24
+ * YAML 1.1 vs 1.2 type-coercion attacks: PyYAML and libyaml still
25
+ * default to YAML 1.1 in 2026, which treats unquoted `no` / `yes`
26
+ * / `y` / `n` / `on` / `off` as booleans (the "Norway problem" —
27
+ * country code "NO" parses as false), and `0777`-shaped numerics
28
+ * parse as octal. These shapes are flagged at the source so
29
+ * operators can refuse silently coerced values.
23
30
  *
24
- * 1. Tag-injection RCE language-specific deserialization tags
25
- * with prefixes !!python/ / !!java. / !!ruby/ / !!perl/ / !!js/
26
- * / !!cs/ / !!net/ / !!system. and the !!apply / !!new family.
27
- * Refused regardless of profile under strict.
31
+ * Anchor-bomb (billion laughs) detection: `&anchor` declares,
32
+ * `*alias` references, recursive aliasing amplifies a small input
33
+ * into GiB on parse. Caps via `maxAnchors` + `maxAliasDepth` +
34
+ * `maxNodes`, plus an explicit alias-amplification ratio
35
+ * (aliases / anchors >= 8 fires `alias-explosion`) catches the
36
+ * exponential expansion shape independent of absolute counts.
37
+ * CVE-2026-27807 MarkUs / CVE-2025-61301 / CVE-2025-61303 ("Laughter
38
+ * in the Wild" — 14 libraries / 10 languages) exemplify the family.
28
39
  *
29
- * 2. Anchor / alias recursion (billion laughs) &anchor declares,
30
- * *alias references. Recursive aliasing amplifies a small input
31
- * into GiB on parse. Caps via maxAnchors + maxAliasDepth + total
32
- * node count.
40
+ * Custom-tag exec surface: local `!Foo` and global `!!Bar` user
41
+ * tags suggest a non-safe parser is downstream even when the tag
42
+ * isn't on the language-specific deserialization denylist. Flagged
43
+ * per profile.
33
44
  *
34
- * 3. Multi-document streams operators expecting a single doc
35
- * silently get the first one and ignore the rest, which can mask
36
- * hostile content.
45
+ * Merge-key chain DoS: `<<: *anchor` invokes the YAML 1.1 merge-
46
+ * key spec; chains of merge keys against deeply nested anchors are
47
+ * an additional anchor-chain expansion vector.
37
48
  *
38
- * 4. Norway problem YAML 1.1 (still default in pyyaml + libyaml
39
- * in 2026) treats unquoted no/yes/y/n/on/off as booleans. Country
40
- * code "NO" false.
49
+ * Multi-document streams: operators expecting a single doc silently
50
+ * receive only the first one and ignore the rest — hostile content
51
+ * in subsequent docs slips past validation that ran on the first.
52
+ * The guard refuses `multiDocPolicy === "reject"` and caps via
53
+ * `maxDocuments`.
41
54
  *
42
- * 5. Leading-zero octals 0777 parses as octal 511 in YAML 1.1.
55
+ * Duplicate-key smuggling, BOM placement, and bidi / null / control
56
+ * / zero-width character threats route through the same shared
57
+ * detector backing the guard-json / guard-csv families.
43
58
  *
44
- * 6. Duplicate keys YAML 1.2 SHOULD-unique; parsers silently
45
- * last-wins, same threat shape as JSON duplicate-key smuggling.
59
+ * Profiles: `strict` / `balanced` / `permissive`. Compliance
60
+ * postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. Operators select
61
+ * via `{ profile: "strict" }` or `{ compliance: "hipaa" }`;
62
+ * postures overlay on top of the profile baseline.
46
63
  *
47
- * 7. Local + custom user tags — even when not language-specific,
48
- * surface that suggests a non-safe parser is downstream.
49
- *
50
- * 8. Merge-key chain depth (<<: *anchor) — anchor-chain DoS.
51
- *
52
- * 9. Bidi / null / control / zero-width chars in scalar values.
53
- *
54
- * 10. Anti-DoS caps — total document size, total node count, max
55
- * anchors, max alias depth, max document count, max scalar
56
- * length, max depth.
64
+ * @card
65
+ * YAML content-safety guard defends against the type-coercion, deserialization, and DoS catalog operators face when accepting YAML sourced from user input.
57
66
  */
58
67
 
59
68
  var codepointClass = require("./codepoint-class");
@@ -433,6 +442,60 @@ function _detectDuplicateKeysYaml(text) {
433
442
 
434
443
  // ---- Public surface ----
435
444
 
445
+ /**
446
+ * @primitive b.guardYaml.validate
447
+ * @signature b.guardYaml.validate(input, opts?)
448
+ * @since 0.7.14
449
+ * @status stable
450
+ * @compliance hipaa, pci-dss, gdpr, soc2
451
+ * @related b.guardYaml.parse, b.guardYaml.gate
452
+ *
453
+ * Inspect `input` (string of YAML source) for the full guard-yaml
454
+ * threat catalog without committing to a parsed value. Returns
455
+ * `{ ok, issues, severities }` where `issues` is the aggregated
456
+ * detector output — every dangerous-tag prefix, custom-tag use,
457
+ * anchor / alias amplification, multi-document split, Norway-
458
+ * problem implicit boolean, leading-zero octal, merge-key chain,
459
+ * duplicate-key smuggle, codepoint-class threat, and parse failure
460
+ * is reported with `kind` / `severity` / `ruleId` / `snippet`.
461
+ * Profile-driven (`strict` / `balanced` / `permissive`) and posture-
462
+ * driven (`hipaa` / `pci-dss` / `gdpr` / `soc2`).
463
+ *
464
+ * Detection runs at the source level so the operator's downstream
465
+ * parser (PyYAML / SnakeYAML / js-yaml / libyaml) need not be
466
+ * consulted to identify hostile shapes. A final pass tries the safe-
467
+ * yaml parser and surfaces parse failure as a critical issue.
468
+ *
469
+ * @opts
470
+ * profile: "strict"|"balanced"|"permissive",
471
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
472
+ * tagPolicy: "reject"|"audit"|"allow",
473
+ * aliasPolicy: "reject"|"audit"|"allow",
474
+ * multiDocPolicy: "reject"|"audit"|"allow",
475
+ * norwayPolicy: "reject"|"audit"|"allow",
476
+ * leadingZeroPolicy: "reject"|"audit"|"allow",
477
+ * duplicateKeyPolicy: "reject"|"audit"|"allow",
478
+ * mergeKeyPolicy: "reject"|"audit"|"allow",
479
+ * bidiPolicy: "reject"|"strip"|"audit"|"allow",
480
+ * controlPolicy: "reject"|"strip"|"allow",
481
+ * nullBytePolicy: "reject"|"strip"|"allow",
482
+ * zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
483
+ * safeCoreTagsAllowed: boolean,
484
+ * maxBytes: number, // total source byte cap
485
+ * maxDepth: number, // recursion depth cap
486
+ * maxAnchors: number, // anchor declaration cap
487
+ * maxAliasDepth: number, // alias-chain depth cap
488
+ * maxDocuments: number, // multi-document doc count cap
489
+ * maxNodes: number, // total node count cap
490
+ * maxScalarLength: number, // per-scalar length cap
491
+ *
492
+ * @example
493
+ * var rv = b.guardYaml.validate("!!python/object/new:cls\nargs: [x]\n", {
494
+ * profile: "strict",
495
+ * });
496
+ * rv.ok; // → false
497
+ * rv.issues.some(function (i) { return i.kind === "dangerous-tag"; }); // → true
498
+ */
436
499
  function validate(input, opts) {
437
500
  opts = _resolveOpts(opts);
438
501
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -449,6 +512,41 @@ function validate(input, opts) {
449
512
  return gateContract.aggregateIssues(_detectIssues(input, opts));
450
513
  }
451
514
 
515
+ /**
516
+ * @primitive b.guardYaml.parse
517
+ * @signature b.guardYaml.parse(input, opts?)
518
+ * @since 0.7.14
519
+ * @status stable
520
+ * @related b.guardYaml.validate, b.guardYaml.gate
521
+ *
522
+ * Parse `input` (string of YAML source) into a JavaScript value
523
+ * after the guard-yaml threat catalog clears. Runs the full
524
+ * validate-shape detector, throws `GuardYamlError` on the first
525
+ * critical issue (dangerous tag, alias-explosion, multi-document
526
+ * under reject, parse failure, etc.), then routes through the safe-
527
+ * yaml parser with the configured `maxBytes` / `maxDepth` /
528
+ * `maxNodes` caps.
529
+ *
530
+ * The throw-on-critical pre-flight is what distinguishes guarded
531
+ * parse from a raw yaml-library `load()`: the operator's downstream
532
+ * code never sees deserialization-tag instantiation, billion-laughs
533
+ * expansion, or duplicate-key smuggling because the source is
534
+ * refused before the parser runs.
535
+ *
536
+ * @opts
537
+ * profile: "strict"|"balanced"|"permissive",
538
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
539
+ * tagPolicy: "reject"|"audit"|"allow",
540
+ * aliasPolicy: "reject"|"audit"|"allow",
541
+ * maxBytes: number, maxDepth: number, maxNodes: number,
542
+ *
543
+ * @example
544
+ * var safe = b.guardYaml.parse("name: alice\nage: 30\n", {
545
+ * profile: "strict",
546
+ * });
547
+ * safe.name; // → "alice"
548
+ * safe.age; // → 30
549
+ */
452
550
  function parse(input, opts) {
453
551
  opts = _resolveOpts(opts);
454
552
  if (typeof input !== "string") {
@@ -468,6 +566,35 @@ function parse(input, opts) {
468
566
  });
469
567
  }
470
568
 
569
+ /**
570
+ * @primitive b.guardYaml.gate
571
+ * @signature b.guardYaml.gate(opts?)
572
+ * @since 0.7.14
573
+ * @status stable
574
+ * @compliance hipaa, pci-dss, gdpr, soc2
575
+ * @related b.guardYaml.validate, b.guardYaml.parse, b.staticServe.create, b.fileUpload.create
576
+ *
577
+ * Build a `b.gateContract` gate suitable for plugging into
578
+ * `b.staticServe({ contentSafety: { ".yaml": gate } })`,
579
+ * `b.fileUpload({ contentSafety: { "application/yaml": gate } })`,
580
+ * or any host primitive that consumes the gate-contract shape.
581
+ * Action chain on validation: `serve` (no issues) → `audit-only`
582
+ * (warn-only issues) → `refuse` (any high/critical issue). YAML
583
+ * sanitize is intentionally not offered — there's no safe re-emit
584
+ * for tag-injection / alias-explosion shapes; the only correct
585
+ * response is refusal.
586
+ *
587
+ * @opts
588
+ * profile: "strict"|"balanced"|"permissive",
589
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
590
+ * name: string, // gate identity for audit / observability
591
+ *
592
+ * @example
593
+ * var yamlGate = b.guardYaml.gate({ profile: "strict" });
594
+ * var hostile = Buffer.from("!!python/object/new:cls\nargs: [x]\n", "utf8");
595
+ * var verdict = await yamlGate.check({ bytes: hostile });
596
+ * verdict.action; // → "refuse"
597
+ */
471
598
  function gate(opts) {
472
599
  opts = _resolveOpts(opts);
473
600
  return gateContract.buildGuardGate(
@@ -486,13 +613,83 @@ function gate(opts) {
486
613
  });
487
614
  }
488
615
 
616
+ /**
617
+ * @primitive b.guardYaml.buildProfile
618
+ * @signature b.guardYaml.buildProfile(opts)
619
+ * @since 0.7.14
620
+ * @status stable
621
+ * @related b.guardYaml.gate, b.guardYaml.compliancePosture
622
+ *
623
+ * Compose a derived profile from one or more named bases plus
624
+ * inline overrides. `opts.extends` is a profile name (`"strict"` /
625
+ * `"balanced"` / `"permissive"`) or an array of names; later entries
626
+ * shadow earlier ones. Inline `opts` keys win last. Used to keep
627
+ * operator-defined profiles traceable to a baseline rather than re-
628
+ * typing every key.
629
+ *
630
+ * @opts
631
+ * extends: string|string[], // base profile name(s) to compose
632
+ * ...: any guard-yaml key, // inline override of resolved keys
633
+ *
634
+ * @example
635
+ * var custom = b.guardYaml.buildProfile({
636
+ * extends: "balanced",
637
+ * tagPolicy: "reject",
638
+ * maxAnchors: 8,
639
+ * });
640
+ * custom.tagPolicy; // → "reject"
641
+ * custom.maxAnchors; // → 8
642
+ */
489
643
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
490
644
 
645
+ /**
646
+ * @primitive b.guardYaml.compliancePosture
647
+ * @signature b.guardYaml.compliancePosture(name)
648
+ * @since 0.7.14
649
+ * @status stable
650
+ * @compliance hipaa, pci-dss, gdpr, soc2
651
+ * @related b.guardYaml.gate, b.guardYaml.buildProfile
652
+ *
653
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
654
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of the
655
+ * posture object — the caller may mutate freely. Throws
656
+ * `GuardYamlError("yaml.bad-posture")` on unknown name.
657
+ *
658
+ * @example
659
+ * var posture = b.guardYaml.compliancePosture("hipaa");
660
+ * posture.tagPolicy; // → "reject"
661
+ * posture.forensicSnippetBytes; // → 256
662
+ */
491
663
  function compliancePosture(name) {
492
664
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "yaml");
493
665
  }
494
666
 
495
667
  var _yamlRulePacks = gateContract.makeRulePackLoader(GuardYamlError, "yaml");
668
+ /**
669
+ * @primitive b.guardYaml.loadRulePack
670
+ * @signature b.guardYaml.loadRulePack(pack)
671
+ * @since 0.7.14
672
+ * @status stable
673
+ * @related b.guardYaml.gate
674
+ *
675
+ * Register an operator-supplied rule pack with the guard-yaml
676
+ * registry. The pack is identified by `pack.id` (non-empty string)
677
+ * and stored for later inspection / dispatch by gates that opt in
678
+ * via `opts.rulePackId`. Returns the pack object unchanged on
679
+ * success; throws `GuardYamlError("yaml.bad-opt")` when `pack` is
680
+ * missing or `pack.id` is not a non-empty string.
681
+ *
682
+ * @example
683
+ * var pack = b.guardYaml.loadRulePack({
684
+ * id: "deploy-keys",
685
+ * rules: [
686
+ * { id: "no-image-latest", severity: "high",
687
+ * detect: function (text) { return /image:\s*\S+:latest\b/.test(text); },
688
+ * reason: "deployment YAML must pin image tag (no :latest)" },
689
+ * ],
690
+ * });
691
+ * pack.id; // → "deploy-keys"
692
+ */
496
693
  var loadRulePack = _yamlRulePacks.load;
497
694
 
498
695
  module.exports = {
package/lib/honeytoken.js CHANGED
@@ -1,36 +1,33 @@
1
1
  "use strict";
2
2
  /**
3
- * b.honeytoken — canary credential framework. Generates decoy values
4
- * (fake api-key shapes, fake admin URLs, fake DB row references) that
5
- * are NEVER handed to a real client; their presence in a request,
6
- * log, or DB lookup means an attacker found something they shouldn't
7
- * have. The framework registers each token at issuance and refuses
8
- * silently in production but always emits a `honeytoken.tripped`
9
- * audit row on any positive lookup.
3
+ * @module b.honeytoken
4
+ * @nav Identity
5
+ * @title Honeytoken
10
6
  *
11
- * var honey = b.honeytoken.create({ audit: b.audit });
7
+ * @intro
8
+ * Framework-seeded canary records that trigger an audit alert on
9
+ * read; integrates with sealed columns. The framework generates
10
+ * decoy values (fake api-key shapes, fake admin URLs, fake DB row
11
+ * references) that are NEVER handed to a real client. Their
12
+ * presence in a request, log, or DB lookup means an attacker
13
+ * reached something they shouldn't have. Every positive lookup
14
+ * emits a `honeytoken.tripped` audit row with the observing
15
+ * actor's 5 W's so a SOC operator can pivot directly to the
16
+ * compromise.
12
17
  *
13
- * var token = honey.issue({
14
- * kind: "apiKey",
15
- * metadata: { plantedAt: "GET /admin/keys/404", linkedTo: "u_42" },
16
- * });
17
- * // { value: "bk_canary_8f3a7b2e0c…", id: "ht_<hex>" }
18
- *
19
- * if (honey.lookup(req.headers["x-api-key"])) {
20
- * // attacker is using the canary; tripped event already audited
21
- * return res.status(403).end();
22
- * }
18
+ * Canary value shapes (`kind`):
19
+ * - `"apiKey"` — `bk_canary_<hex>` (mirrors b.apiKey shape)
20
+ * - `"session"` `bks_canary_<hex>` (mirrors b.session shape)
21
+ * - `"url"` — `/admin/canary-<hex>` (planted as a clickable link)
22
+ * - `"rowId"` `ht_canary_<hex>` (planted as a fake foreign key)
23
23
  *
24
- * Canary value shapes (`kind`):
25
- * - "apiKey" → `bk_canary_<32 hex>` (matches b.apiKey shape)
26
- * - "session" → `bks_canary_<48 hex>` (matches b.session shape)
27
- * - "url" → `/admin/canary-<32 hex>` (planted as a clickable link)
28
- * - "rowId" → `ht_canary_<32 hex>` (planted as a fake foreign key)
24
+ * Audit shape:
25
+ * - `honeytoken.issued` — outcome=success; metadata { id, kind }
26
+ * - `honeytoken.tripped` outcome=failure; metadata { id, kind,
27
+ * metadata, observedAt, observedActor }
29
28
  *
30
- * Audit shape:
31
- * - `honeytoken.issued` outcome=success; metadata: { id, kind }
32
- * - `honeytoken.tripped` — outcome=failure; metadata: { id, kind,
33
- * metadata, observedAt, observedActor }
29
+ * @card
30
+ * Framework-seeded canary records that trigger an audit alert on read; integrates with sealed columns.
34
31
  */
35
32
 
36
33
  var crypto = require("./crypto");
@@ -49,6 +46,45 @@ var KINDS = Object.freeze({
49
46
  rowId: function () { return "ht_canary_" + crypto.generateToken(16); }, // allow:raw-byte-literal — 16-byte canary entropy
50
47
  });
51
48
 
49
+ /**
50
+ * @primitive b.honeytoken.create
51
+ * @signature b.honeytoken.create(opts)
52
+ * @since 0.8.40
53
+ * @status stable
54
+ * @compliance soc2, nis2, dora
55
+ * @related b.audit, b.apiKey.create, b.session
56
+ *
57
+ * Build an in-process honeytoken registry. Returns a handle exposing
58
+ * `issue(spec)` to mint a canary, `lookup(value, observedActor?)` to
59
+ * test an incoming value (audit-emits `honeytoken.tripped` on hit),
60
+ * `revoke(id)` to retire a canary, and `size()` for diagnostics. The
61
+ * registry is per-process — operators running multiple workers wire
62
+ * a shared b.audit sink and reconcile alerts at the audit layer
63
+ * rather than sharing the registry across nodes (a canary's value
64
+ * is what's planted in the trap, not what's known to the framework).
65
+ *
66
+ * @opts
67
+ * audit: b.audit, // audit sink for issued / tripped events (optional, recommended)
68
+ *
69
+ * @example
70
+ * var honey = b.honeytoken.create({ audit: b.audit });
71
+ *
72
+ * var canary = honey.issue({
73
+ * kind: "apiKey",
74
+ * metadata: { plantedAt: "GET /admin/keys/list", linkedTo: "user-42" },
75
+ * });
76
+ * // → { id: "ht_<hex>", value: "bk_canary_<hex>" }
77
+ *
78
+ * // Plant the canary value somewhere an attacker who's escalated
79
+ * // privileges might find it (a fake row in an admin listing, a
80
+ * // dummy env-var leaked into a traceback page, etc.).
81
+ *
82
+ * // On every incoming credential, check for canary use:
83
+ * if (honey.lookup(req.headers["x-api-key"], { ip: req.ip })) {
84
+ * // tripped — audit already emitted; respond as if invalid.
85
+ * return res.writeHead(403).end();
86
+ * }
87
+ */
52
88
  function create(opts) {
53
89
  opts = opts || {};
54
90
  validateOpts(opts, ["audit"], "honeytoken.create");
@@ -1,4 +1,35 @@
1
1
  "use strict";
2
+ /**
3
+ * @module b.htmlBalance
4
+ * @nav Tools
5
+ * @title HTML Balance
6
+ *
7
+ * @intro
8
+ * HTML tag-balance verification. Walks an HTML fragment and refuses
9
+ * unbalanced container tags, orphan close tags, mismatched-name
10
+ * close tags, void-element close tags, unterminated comments, and
11
+ * unterminated raw-text elements (`<script>` / `<style>` / `<title>`
12
+ * / `<textarea>`). Returns `null` when balanced; otherwise an issue
13
+ * object with `code`, `message`, `line`, and `column` so operators
14
+ * can surface "you forgot to close this tag at line N" feedback at
15
+ * save time.
16
+ *
17
+ * Intent is operator-side structural feedback, not security
18
+ * validation. Attribute syntax, custom-element / shadow-DOM
19
+ * namespaces, script/style content semantics, and XSS-class checks
20
+ * are all out of scope here — the security pass is `b.guardHtml`,
21
+ * composed by `b.htmlBalance.checkSafe` for operators wanting both
22
+ * gates in one call.
23
+ *
24
+ * Void elements (`<br>`, `<img>`, `<input>`, …) are recognised and
25
+ * never expected to close. Self-closing slash forms (`<foo />`)
26
+ * are honoured. Raw-text elements skip their content entirely so
27
+ * `<script>` bodies containing literal `<` / `</` aren't mis-read
28
+ * as markup.
29
+ *
30
+ * @card
31
+ * HTML tag-balance verification.
32
+ */
2
33
  /**
3
34
  * html-balance — minimal HTML structural sanity check.
4
35
  *
@@ -45,6 +76,28 @@ function _posToLineColumn(src, pos) {
45
76
  return { line: line, column: col };
46
77
  }
47
78
 
79
+ /**
80
+ * @primitive b.htmlBalance.check
81
+ * @signature b.htmlBalance.check(html)
82
+ * @since 0.1.0
83
+ * @related b.htmlBalance.checkSafe, b.guardHtml
84
+ *
85
+ * Returns `null` when `html` is balanced; otherwise an issue object
86
+ * `{ code, message, line, column }`. Issue codes: `html/unterminated-comment`,
87
+ * `html/unterminated-tag`, `html/orphan-close`, `html/mismatched-close`,
88
+ * `html/void-close`, `html/unclosed-raw-text`, `html/unclosed-tag`.
89
+ * Non-string inputs and the empty string return `null` (nothing to
90
+ * balance). Self-closing slash forms and HTML5 void elements are
91
+ * recognised; raw-text elements skip their bodies so `<script>` JS
92
+ * containing `<` characters doesn't mis-balance.
93
+ *
94
+ * @example
95
+ * b.htmlBalance.check("<div><p>hello</p></div>");
96
+ * // → null
97
+ *
98
+ * var issue = b.htmlBalance.check("<div><p>hello</div>");
99
+ * // → { code: "html/mismatched-close", message: "</div> at line 1 col 14 does not match open <p> at line 1 col 6", line: 1, column: 14 }
100
+ */
48
101
  function check(html) {
49
102
  if (typeof html !== "string" || html.length === 0) return null;
50
103
  var stack = []; // [{ tag, openPos }]
@@ -242,6 +295,36 @@ function check(html) {
242
295
  var lazyRequire = require("./lazy-require");
243
296
  var _guardHtml = lazyRequire(function () { return require("./guard-html"); });
244
297
 
298
+ /**
299
+ * @primitive b.htmlBalance.checkSafe
300
+ * @signature b.htmlBalance.checkSafe(html, opts?)
301
+ * @since 0.7.7
302
+ * @related b.htmlBalance.check, b.guardHtml
303
+ *
304
+ * Composes `check()` (cheap structural well-formedness) with
305
+ * `b.guardHtml.gate({ profile })` (security-class checks against the
306
+ * strict / balanced / permissive vocabulary plus an optional
307
+ * compliance posture). Returns `{ balanceIssue, guardIssues, ok }`
308
+ * so callers can distinguish a structural problem from a content-
309
+ * safety reject and decide which path to surface to the operator.
310
+ *
311
+ * `opts.contentSafety` mirrors the same shape as `b.fileUpload({
312
+ * contentSafety })` and `b.staticServe({ contentSafety })` so a
313
+ * single `{ profile, posture }` value flows across the stack
314
+ * unchanged.
315
+ *
316
+ * @opts
317
+ * profile: "strict" | "balanced" | "permissive",
318
+ * posture: string, // compliance posture name; e.g. "hipaa", "pci-dss"
319
+ * contentSafety: { profile, posture }, // shared shape with b.fileUpload / b.staticServe
320
+ *
321
+ * @example
322
+ * var rv = b.htmlBalance.checkSafe("<div onclick=\"x()\">hi</div>", { profile: "strict" });
323
+ * // → { balanceIssue: null, guardIssues: [{ kind: "event-handler-attribute", ... }], ok: false }
324
+ *
325
+ * b.htmlBalance.checkSafe("<p>hello</p>", { profile: "strict" });
326
+ * // → { balanceIssue: null, guardIssues: [], ok: true }
327
+ */
245
328
  function checkSafe(html, opts) {
246
329
  opts = opts || {};
247
330
  var balanceIssue = check(html);