@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-csv.js CHANGED
@@ -1,56 +1,57 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-csv — CSV content-safety primitive (b.guardCsv).
4
- *
5
- * Wraps lib/csv.js (RFC 4180 parse + stringify) with the broader threat
6
- * catalog operators face when emitting CSVs from user-supplied data,
7
- * plus the b.gateContract composition contract for use as a gate inside
8
- * b.staticServe / b.fileUpload / b.mail / b.objectStore.
9
- *
10
- * var out = b.guardCsv.serialize(rows, { profile: "strict" });
11
- * var rv = b.guardCsv.validate(input, { profile: "strict" });
12
- * var s = b.guardCsv.sanitize(input, { profile: "balanced" });
13
- * var g = b.guardCsv.gate({ profile: "strict" });
14
- * b.staticServe.create({ contentSafety: { ".csv": g } });
15
- *
16
- * Threat-detection regex literals are composed PROGRAMMATICALLY from
17
- * numeric codepoint ranges (BIDI_RANGES / C0_CTRL_RANGES / etc.) so the
18
- * source file never embeds the attack characters themselves — only their
19
- * codepoint numbers. This mirrors the way an attacker would compose the
20
- * payload (programmatic codepoint emission, not literal typing) and
21
- * keeps the source ASCII-clean (zero irregular-whitespace lint findings,
22
- * no eslint-disable comments, machine-greppable as data tables).
23
- *
24
- * Threat catalog covered:
25
- *
26
- * - Formula injection (5 modes: prefix-tab / prefix-quote /
27
- * wrap-with-quotes-and-prefix / reject / allowlist) with all 8
28
- * ASCII triggers (= + - @ TAB CR LF |) plus full-width variants
29
- * (U+FF1D / U+FF0B / U+FF0D / U+FF20) per OWASP locale catalog.
30
- * - Dangerous-function denylist WEBSERVICE / HYPERLINK / IMAGE /
31
- * IMPORT* / RTD / DDE / CALL / GOOGLEFINANCE / GOOGLETRANSLATE.
32
- * - Unicode bidi override (CVE-2021-42574 Trojan Source).
33
- * - Homoglyph detection (Cyrillic / Greek / fullwidth Latin) when
34
- * mixed with ASCII letters in the same cell.
35
- * - C0 control chars (minus tab / lf / cr — those are dialect chars
36
- * the parser handles separately).
37
- * - Null byte detection (single canonical handling — strip / reject).
38
- * - BOM injection mid-stream (any BOM past byte 0).
39
- * - Zero-width chars (ZWSP / ZWNJ / ZWJ / WJ / SHY).
40
- * - Dialect ambiguity (mixed line endings) strict refuses.
41
- * - CSV-bomb caps: per-cell, total, sanitize amplification ratio.
42
- * - Numeric precision loss (above Number.MAX_SAFE_INTEGER → decimal
43
- * string per policy).
44
- * - Trailing whitespace exfiltration policy (trim / preserve / reject).
45
- * - PII redaction via composes b.redact when piiPolicy === "redact".
46
- * - Schema-bound serializer with type / regex / range / nullable
47
- * validation.
48
- * - Profiles: strict (OWASP-aligned, prefix-tab default per OWASP) /
49
- * balanced / permissive / email-attachment.
50
- * - Compliance postures: hipaa / pci-dss / gdpr / soc2.
51
- * - Operator extensibility: profile composition, custom rules, hooks
52
- * (beforeCheck / afterCheck / onIssue / onSanitize / onRefuse /
53
- * onAudit), threat-intel feeds, sandbox isolation, snapshot tests.
3
+ * @module b.guardCsv
4
+ * @nav Guards
5
+ * @title Guard Csv
6
+ *
7
+ * @intro
8
+ * CSV content-safety guard defends against the broader threat
9
+ * catalog operators face when emitting or accepting CSVs sourced from
10
+ * user input. `b.csv.parse` / `b.csv.stringify` handle RFC 4180
11
+ * shape; this module layers the security catalog on top.
12
+ *
13
+ * CSV-injection / formula-trigger defense: spreadsheet evaluators
14
+ * (Excel / LibreOffice / Google Sheets) treat any cell beginning with
15
+ * `=`, `+`, `-`, `@`, TAB, CR, LF, or `|` as a formula — including
16
+ * exfiltration vectors like `=WEBSERVICE(...)`, `=HYPERLINK(...)`,
17
+ * `=IMPORTXML(...)`. Full-width variants (U+FF1D `=`, U+FF0B `+`,
18
+ * U+FF0D `-`, U+FF20 `@`) are caught alongside the ASCII triggers
19
+ * per the OWASP locale catalog. Five mitigation modes apply:
20
+ * `prefix-tab` (OWASP-recommended, prepends TAB so the evaluator
21
+ * treats the cell as text), `prefix-quote` (legacy `'` prefix),
22
+ * `wrap-with-quotes-and-prefix` (email-attachment posture),
23
+ * `reject` (throw), `allowlist` (only documented safe functions
24
+ * like SUM / AVERAGE pass through unprefixed).
25
+ *
26
+ * Unicode bidi/zero-width strip: CVE-2021-42574 Trojan Source bidi
27
+ * overrides (U+202A-202E, U+2066-2069) are rejected or stripped
28
+ * per profile; zero-width characters (ZWSP / ZWNJ / ZWJ / WJ / SHY)
29
+ * always strip. Leading bidi/zero-width prefixes are stripped before
30
+ * the formula scan so a cell beginning with U+200B`=SUM(...)` cannot
31
+ * slip past the start-anchor check.
32
+ *
33
+ * CSV-bomb caps: per-cell (`maxCellBytes`, default 64 KiB), total
34
+ * (`maxTotalBytes`, default 1 GiB), row count (`maxRows`, default
35
+ * ~1 M), column count (`maxColumns`, default 1024), and a sanitize
36
+ * amplification ratio (`sanitizeAmplificationCap`, default 1.5x)
37
+ * that refuses pathological re-quote expansions.
38
+ *
39
+ * Doubled-quote escape is delegated to `b.csv.stringify` every
40
+ * cell value containing the delimiter, the quote char, CR, or LF
41
+ * is wrapped in quotes with embedded quotes doubled per RFC 4180.
42
+ *
43
+ * Profiles: `strict` / `balanced` / `permissive` /
44
+ * `email-attachment`. Compliance postures: `hipaa` / `pci-dss` /
45
+ * `gdpr` / `soc2`. Operators select via `{ profile: "strict" }` or
46
+ * `{ compliance: "hipaa" }`; postures overlay on top of the
47
+ * profile baseline.
48
+ *
49
+ * Threat-detection regex literals are composed programmatically
50
+ * from numeric codepoint ranges so the source file stays pure
51
+ * ASCII never embeds the attack characters themselves.
52
+ *
53
+ * @card
54
+ * CSV content-safety guard — defends against the broader threat catalog operators face when emitting or accepting CSVs sourced from user input.
54
55
  */
