@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-auth.js CHANGED
@@ -1,30 +1,44 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-auth — Composite auth-bundle safety primitive (b.guardAuth).
3
+ * @module b.guardAuth
4
+ * @nav Guards
5
+ * @title Guard Auth
4
6
  *
5
- * Composes guardJwt + guardOauth + the cookie/header middleware
6
- * validators into a single auth-flow check. KIND="auth-bundle"
7
- * consumes `ctx.authBundle` shape:
8
- * {
9
- * jwtToken?: string, // routed to guardJwt
10
- * oauthFlow?: object, // routed to guardOauth
11
- * cookieHeader?: string, // routed to b.cookies.parseSafe
12
- * requestHeaders?: object, // routed through threat-detection
13
- * }
7
+ * @intro
8
+ * Composite auth-bundle safety primitive (KIND="auth-bundle"). One
9
+ * gate that sequences `b.guardJwt` (bearer token), `b.guardOauth`
10
+ * (authorization-code / token-exchange flow shape), `b.cookies.parseSafe`
11
+ * (Cookie header), and a light request-header threat scan
12
+ * (Content-Length + Transfer-Encoding header smuggling per RFC 9112
13
+ * §6.1) into a single check operators wire into the request
14
+ * lifecycle. Consumes `ctx.authBundle`:
14
15
  *
15
- * Each sub-validator runs independently; aggregated issues are
16
- * returned with a `source` field tagging which sub-guard raised them.
17
- * Operators get one gate to drop into a request lifecycle that covers
18
- * the full canonical-auth threat surface.
16
+ * {
17
+ * jwtToken?: string, // routed to guardJwt
18
+ * oauthFlow?: object, // routed to guardOauth
19
+ * cookieHeader?: string, // routed to b.cookies.parseSafe
20
+ * requestHeaders?: object, // routed through threat detection
21
+ * }
19
22
  *
20
- * var rv = b.guardAuth.validate({
21
- * jwtToken: bearerToken,
22
- * oauthFlow: req.query,
23
- * cookieHeader: req.headers.cookie,
24
- * requestHeaders: req.headers,
25
- * }, { profile: "strict" });
23
+ * Each sub-validator runs independently; aggregated issues carry a
24
+ * `source` field (`"jwt"` / `"oauth"` / `"cookies"` / `"headers"` /
25
+ * `"auth"`) tagging which sub-guard raised them so operators see
26
+ * the full failure surface in one verdict.
27
+ *
28
+ * Refusal posture: stale-token / alg=none JWT / unknown OAuth grant /
29
+ * CL+TE header smuggling all surface as high-severity issues. Strict
30
+ * profile requires at least one auth input via `requireAtLeastOne` —
31
+ * a bundle with no jwtToken / oauthFlow / cookieHeader / requestHeaders
32
+ * is refused so operators don't accidentally ship an unauthenticated
33
+ * request through a gate they thought was active.
26
34
  *
27
- * var g = b.guardAuth.gate({ profile: "strict" });
35
+ * Profiles: `strict` / `balanced` / `permissive`. Compliance postures:
36
+ * `hipaa` / `pci-dss` / `gdpr` / `soc2`. Operators select via
37
+ * `{ profile: "strict" }` or `{ compliance: "hipaa" }`; postures
38
+ * overlay on the profile baseline.
39
+ *
40
+ * @card
41
+ * Composite auth-bundle safety primitive (KIND="auth-bundle").
28
42
  */
29
43
 
30
44
  var lazyRequire = require("./lazy-require");
@@ -183,6 +197,41 @@ function _detectIssues(bundle, opts) {
183
197
  return issues;
184
198
  }
185
199
 