55
56
 
56
57
  var codepointClass = require("./codepoint-class");
@@ -449,6 +450,54 @@ function _resolveOpts(opts) {
449
450
 
450
451
  // ---- Cell-level escape with full threat application ----
451
452
 
453
+ /**
454
+ * @primitive b.guardCsv.escapeCell
455
+ * @signature b.guardCsv.escapeCell(value, opts?)
456
+ * @since 0.7.5
457
+ * @status stable
458
+ * @related b.guardCsv.serialize, b.guardCsv.gate, b.csv.stringify
459
+ *
460
+ * Apply the full guard-csv threat catalog to a single cell value:
461
+ * formula-prefix mitigation, null-byte / C0-control / bidi handling,
462
+ * trailing-whitespace policy, numeric-precision policy, and BigInt
463
+ * disposition. Returns the safe string form. Throws `GuardCsvError`
464
+ * when a `reject` policy fires (formula-trigger under
465
+ * `formulaInjectionPolicy: "reject"`, control char under
466
+ * `controlCharPolicy: "reject"`, etc.) or when the cell exceeds
467
+ * `maxCellBytes`.
468
+ *
469
+ * Used internally by `b.guardCsv.serialize` per cell; exposed
470
+ * directly for operators that emit CSV through their own writer
471
+ * (streaming exports, third-party libraries) and only need the
472
+ * per-cell defense.
473
+ *
474
+ * @opts
475
+ * formulaInjectionPolicy: "prefix-tab"|"prefix-quote"|"wrap-with-quotes-and-prefix"|"reject"|"allowlist",
476
+ * formulasAllowlist: string[], // when policy === "allowlist"
477
+ * bidiCharPolicy: "reject"|"strip"|"audit"|"allow",
478
+ * controlCharPolicy: "reject"|"strip"|"allow",
479
+ * nullByteHandling: "reject"|"strip"|"allow",
480
+ * trailingWhitespacePolicy: "trim"|"preserve"|"reject",
481
+ * numericPrecisionPolicy: "decimal-string-above-safe-int"|"scientific"|"reject-bigint",
482
+ * maxCellBytes: number, // default 65536 (64 KiB)
483
+ *
484
+ * @example
485
+ * var safe = b.guardCsv.escapeCell("=cmd|x", { formulaInjectionPolicy: "prefix-tab" });
486
+ * safe; // → "\t=cmd|x"
487
+ *
488
+ * // Reject mode throws GuardCsvError instead of disarming.
489
+ * try {
490
+ * b.guardCsv.escapeCell("+1234567", { formulaInjectionPolicy: "reject" });
491
+ * } catch (e) {
492
+ * e.code; // → "csv.formula-injection"
493
+ * }
494
+ *
495
+ * // Numeric precision: above MAX_SAFE_INTEGER, write as decimal string.
496
+ * var huge = b.guardCsv.escapeCell(9007199254740993, {
497
+ * numericPrecisionPolicy: "decimal-string-above-safe-int",
498
+ * });
499
+ * huge; // → "9007199254740993"
500
+ */
452
501
  function escapeCell(value, opts) {
453
502
  opts = Object.assign({}, DEFAULTS, opts || {});
454
503
  var str = value == null ? "" : String(value);
@@ -516,6 +565,41 @@ function escapeCell(value, opts) {
516
565
 
517
566
  // ---- Schema-bound serializer ----
518
567
 
568
+ /**
569
+ * @primitive b.guardCsv.schema
570
+ * @signature b.guardCsv.schema(spec)
571
+ * @since 0.7.5
572
+ * @status stable
573
+ * @related b.guardCsv.serialize, b.guardCsv.validate
574
+ *
575
+ * Build a schema-bound serializer/validator pair. Each row's column
576
+ * values are checked against the column's `type` (`"string"` /
577
+ * `"number"` / `"boolean"`), optional `regex`, optional `min` / `max`
578
+ * (for numbers), and `nullable` flag before the row reaches
579
+ * `serialize`. Type / range / regex / null violations throw
580
+ * `GuardCsvError` with codes `csv.schema-type` / `csv.schema-range`
581
+ * / `csv.schema-regex` / `csv.schema-null` and the offending row
582
+ * index — operators get the failing-row coordinates without parsing
583
+ * the error string.
584
+ *
585
+ * Returns `{ serialize, validate, columns }`. The returned
586
+ * `serialize` accepts the same opts as `b.guardCsv.serialize` and
587
+ * applies the column ordering automatically.
588
+ *
589
+ * @example
590
+ * var bound = b.guardCsv.schema({
591
+ * columns: [
592
+ * { name: "email", type: "string", regex: /^[^@]+@[^@]+$/ },
593
+ * { name: "age", type: "number", min: 0, max: 150, nullable: true },
594
+ * ],
595
+ * });
596
+ *
597
+ * var out = bound.serialize([
598
+ * { email: "alice@example.com", age: 30 },
599
+ * { email: "bob@example.com", age: null },
600
+ * ], { profile: "strict" });
601
+ * out.indexOf("alice@example.com") !== -1; // → true
602
+ */
519
603
  function schema(spec) {
520
604
  validateOpts.requireObject(spec, "guardCsv.schema", GuardCsvError);
521
605
  if (!Array.isArray(spec.columns)) {
@@ -588,6 +672,52 @@ function schema(spec) {
588
672
 
589
673
  // ---- Module-level entry points ----
590
674
 
675
+ /**
676
+ * @primitive b.guardCsv.serialize
677
+ * @signature b.guardCsv.serialize(rows, opts?)
678
+ * @since 0.7.5
679
+ * @status stable
680
+ * @compliance hipaa, pci-dss, gdpr, soc2
681
+ * @related b.guardCsv.escapeCell, b.guardCsv.gate, b.csv.stringify
682
+ *
683
+ * Emit RFC 4180 CSV from `rows` (array of objects or array of
684
+ * arrays) with the full guard-csv threat catalog applied per cell
685
+ * — formula-prefix mitigation, bidi/null/control handling,
686
+ * trailing-whitespace policy, numeric-precision policy. Doubled-
687
+ * quote escape is delegated to `b.csv.stringify`. Caps enforced:
688
+ * `maxRows`, `maxCellBytes`, `maxColumns`, `maxTotalBytes` (each
689
+ * a positive finite integer; passing `Infinity` throws).
690
+ *
691
+ * When `piiPolicy: "redact"` is set and an `opts.redact` instance
692
+ * is passed (typically `b.redact.create(...)`), every emitted
693
+ * string cell is run through `redact.string(...)` before
694
+ * stringification. The HIPAA / PCI-DSS / GDPR postures default
695
+ * `piiPolicy` to `"redact"`.
696
+ *
697
+ * @opts
698
+ * profile: "strict"|"balanced"|"permissive"|"email-attachment",
699
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
700
+ * headers: string[]|false, // explicit column order; false suppresses header row
701
+ * delimiter: string, // default ","
702
+ * lineEnding: string, // default "\r\n"
703
+ * bomPrefix: boolean, // prepend U+FEFF (Excel-friendly)
704
+ * maxRows: number, // default 1048576
705
+ * maxCellBytes: number, // default 65536
706
+ * maxColumns: number, // default 1024
707
+ * maxTotalBytes: number, // default 1073741824 (1 GiB)
708
+ * piiPolicy: "preserve"|"redact",
709
+ * redact: b.redact instance, // required when piiPolicy === "redact"
710
+ *
711
+ * @example
712
+ * var out = b.guardCsv.serialize([
713
+ * { name: "alice", note: "=WEBSERVICE(\"http://x\")" },
714
+ * { name: "bob", note: "ok" },
715
+ * ], { profile: "strict" });
716
+ *
717
+ * // Formula trigger disarmed with a leading TAB per OWASP guidance:
718
+ * out.indexOf("\t=WEBSERVICE") !== -1; // → true
719
+ * out.indexOf("\r\n") !== -1; // → true
720
+ */
591
721
  function serialize(rows, opts) {
592
722
  opts = _resolveOpts(opts);
593
723
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -663,11 +793,89 @@ function serialize(rows, opts) {
663
793
  return out;
664
794
  }
665
795
 
796
+ /**
797
+ * @primitive b.guardCsv.validate
798
+ * @signature b.guardCsv.validate(input, opts?)
799
+ * @since 0.7.5
800
+ * @status stable
801
+ * @compliance hipaa, pci-dss, gdpr, soc2
802
+ * @related b.guardCsv.sanitize, b.guardCsv.gate
803
+ *
804
+ * Inspect `input` (string or Buffer of CSV text) and return
805
+ * `{ ok, issues, summary }`. Each issue carries `{ kind, severity,
806
+ * ruleId, location, snippet }` with severity in
807
+ * `"warn"|"high"|"critical"`. Detected: BOM mid-stream, Unicode
808
+ * bidi override (CVE-2021-42574), C0 control char, null byte,
809
+ * homoglyph, zero-width char, formula-prefix cell (bidi/zero-width
810
+ * leading prefix is stripped before the scan), dangerous-function
811
+ * denylist hit, mixed line endings (when `dialectPolicy: "strict"`).
812
+ * Pure inspection — never mutates input or throws.
813
+ *
814
+ * @opts
815
+ * profile: "strict"|"balanced"|"permissive"|"email-attachment",
816
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
817
+ * bidiCharPolicy: "reject"|"strip"|"audit"|"allow",
818
+ * controlCharPolicy: "reject"|"strip"|"allow",
819
+ * nullByteHandling: "reject"|"strip"|"allow",
820
+ * homoglyphPolicy: "audit"|"strip"|"allow",
821
+ * formulaInjectionPolicy: "prefix-tab"|"prefix-quote"|"wrap-with-quotes-and-prefix"|"reject"|"audit-only"|"allow",
822
+ * dangerousFunctions: string[],
823
+ * dialectPolicy: "strict"|"permissive",
824
+ *
825
+ * @example
826
+ * var rv = b.guardCsv.validate("name,formula\r\nalice,=WEBSERVICE(\"x\")\r\n", {
827
+ * profile: "strict",
828
+ * });
829
+ * rv.ok; // → false
830
+ * rv.issues.some(function (i) { return i.kind === "dangerous-function"; }); // → true
831
+ */
666
832
  function validate(input, opts) {
667
833
  opts = _resolveOpts(opts);
668
834
  return gateContract.runIssueValidator(input, opts, _detectIssues);
669
835
  }
670
836
 
837
+ /**
838
+ * @primitive b.guardCsv.sanitize
839
+ * @signature b.guardCsv.sanitize(input, opts?)
840
+ * @since 0.7.5
841
+ * @status stable
842
+ * @related b.guardCsv.validate, b.guardCsv.gate
843
+ *
844
+ * Best-effort cleanup of `input` (string or Buffer): strips leading
845
+ * BOM (when `bomPrefix: false`), bidi override chars (when
846
+ * `bidiCharPolicy: "strip"`), C0 control chars (when
847
+ * `controlCharPolicy: "strip"`), null bytes (when
848
+ * `nullByteHandling: "strip"`), zero-width chars (always), and
849
+ * trailing whitespace per `trailingWhitespacePolicy`. Refuses
850
+ * pathological expansion: when the sanitized output exceeds
851
+ * `sanitizeAmplificationCap` (default 1.5x) the function throws
852
+ * `GuardCsvError("csv.sanitize-amplified")` — sanitize is a
853
+ * shrinking operation by contract, never a growing one.
854
+ *
855
+ * Note: sanitize does NOT prepend formula-trigger mitigations to
856
+ * cells (that's `b.guardCsv.serialize` / `b.guardCsv.escapeCell`'s
857
+ * job, applied during emission). Use the `gate` action chain for
858
+ * accept-side defense — it sanitizes, re-parses, and re-serializes
859
+ * with the formula mitigation baked in.
860
+ *
861
+ * @opts
862
+ * profile: "strict"|"balanced"|"permissive"|"email-attachment",
863
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
864
+ * bidiCharPolicy: "reject"|"strip"|"audit"|"allow",
865
+ * controlCharPolicy: "reject"|"strip"|"allow",
866
+ * nullByteHandling: "reject"|"strip"|"allow",
867
+ * homoglyphPolicy: "audit"|"strip"|"allow",
868
+ * trailingWhitespacePolicy: "trim"|"preserve"|"reject",
869
+ * sanitizeAmplificationCap: number, // default 1.5
870
+ *
871
+ * @example
872
+ * // Build hostile input programmatically so the source stays ASCII.
873
+ * var ZWSP = String.fromCharCode(0x200B);
874
+ * var clean = b.guardCsv.sanitize("name,note\r\nalice,hi" + ZWSP + "\r\n", {
875
+ * profile: "balanced",
876
+ * });
877
+ * clean.indexOf(ZWSP) === -1; // → true
878
+ */
671
879
  function sanitize(input, opts) {
672
880
  opts = _resolveOpts(opts);
673
881
  var text = typeof input === "string"
@@ -686,6 +894,29 @@ function sanitize(input, opts) {
686
894
  return sanitized;
687
895
  }
688
896
 
897
+ /**
898
+ * @primitive b.guardCsv.detect
899
+ * @signature b.guardCsv.detect(input)
900
+ * @since 0.7.5
901
+ * @status stable
902
+ * @related b.guardCsv.validate, b.csv.parse
903
+ *
904
+ * Sniff dialect heuristics from `input` (string or Buffer): most-
905
+ * frequent delimiter on the first line (`","`, `";"`, `"\t"`,
906
+ * `"|"`), dominant line-ending, header presence (first line starts
907
+ * with an ASCII letter), encoding hint (`"utf-8"` vs `"utf-8-sig"`
908
+ * when a leading BOM is present), and a single-pass `dialect`
909
+ * verdict (`"consistent"` vs `"mixed"` line endings). Returns a
910
+ * confidence score in `[0, 1]`. Pure inspection.
911
+ *
912
+ * @example
913
+ * var d = b.guardCsv.detect("name,age\r\nalice,30\r\nbob,40\r\n");
914
+ * d.delimiter; // → ","
915
+ * d.lineEnding; // → "\r\n"
916
+ * d.hasHeader; // → true
917
+ * d.encoding; // → "utf-8"
918
+ * d.dialect; // → "consistent"
919
+ */
689
920
  function detect(input) {
690
921
  var text = typeof input === "string"
691
922
  ? input
@@ -724,6 +955,51 @@ function detect(input) {
724
955
 
725
956
  // ---- Gate factory (b.gateContract shape) ----
726
957
 
958
+ /**
959
+ * @primitive b.guardCsv.gate
960
+ * @signature b.guardCsv.gate(opts?)
961
+ * @since 0.7.5
962
+ * @status stable
963
+ * @compliance hipaa, pci-dss, gdpr, soc2
964
+ * @related b.guardCsv.validate, b.guardCsv.sanitize, b.staticServe.create, b.fileUpload.create
965
+ *
966
+ * Build a `b.gateContract` gate suitable for plugging into
967
+ * `b.staticServe({ contentSafety: { ".csv": gate } })`,
968
+ * `b.fileUpload({ contentSafety: { "text/csv": gate } })`,
969
+ * `b.mail`, or `b.objectStore`. Action chain on validation:
970
+ * `serve` (no issues) → `audit-only` (warn-only issues) →
971
+ * `sanitize` (critical/high but no `reject` policy active —
972
+ * sanitize, re-parse, re-serialize so formula mitigation lands)
973
+ * → `refuse` (critical/high under any `reject` policy, or when
974
+ * sanitize fails / amplifies past cap).
975
+ *
976
+ * Operator extensibility: pass `operatorRules: [{ id, severity,
977
+ * detect: fn(ctx)→boolean, reason }]` to inject custom detectors
978
+ * alongside the built-in catalog. Rules run best-effort — a
979
+ * throwing detector is silently skipped (the framework cannot
980
+ * crash a request because an operator rule mishandled bytes).
981
+ *
982
+ * @opts
983
+ * profile: "strict"|"balanced"|"permissive"|"email-attachment",
984
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
985
+ * name: string, // gate identity for audit / observability
986
+ * operatorRules: [{ id: string, severity: "warn"|"high"|"critical",
987
+ * detect: function, reason: string }],
988
+ *
989
+ * @example
990
+ * var csvGate = b.guardCsv.gate({ profile: "strict" });
991
+ *
992
+ * // Wire into staticServe so every served .csv runs through the gate.
993
+ * var serve = b.staticServe.create({
994
+ * root: "/var/data",
995
+ * contentSafety: { ".csv": csvGate },
996
+ * });
997
+ *
998
+ * // Direct invocation for an upload pipeline:
999
+ * var hostile = Buffer.from("name,formula\r\nalice,=cmd|x\r\n", "utf8");
1000
+ * var verdict = await csvGate.check({ bytes: hostile });
1001
+ * verdict.action; // → "refuse"
1002
+ */
727
1003
  function gate(opts) {
728
1004
  opts = _resolveOpts(opts);
729
1005
  return gateContract.buildGuardGate(
@@ -784,13 +1060,83 @@ function gate(opts) {
784
1060
  });
785
1061
  }
786
1062
 
1063
+ /**
1064
+ * @primitive b.guardCsv.buildProfile
1065
+ * @signature b.guardCsv.buildProfile(opts)
1066
+ * @since 0.7.5
1067
+ * @status stable
1068
+ * @related b.guardCsv.gate, b.guardCsv.compliancePosture
1069
+ *
1070
+ * Compose a derived profile from one or more named bases plus
1071
+ * inline overrides. `opts.extends` is a profile name (`"strict"`
1072
+ * / `"balanced"` / `"permissive"` / `"email-attachment"`) or an
1073
+ * array of names; later entries shadow earlier ones. Inline
1074
+ * `opts` keys win last. Used to keep operator-defined profiles
1075
+ * traceable to a baseline rather than re-typing every key.
1076
+ *
1077
+ * @opts
1078
+ * extends: string|string[], // base profile name(s) to compose
1079
+ * ...: any guard-csv key, // inline override of resolved keys
1080
+ *
1081
+ * @example
1082
+ * var custom = b.guardCsv.buildProfile({
1083
+ * extends: "strict",
1084
+ * trailingWhitespacePolicy: "preserve",
1085
+ * bomPrefix: true,
1086
+ * });
1087
+ * custom.formulaInjectionPolicy; // → "prefix-tab"
1088
+ * custom.bomPrefix; // → true
1089
+ */
787
1090
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
788
1091
 
1092
+ /**
1093
+ * @primitive b.guardCsv.compliancePosture
1094
+ * @signature b.guardCsv.compliancePosture(name)
1095
+ * @since 0.7.5
1096
+ * @status stable
1097
+ * @compliance hipaa, pci-dss, gdpr, soc2
1098
+ * @related b.guardCsv.gate, b.guardCsv.buildProfile
1099
+ *
1100
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
1101
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
1102
+ * the posture object — the caller may mutate freely. Throws
1103
+ * `GuardCsvError("csv.bad-posture")` on unknown name.
1104
+ *
1105
+ * @example
1106
+ * var posture = b.guardCsv.compliancePosture("hipaa");
1107
+ * posture.piiPolicy; // → "redact"
1108
+ * posture.bidiCharPolicy; // → "reject"
1109
+ */
789
1110
  function compliancePosture(name) {
790
1111
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "csv");
791
1112
  }
792
1113
 
793
1114
  var _csvRulePacks = gateContract.makeRulePackLoader(GuardCsvError, "csv");
1115
+ /**
1116
+ * @primitive b.guardCsv.loadRulePack
1117
+ * @signature b.guardCsv.loadRulePack(pack)
1118
+ * @since 0.7.5
1119
+ * @status stable
1120
+ * @related b.guardCsv.gate
1121
+ *
1122
+ * Register an operator-supplied rule pack with the guard-csv
1123
+ * registry. The pack is identified by `pack.id` (non-empty
1124
+ * string) and stored for later inspection / dispatch by gates
1125
+ * that opt in via `opts.rulePackId`. Returns the pack object
1126
+ * unchanged on success; throws `GuardCsvError("csv.bad-opt")`
1127
+ * when `pack` is missing or `pack.id` is not a non-empty string.
1128
+ *
1129
+ * @example
1130
+ * var pack = b.guardCsv.loadRulePack({
1131
+ * id: "pii-extra",
1132
+ * rules: [
1133
+ * { id: "ssn-cell", severity: "critical",
1134
+ * detect: function (cell) { return /^\d{3}-\d{2}-\d{4}$/.test(cell); },
1135
+ * reason: "US SSN-shaped value in CSV cell" },
1136
+ * ],
1137
+ * });
1138
+ * pack.id; // → "pii-extra"
1139
+ */
794
1140
  var loadRulePack = _csvRulePacks.load;
795
1141
 
796
1142
  module.exports = {