200
+ /**
201
+ * @primitive b.guardAuth.validate
202
+ * @signature b.guardAuth.validate(input, opts?)
203
+ * @since 0.7.41
204
+ * @status stable
205
+ * @compliance hipaa, pci-dss, gdpr, soc2
206
+ * @related b.guardAuth.sanitize, b.guardAuth.gate, b.guardJwt.validate, b.guardOauth.validate
207
+ *
208
+ * Inspect an auth-bundle object and return `{ ok, issues, summary }`.
209
+ * Each issue carries `{ kind, severity, ruleId, source, snippet }`
210
+ * with severity in `"warn"|"high"|"critical"` and `source` tagging
211
+ * the sub-guard that raised it (`"jwt"` / `"oauth"` / `"cookies"` /
212
+ * `"headers"` / `"auth"`). Pure inspection — never mutates input or
213
+ * throws on hostile bundles.
214
+ *
215
+ * Strict profile sets `requireAtLeastOne: true` so an empty bundle
216
+ * (no jwtToken / oauthFlow / cookieHeader / requestHeaders) emits a
217
+ * `no-auth-input` issue — guards against an operator wiring a gate
218
+ * onto a request that ships no credentials at all.
219
+ *
220
+ * @opts
221
+ * profile: "strict"|"balanced"|"permissive",
222
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
223
+ * childProfile: "strict"|"balanced"|"permissive", // forwarded to guardJwt / guardOauth
224
+ * requireAtLeastOne: boolean,
225
+ * allowedRedirectUris: string[], // forwarded to guardOauth
226
+ * maxBytes: number, // bundle JSON-byte cap
227
+ *
228
+ * @example
229
+ * var rv = b.guardAuth.validate({
230
+ * jwtToken: "eyJhbGciOiJub25lIn0.eyJzdWIiOiJ4In0.",
231
+ * }, { profile: "strict" });
232
+ * rv.ok; // → false
233
+ * rv.issues.some(function (i) { return i.source === "jwt"; }); // → true
234
+ */
186
235
  function validate(input, opts) {
187
236
  opts = _resolveOpts(opts);
188
237
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -191,6 +240,32 @@ function validate(input, opts) {
191
240
  return gateContract.aggregateIssues(_detectIssues(input, opts));
192
241
  }
193
242
 
243
+ /**
244
+ * @primitive b.guardAuth.sanitize
245
+ * @signature b.guardAuth.sanitize(input, opts?)
246
+ * @since 0.7.41
247
+ * @status stable
248
+ * @related b.guardAuth.validate, b.guardAuth.gate
249
+ *
250
+ * Strict pass-through validator. The auth-bundle is composed of values
251
+ * the framework cannot safely mutate (forging a JWT alg / rewriting an
252
+ * OAuth state parameter / dropping cookies would be silently dangerous
253
+ * — sanitize must never disarm an actual attack token), so this
254
+ * function refuses (throws `GuardAuthError`) on any critical or high
255
+ * issue and returns the input unchanged when clean.
256
+ *
257
+ * @opts
258
+ * profile: "strict"|"balanced"|"permissive",
259
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
260
+ *
261
+ * @example
262
+ * var clean = b.guardAuth.sanitize({
263
+ * jwtToken: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." +
264
+ * "eyJpc3MiOiJleGFtcGxlIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE3MDAwMDAwMDB9.sig",
265
+ * cookieHeader: "sid=abc123",
266
+ * }, { profile: "balanced" });
267
+ * clean.cookieHeader; // → "sid=abc123"
268
+ */
194
269
  function sanitize(input, opts) {
195
270
  opts = _resolveOpts(opts);
196
271
  if (!input || typeof input !== "object") {
@@ -207,6 +282,36 @@ function sanitize(input, opts) {
207
282
  return input;
208
283
  }
209
284
 
285
+ /**
286
+ * @primitive b.guardAuth.gate
287
+ * @signature b.guardAuth.gate(opts?)
288
+ * @since 0.7.41
289
+ * @status stable
290
+ * @compliance hipaa, pci-dss, gdpr, soc2
291
+ * @related b.guardAuth.validate, b.guardAuth.sanitize, b.middleware.bearerAuth
292
+ *
293
+ * Build a `b.gateContract` gate that consumes `ctx.authBundle` (or
294
+ * `ctx.auth`) and dispatches to guardJwt / guardOauth / cookies /
295
+ * header-smuggling detection. Action chain on validation:
296
+ * `serve` (no bundle, or bundle clean) → `audit-only` (warn-only
297
+ * issues) → `refuse` (any critical or high issue from any
298
+ * sub-validator). No `sanitize` action — the auth bundle isn't
299
+ * repairable in transit.
300
+ *
301
+ * @opts
302
+ * profile: "strict"|"balanced"|"permissive",
303
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
304
+ * name: string, // gate identity for audit / observability
305
+ * childProfile: "strict"|"balanced"|"permissive",
306
+ * allowedRedirectUris: string[],
307
+ *
308
+ * @example
309
+ * var authGate = b.guardAuth.gate({ profile: "strict" });
310
+ * var verdict = await authGate.check({ authBundle: {
311
+ * jwtToken: "eyJhbGciOiJub25lIn0.eyJzdWIiOiJ4In0.",
312
+ * } });
313
+ * verdict.action; // → "refuse"
314
+ */
210
315
  function gate(opts) {
211
316
  opts = _resolveOpts(opts);
212
317
  return gateContract.buildGuardGate(
@@ -230,14 +335,82 @@ function gate(opts) {
230
335
  });
231
336
  }
232
337
 
338
+ /**
339
+ * @primitive b.guardAuth.buildProfile
340
+ * @signature b.guardAuth.buildProfile(opts)
341
+ * @since 0.7.41
342
+ * @status stable
343
+ * @related b.guardAuth.gate, b.guardAuth.compliancePosture
344
+ *
345
+ * Compose a derived profile from one or more named bases plus inline
346
+ * overrides. `opts.extends` is a profile name (`"strict"` /
347
+ * `"balanced"` / `"permissive"`) or an array of names; later entries
348
+ * shadow earlier ones. Inline `opts` keys win last. Used to keep
349
+ * operator-defined profiles traceable to a baseline rather than
350
+ * re-typing every key.
351
+ *
352
+ * @opts
353
+ * extends: string|string[], // base profile name(s) to compose
354
+ *
355
+ * @example
356
+ * var custom = b.guardAuth.buildProfile({
357
+ * extends: "balanced",
358
+ * requireAtLeastOne: true,
359
+ * });
360
+ * custom.requireAtLeastOne; // → true
361
+ * custom.bidiPolicy; // → "reject"
362
+ */
233
363
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
234
364
 
365
+ /**
366
+ * @primitive b.guardAuth.compliancePosture
367
+ * @signature b.guardAuth.compliancePosture(name)
368
+ * @since 0.7.41
369
+ * @status stable
370
+ * @compliance hipaa, pci-dss, gdpr, soc2
371
+ * @related b.guardAuth.gate, b.guardAuth.buildProfile
372
+ *
373
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
374
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of the
375
+ * posture object — the caller may mutate freely. Throws
376
+ * `GuardAuthError("auth.bad-posture")` on unknown name.
377
+ *
378
+ * @example
379
+ * var posture = b.guardAuth.compliancePosture("hipaa");
380
+ * posture.forensicSnippetBytes; // → 512
381
+ * posture.bidiPolicy; // → "reject"
382
+ */
235
383
  function compliancePosture(name) {
236
384
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
237
385
  _err, "auth");
238
386
  }
239
387
 
240
388
  var _authRulePacks = gateContract.makeRulePackLoader(GuardAuthError, "auth");
389
+ /**
390
+ * @primitive b.guardAuth.loadRulePack
391
+ * @signature b.guardAuth.loadRulePack(pack)
392
+ * @since 0.7.41
393
+ * @status stable
394
+ * @related b.guardAuth.gate
395
+ *
396
+ * Register an operator-supplied rule pack with the guard-auth
397
+ * registry. The pack is identified by `pack.id` (non-empty string)
398
+ * and stored for later inspection / dispatch by gates that opt in
399
+ * via `opts.rulePackId`. Returns the pack object unchanged on
400
+ * success; throws `GuardAuthError("auth.bad-opt")` when `pack` is
401
+ * missing or `pack.id` is not a non-empty string.
402
+ *
403
+ * @example
404
+ * var pack = b.guardAuth.loadRulePack({
405
+ * id: "tenant-bearer-prefix",
406
+ * rules: [
407
+ * { id: "tenant-prefix", severity: "high",
408
+ * detect: function (b2) { return b2.jwtToken && b2.jwtToken.indexOf("tenant_") !== 0; },
409
+ * reason: "JWT does not carry the required tenant_ prefix" },
410
+ * ],
411
+ * });
412
+ * pack.id; // → "tenant-bearer-prefix"
413
+ */
241
414
  var loadRulePack = _authRulePacks.load;
242
415
 
243
416
  module.exports = {
package/lib/guard-cidr.js CHANGED
@@ -1,36 +1,46 @@
1
1
  "use strict";
2
2
  /**
3
- * guard-cidr — CIDR identifier-safety primitive (b.guardCidr).
3
+ * @module b.guardCidr
4
+ * @nav Guards
5
+ * @title Guard Cidr
4
6
  *
5
- * Validates user-supplied CIDR notation strings (IPv4 + IPv6) destined
6
- * for network-allowlists, ACLs, security-group rules, and tenant-
7
- * boundary configuration. KIND="identifier" consumes ctx.identifier
8
- * (or ctx.cidr).
7
+ * @intro
8
+ * CIDR identifier-safety primitive (KIND="identifier"). Validates
9
+ * user-supplied CIDR notation strings (IPv4 + IPv6) destined for
10
+ * network allowlists, ACLs, security-group rules, and tenant-
11
+ * boundary configuration. Consumes `ctx.identifier` (or
12
+ * `ctx.cidr`).
9
13
  *
10
- * Threat catalog:
11
- * - Shape malformation not "address/mask".
12
- * - IPv4 octet out of range (> 255), wrong number of octets.
13
- * - IPv6 zero-group inflation, multiple `::` (ambiguous).
14
- * - Mask out of range (IPv4: 0-32; IPv6: 0-128); negative.
15
- * - Network-address misalignment `10.0.0.1/24` carries host bits
16
- * set when /24 implies the pure network address `10.0.0.0/24`.
17
- * Often a typo that produces unexpected match semantics.
18
- * - Reserved IPv4 ranges (RFC 1918 private 10/8, 172.16/12,
19
- * 192.168/16; loopback 127/8; link-local 169.254/16; multicast
20
- * 224/4; reserved 240/4; documentation 192.0.2/24, 198.51.100/24,
21
- * 203.0.113/24; benchmarking 198.18/15; CGNAT 100.64/10).
22
- * - Reserved IPv6 ranges — loopback `::1`, unspecified `::/128`,
23
- * ULA `fc00::/7`, link-local `fe80::/10`, multicast `ff00::/8`,
24
- * IPv4-mapped `::ffff:0:0/96`, documentation `2001:db8::/32`,
25
- * teredo `2001::/32`, deprecated 6to4 `2002::/16`.
26
- * - IPv4-mapped IPv6 confusion (CVE-2021-22931 IPv6 / IPv4 dual-
27
- * stack class) — `::ffff:192.168.1.1` represents the IPv4 address
28
- * in IPv6 namespace and trips dual-stack allowlist matchers.
29
- * - BIDI / zero-width / control / null-byte universal refuse.
14
+ * Shape and prefix-bound enforcement: every CIDR splits into
15
+ * `address/mask`. IPv4 must be strict dotted-decimal (no leading
16
+ * zeros octal-form `0177.0.0.1` is refused at the parser; that
17
+ * class is owned by `b.guardDomain`). IPv4 mask is `[0-32]`; IPv6
18
+ * mask is `[0-128]`; out-of-range and non-numeric masks refuse.
19
+ * IPv6 supports `::` zero-group compression with the standard
20
+ * "at most one `::`" rule.
30
21
  *
31
- * var rv = b.guardCidr.validate("10.0.0.0/8", { profile: "strict" });
32
- * var safe = b.guardCidr.sanitize("10.0.0.1/24", { profile: "balanced" });
33
- * var g = b.guardCidr.gate({ profile: "strict" });
22
+ * Reserved-block awareness: IPv4 ranges per RFC 1918 (private 10/8,
23
+ * 172.16/12, 192.168/16), loopback 127/8, link-local 169.254/16,
24
+ * multicast 224/4, reserved class-E 240/4, documentation 192.0.2/24,
25
+ * 198.51.100/24, 203.0.113/24, benchmarking 198.18/15, and CGNAT
26
+ * 100.64/10. IPv6 ranges: loopback `::1`, unspecified `::/128`,
27
+ * ULA `fc00::/7`, link-local `fe80::/10`, multicast `ff00::/8`,
28
+ * IPv4-mapped `::ffff:0:0/96`, documentation `2001:db8::/32`,
29
+ * teredo `2001::/32`, deprecated 6to4 `2002::/16`. IPv4-mapped IPv6
30
+ * trips dual-stack allowlist confusion (CVE-2021-22931 class) and
31
+ * refuses under strict.
32
+ *
33
+ * Network-address alignment: `10.0.0.1/24` has host bits set under
34
+ * a /24 mask when the canonical network is `10.0.0.0/24`. Common
35
+ * typo class — refused under strict, audited under balanced.
36
+ * BIDI / control / null-byte / zero-width are universal-refuse at
37
+ * every profile (codepoint-class catalog).
38
+ *
39
+ * Profiles: `strict` / `balanced` / `permissive`. Compliance
40
+ * postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`.
41
+ *
42
+ * @card
43
+ * CIDR identifier-safety primitive (KIND="identifier").
34
44
  */
35
45
 
36
46
  var codepointClass = require("./codepoint-class");
@@ -418,6 +428,41 @@ function _detectIssues(input, opts) {
418
428
  return issues;
419
429
  }
420
430
 
431
+ /**
432
+ * @primitive b.guardCidr.validate
433
+ * @signature b.guardCidr.validate(input, opts?)
434
+ * @since 0.7.41
435
+ * @status stable
436
+ * @compliance hipaa, pci-dss, gdpr, soc2
437
+ * @related b.guardCidr.sanitize, b.guardCidr.gate
438
+ *
439
+ * Inspect a CIDR notation string and return `{ ok, issues, summary }`.
440
+ * Each issue carries `{ kind, severity, ruleId, snippet }` with
441
+ * severity in `"warn"|"high"|"critical"`. Detected: malformed address
442
+ * shape, octet-out-of-range, mask-out-of-range, network-address
443
+ * misalignment, reserved-range membership, IPv4-mapped-IPv6
444
+ * confusion, family mismatch, bare IP without `/mask`, BIDI / control
445
+ * / null-byte / zero-width codepoints. Pure inspection — never
446
+ * mutates input or throws.
447
+ *
448
+ * @opts
449
+ * profile: "strict"|"balanced"|"permissive",
450
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
451
+ * family: "either"|"ipv4-only"|"ipv6-only",
452
+ * networkAlignmentPolicy: "reject"|"audit"|"allow",
453
+ * reservedRangesPolicy: "reject"|"audit"|"allow",
454
+ * ipv4MappedIpv6Policy: "reject"|"audit"|"allow",
455
+ * requireMaskPolicy: "reject-bare-ip"|"audit-bare-ip"|"allow-bare-ip",
456
+ * maxBytes: number, // CIDR string byte cap (default 64)
457
+ *
458
+ * @example
459
+ * var rv = b.guardCidr.validate("10.0.0.0/8", { profile: "strict" });
460
+ * rv.ok; // → false
461
+ * rv.issues.some(function (i) { return i.kind === "reserved-range"; }); // → true
462
+ *
463
+ * var clean = b.guardCidr.validate("8.8.8.0/24", { profile: "strict" });
464
+ * clean.ok; // → true
465
+ */
421
466
  function validate(input, opts) {
422
467
  opts = _resolveOpts(opts);
423
468
  numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
@@ -434,6 +479,31 @@ function validate(input, opts) {
434
479
  return gateContract.aggregateIssues(_detectIssues(input, opts));
435
480
  }
436
481
 
482
+ /**
483
+ * @primitive b.guardCidr.sanitize
484
+ * @signature b.guardCidr.sanitize(input, opts?)
485
+ * @since 0.7.41
486
+ * @status stable
487
+ * @related b.guardCidr.validate, b.guardCidr.gate
488
+ *
489
+ * Normalize a CIDR string when no critical/high issues fire. Throws
490
+ * `GuardCidrError` on any high/critical refusal (reserved-range,
491
+ * misalignment under strict, BIDI / null-byte / control bytes).
492
+ * Safe transforms applied otherwise: lowercase IPv6 hex groups,
493
+ * preserve mask form. IPv4 is returned unchanged (no canonical
494
+ * casing).
495
+ *
496
+ * @opts
497
+ * profile: "strict"|"balanced"|"permissive",
498
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
499
+ *
500
+ * @example
501
+ * var safe = b.guardCidr.sanitize("2001:DB8::/32", { profile: "permissive" });
502
+ * safe; // → "2001:db8::/32"
503
+ *
504
+ * var v4 = b.guardCidr.sanitize("8.8.8.0/24", { profile: "strict" });
505
+ * v4; // → "8.8.8.0/24"
506
+ */
437
507
  function sanitize(input, opts) {
438
508
  opts = _resolveOpts(opts);
439
509
  if (typeof input !== "string") {
@@ -454,6 +524,32 @@ function sanitize(input, opts) {
454
524
  return mask === null ? addr.toLowerCase() : addr.toLowerCase() + "/" + mask;
455
525
  }
456
526
 
527
+ /**
528
+ * @primitive b.guardCidr.gate
529
+ * @signature b.guardCidr.gate(opts?)
530
+ * @since 0.7.41
531
+ * @status stable
532
+ * @compliance hipaa, pci-dss, gdpr, soc2
533
+ * @related b.guardCidr.validate, b.guardCidr.sanitize
534
+ *
535
+ * Build a `b.gateContract` gate that consumes `ctx.identifier` (or
536
+ * `ctx.cidr`) and dispatches `serve` (no input or clean) →
537
+ * `audit-only` (warn-only issues) → `refuse` (any critical or high
538
+ * issue). No `sanitize` action — CIDR sanitization is caller-driven
539
+ * via `b.guardCidr.sanitize`; an allowlist gate that silently rewrote
540
+ * the operator's network range would be its own bug class.
541
+ *
542
+ * @opts
543
+ * profile: "strict"|"balanced"|"permissive",
544
+ * compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
545
+ * name: string, // gate identity for audit / observability
546
+ * family: "either"|"ipv4-only"|"ipv6-only",
547
+ *
548
+ * @example
549
+ * var cidrGate = b.guardCidr.gate({ profile: "strict", family: "ipv4-only" });
550
+ * var verdict = await cidrGate.check({ identifier: "10.0.0.0/8" });
551
+ * verdict.action; // → "refuse"
552
+ */
457
553
  function gate(opts) {
458
554
  opts = _resolveOpts(opts);
459
555
  return gateContract.buildGuardGate(
@@ -477,14 +573,80 @@ function gate(opts) {
477
573
  });
478
574
  }
479
575
 
576
+ /**
577
+ * @primitive b.guardCidr.buildProfile
578
+ * @signature b.guardCidr.buildProfile(opts)
579
+ * @since 0.7.41
580
+ * @status stable
581
+ * @related b.guardCidr.gate, b.guardCidr.compliancePosture
582
+ *
583
+ * Compose a derived profile from one or more named bases plus inline
584
+ * overrides. `opts.extends` is a profile name (`"strict"` /
585
+ * `"balanced"` / `"permissive"`) or an array of names; later entries
586
+ * shadow earlier ones. Inline `opts` keys win last.
587
+ *
588
+ * @opts
589
+ * extends: string|string[], // base profile name(s) to compose
590
+ *
591
+ * @example
592
+ * var custom = b.guardCidr.buildProfile({
593
+ * extends: "balanced",
594
+ * reservedRangesPolicy: "reject",
595
+ * });
596
+ * custom.reservedRangesPolicy; // → "reject"
597
+ * custom.bidiPolicy; // → "reject"
598
+ */
480
599
  var buildProfile = gateContract.makeProfileBuilder(PROFILES);
481
600
 
601
+ /**
602
+ * @primitive b.guardCidr.compliancePosture
603
+ * @signature b.guardCidr.compliancePosture(name)
604
+ * @since 0.7.41
605
+ * @status stable
606
+ * @compliance hipaa, pci-dss, gdpr, soc2
607
+ * @related b.guardCidr.gate, b.guardCidr.buildProfile
608
+ *
609
+ * Look up a compliance-posture overlay by name (`"hipaa"` /
610
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of the
611
+ * posture object — the caller may mutate freely. Throws
612
+ * `GuardCidrError("cidr.bad-posture")` on unknown name.
613
+ *
614
+ * @example
615
+ * var posture = b.guardCidr.compliancePosture("hipaa");
616
+ * posture.reservedRangesPolicy; // → "reject"
617
+ * posture.forensicSnippetBytes; // → 128
618
+ */
482
619
  function compliancePosture(name) {
483
620
  return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
484
621
  _err, "cidr");
485
622
  }
486
623
 
487
624
  var _cidrRulePacks = gateContract.makeRulePackLoader(GuardCidrError, "cidr");
625
+ /**
626
+ * @primitive b.guardCidr.loadRulePack
627
+ * @signature b.guardCidr.loadRulePack(pack)
628
+ * @since 0.7.41
629
+ * @status stable
630
+ * @related b.guardCidr.gate
631
+ *
632
+ * Register an operator-supplied rule pack with the guard-cidr
633
+ * registry. The pack is identified by `pack.id` (non-empty string)
634
+ * and stored for later inspection / dispatch by gates that opt in
635
+ * via `opts.rulePackId`. Returns the pack object unchanged on
636
+ * success; throws `GuardCidrError("cidr.bad-opt")` when `pack` is
637
+ * missing or `pack.id` is not a non-empty string.
638
+ *
639
+ * @example
640
+ * var pack = b.guardCidr.loadRulePack({
641
+ * id: "tenant-private-only",
642
+ * rules: [
643
+ * { id: "external-allowlisted", severity: "high",
644
+ * detect: function (cidr) { return cidr.indexOf("10.") !== 0; },
645
+ * reason: "tenant policy: only 10.0.0.0/8 ranges permitted" },
646
+ * ],
647
+ * });
648
+ * pack.id; // → "tenant-private-only"
649
+ */
488
650
  var loadRulePack = _cidrRulePacks.load;
489
651
 
490
652
  module.exports = {