@blamejs/core 0.14.26 → 0.15.0

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 (150) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -2
  3. package/index.js +4 -0
  4. package/lib/agent-envelope-mac.js +104 -0
  5. package/lib/agent-event-bus.js +105 -4
  6. package/lib/agent-posture-chain.js +8 -42
  7. package/lib/ai-content-detect.js +9 -10
  8. package/lib/api-key.js +107 -74
  9. package/lib/atomic-file.js +62 -4
  10. package/lib/audit-chain.js +47 -11
  11. package/lib/audit-sign.js +77 -2
  12. package/lib/audit-tools.js +79 -51
  13. package/lib/audit.js +249 -123
  14. package/lib/auth/openid-federation.js +108 -47
  15. package/lib/backup/index.js +13 -10
  16. package/lib/break-glass.js +202 -144
  17. package/lib/cache.js +174 -105
  18. package/lib/chain-writer.js +38 -16
  19. package/lib/cli.js +19 -14
  20. package/lib/cluster-provider-db.js +130 -104
  21. package/lib/cluster-storage.js +119 -22
  22. package/lib/cluster.js +119 -71
  23. package/lib/compliance.js +169 -4
  24. package/lib/consent.js +73 -24
  25. package/lib/constants.js +16 -11
  26. package/lib/crypto-field.js +474 -92
  27. package/lib/db-declare-row-policy.js +35 -22
  28. package/lib/db-file-lifecycle.js +3 -2
  29. package/lib/db-query.js +497 -255
  30. package/lib/db-schema.js +209 -44
  31. package/lib/db.js +176 -95
  32. package/lib/error-page.js +14 -1
  33. package/lib/external-db-migrate.js +229 -139
  34. package/lib/external-db.js +25 -15
  35. package/lib/file-upload.js +52 -7
  36. package/lib/framework-error.js +14 -1
  37. package/lib/framework-files.js +73 -0
  38. package/lib/framework-schema.js +695 -394
  39. package/lib/gate-contract.js +649 -1
  40. package/lib/guard-agent-registry.js +26 -44
  41. package/lib/guard-all.js +1 -0
  42. package/lib/guard-auth.js +42 -112
  43. package/lib/guard-cidr.js +33 -154
  44. package/lib/guard-csv.js +46 -113
  45. package/lib/guard-domain.js +34 -157
  46. package/lib/guard-dsn.js +27 -43
  47. package/lib/guard-email.js +47 -69
  48. package/lib/guard-envelope.js +19 -32
  49. package/lib/guard-event-bus-payload.js +24 -42
  50. package/lib/guard-event-bus-topic.js +25 -43
  51. package/lib/guard-filename.js +42 -106
  52. package/lib/guard-graphql.js +42 -123
  53. package/lib/guard-html.js +53 -108
  54. package/lib/guard-idempotency-key.js +24 -42
  55. package/lib/guard-image.js +46 -103
  56. package/lib/guard-imap-command.js +18 -32
  57. package/lib/guard-jmap.js +16 -30
  58. package/lib/guard-json.js +38 -108
  59. package/lib/guard-jsonpath.js +38 -171
  60. package/lib/guard-jwt.js +49 -179
  61. package/lib/guard-list-id.js +25 -41
  62. package/lib/guard-list-unsubscribe.js +27 -43
  63. package/lib/guard-mail-compose.js +24 -42
  64. package/lib/guard-mail-move.js +26 -44
  65. package/lib/guard-mail-query.js +28 -46
  66. package/lib/guard-mail-reply.js +24 -42
  67. package/lib/guard-mail-sieve.js +24 -42
  68. package/lib/guard-managesieve-command.js +17 -31
  69. package/lib/guard-markdown.js +37 -104
  70. package/lib/guard-message-id.js +26 -45
  71. package/lib/guard-mime.js +39 -151
  72. package/lib/guard-oauth.js +54 -135
  73. package/lib/guard-pdf.js +45 -101
  74. package/lib/guard-pop3-command.js +21 -31
  75. package/lib/guard-posture-chain.js +24 -42
  76. package/lib/guard-regex.js +33 -107
  77. package/lib/guard-saga-config.js +24 -42
  78. package/lib/guard-shell.js +42 -172
  79. package/lib/guard-smtp-command.js +48 -54
  80. package/lib/guard-snapshot-envelope.js +24 -42
  81. package/lib/guard-sql.js +1491 -0
  82. package/lib/guard-stream-args.js +24 -43
  83. package/lib/guard-svg.js +47 -65
  84. package/lib/guard-template.js +35 -172
  85. package/lib/guard-tenant-id.js +26 -45
  86. package/lib/guard-time.js +32 -154
  87. package/lib/guard-trace-context.js +25 -44
  88. package/lib/guard-uuid.js +32 -153
  89. package/lib/guard-xml.js +38 -113
  90. package/lib/guard-yaml.js +51 -163
  91. package/lib/http-client.js +37 -9
  92. package/lib/inbox.js +120 -107
  93. package/lib/legal-hold.js +107 -50
  94. package/lib/log-stream-cloudwatch.js +47 -31
  95. package/lib/log-stream-otlp.js +32 -18
  96. package/lib/mail-crypto-smime.js +2 -6
  97. package/lib/mail-greylist.js +2 -6
  98. package/lib/mail-helo.js +2 -6
  99. package/lib/mail-journal.js +85 -64
  100. package/lib/mail-rbl.js +2 -6
  101. package/lib/mail-scan.js +2 -6
  102. package/lib/mail-server-jmap.js +117 -12
  103. package/lib/mail-spam-score.js +2 -6
  104. package/lib/mail-store.js +287 -154
  105. package/lib/middleware/body-parser.js +71 -25
  106. package/lib/middleware/csrf-protect.js +19 -8
  107. package/lib/middleware/fetch-metadata.js +17 -7
  108. package/lib/middleware/idempotency-key.js +54 -38
  109. package/lib/middleware/rate-limit.js +102 -32
  110. package/lib/middleware/security-headers.js +21 -5
  111. package/lib/migrations.js +108 -66
  112. package/lib/network-heartbeat.js +7 -0
  113. package/lib/nonce-store.js +31 -9
  114. package/lib/object-store/azure-blob-bucket-ops.js +9 -4
  115. package/lib/object-store/azure-blob.js +57 -3
  116. package/lib/object-store/sigv4.js +10 -0
  117. package/lib/observability.js +87 -0
  118. package/lib/otel-export.js +25 -1
  119. package/lib/outbox.js +136 -82
  120. package/lib/parsers/safe-xml.js +47 -7
  121. package/lib/pqc-agent.js +44 -0
  122. package/lib/pubsub-cluster.js +42 -20
  123. package/lib/queue-local.js +202 -139
  124. package/lib/queue-redis.js +9 -1
  125. package/lib/queue-sqs.js +6 -0
  126. package/lib/redact.js +68 -11
  127. package/lib/redis-client.js +160 -31
  128. package/lib/retention.js +82 -39
  129. package/lib/router.js +212 -5
  130. package/lib/safe-dns.js +29 -45
  131. package/lib/safe-ical.js +18 -33
  132. package/lib/safe-icap.js +27 -43
  133. package/lib/safe-sieve.js +21 -40
  134. package/lib/safe-sql.js +124 -3
  135. package/lib/safe-vcard.js +18 -33
  136. package/lib/scheduler.js +35 -12
  137. package/lib/seeders.js +122 -74
  138. package/lib/session-stores.js +42 -14
  139. package/lib/session.js +109 -72
  140. package/lib/sql.js +3885 -0
  141. package/lib/ssrf-guard.js +51 -4
  142. package/lib/static.js +177 -34
  143. package/lib/subject.js +55 -17
  144. package/lib/vault/index.js +3 -2
  145. package/lib/vault/passphrase-ops.js +3 -2
  146. package/lib/vault/rotate.js +104 -64
  147. package/lib/vendor-data.js +2 -0
  148. package/lib/websocket.js +35 -5
  149. package/package.json +1 -1
  150. package/sbom.cdx.json +6 -6
@@ -333,180 +333,47 @@ function sanitize(input, opts) {
333
333
  throw _err("jsonpath.bad-input", "sanitize requires string input");
334
334
  }
335
335
  var issues = _detectIssues(input, opts);
336
- for (var i = 0; i < issues.length; i += 1) {
337
- if (issues[i].severity === "critical" || issues[i].severity === "high") {
338
- throw _err(issues[i].ruleId || "jsonpath.refused",
339
- "guardJsonpath.sanitize: " + issues[i].snippet);
340
- }
341
- }
336
+ gateContract.throwOnRefusalSeverity(issues, { errorClass: GuardJsonpathError, codePrefix: "jsonpath" });
342
337
  return input;
343
338
  }
344
339
 
345
- /**
346
- * @primitive b.guardJsonpath.gate
347
- * @signature b.guardJsonpath.gate(opts)
348
- * @since 0.7.13
349
- * @status stable
350
- * @compliance hipaa, pci-dss, gdpr, soc2
351
- * @related b.guardJsonpath.validate, b.guardJsonpath.sanitize
352
- *
353
- * Build a `b.gateContract` gate that screens `ctx.identifier` (or
354
- * `ctx.jsonpath`) before the path reaches a JSONPath evaluator.
355
- * Action chain: `serve` (no issues) → `audit-only` (warn-only) →
356
- * `refuse` (any `critical` or `high`). No `sanitize` action —
357
- * JSONPath strings cannot be repaired. Compose into query
358
- * endpoints / search filters / data-export flows so operator-fed
359
- * paths hit the guard before any evaluator dispatch.
360
- *
361
- * @opts
362
- * profile: "strict"|"balanced"|"permissive",
363
- * compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
364
- * name: string, // override gate name in audit emissions
365
- * filterExprPolicy: "reject"|"audit"|"allow",
366
- * scriptExprPolicy: "reject"|"audit"|"allow",
367
- * dynamicHintPolicy: "reject"|"audit"|"allow",
368
- * bracketNestingPolicy: "reject"|"audit"|"allow",
369
- * recursiveDescentPolicy: "reject"|"audit"|"allow",
370
- * maxRecursiveDescents: number,
371
- * maxPatternBytes: number,
372
- *
373
- * @example
374
- * var gate = b.guardJsonpath.gate({ profile: "strict" });
375
- *
376
- * gate({ identifier: "$..[?(@.x)]" }).then(function (rv) {
377
- * rv.ok; // → false
378
- * rv.action; // → "refuse"
379
- * });
380
- *
381
- * gate({ identifier: "$.users[*].name" }).then(function (rv) {
382
- * rv.action; // → "serve"
383
- * });
384
- */
385
- function gate(opts) {
386
- opts = _resolveOpts(opts);
387
- return gateContract.buildGuardGate(
388
- opts.name || "guardJsonpath:" + (opts.profile || "default"),
389
- opts,
390
- async function (ctx) {
391
- var pattern = ctx && (ctx.identifier || ctx.jsonpath);
392
- if (pattern === undefined || pattern === null) {
393
- return { ok: true, action: "serve" };
394
- }
395
- var rv = validate(pattern, opts);
396
- if (rv.issues.length === 0) return { ok: true, action: "serve" };
397
- var hasCritical = rv.issues.some(function (i) {
398
- return i.severity === "critical";
399
- });
400
- var hasHigh = rv.issues.some(function (i) {
401
- return i.severity === "high";
402
- });
403
- if (!hasCritical && !hasHigh) {
404
- return { ok: true, action: "audit-only", issues: rv.issues };
405
- }
406
- return { ok: false, action: "refuse", issues: rv.issues };
407
- });
408
- }
340
+ // The gate is the standard serve -> audit-only -> refuse chain; it is
341
+ // assembled by gateContract.defineGuard's default gate below. JSONPath
342
+ // strings can't be repaired, so there's no sanitize action — the default
343
+ // chain (no sanitize) matches exactly. The default gate reads the path
344
+ // from ctx.identifier || ctx.jsonpath via spec.ctxFields; its
345
+ // "guardJsonpath:<profile>" gate name and serve/audit-only/refuse
346
+ // decisions are identical to the hand-written gate this replaced.
409
347
 
410
- /**
411
- * @primitive b.guardJsonpath.buildProfile
412
- * @signature b.guardJsonpath.buildProfile(opts)
413
- * @since 0.7.13
414
- * @status stable
415
- * @related b.guardJsonpath.gate, b.guardJsonpath.compliancePosture
416
- *
417
- * Compose a derived guardJsonpath profile from one or more named
418
- * bases plus inline overrides. `opts.extends` is a profile name
419
- * (`"strict"` / `"balanced"` / `"permissive"`) or an array of
420
- * names; later entries shadow earlier ones. Inline `opts` keys win
421
- * last. Used to keep operator-defined profiles traceable to a
422
- * baseline rather than re-typing every key.
423
- *
424
- * @opts
425
- * extends: string|string[], // base profile name(s) to compose
426
- * ...: any guardJsonpath key, // inline override of resolved keys
427
- *
428
- * @example
429
- * var custom = b.guardJsonpath.buildProfile({
430
- * extends: "balanced",
431
- * maxRecursiveDescents: 1,
432
- * recursiveDescentPolicy: "reject",
433
- * });
434
- * custom.maxRecursiveDescents; // → 1
435
- * custom.filterExprPolicy; // → "reject"
436
- */
437
- var buildProfile = gateContract.makeProfileBuilder(PROFILES);
438
-
439
- /**
440
- * @primitive b.guardJsonpath.compliancePosture
441
- * @signature b.guardJsonpath.compliancePosture(name)
442
- * @since 0.7.13
443
- * @status stable
444
- * @compliance hipaa, pci-dss, gdpr, soc2
445
- * @related b.guardJsonpath.gate, b.guardJsonpath.buildProfile
446
- *
447
- * Look up a compliance-posture overlay by name (`"hipaa"` /
448
- * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
449
- * the posture object — the caller may mutate freely. Throws
450
- * `GuardJsonpathError("jsonpath.bad-posture")` on unknown name.
451
- *
452
- * @example
453
- * var posture = b.guardJsonpath.compliancePosture("hipaa");
454
- * posture.filterExprPolicy; // → "reject"
455
- * posture.forensicSnippetBytes; // → 256
456
- */
457
- function compliancePosture(name) {
458
- return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
459
- _err, "jsonpath");
460
- }
348
+ // buildProfile / compliancePosture / loadRulePack are assembled by
349
+ // gateContract.defineGuard below (makeProfileBuilder(PROFILES) /
350
+ // lookupCompliancePosture(_, COMPLIANCE_POSTURES) / makeRulePackLoader).
351
+ // Their wiki sections render from the single-sourced @abiTemplate blocks
352
+ // in gate-contract.js, instantiated per guard by the page generator.
461
353
 
462
- var _jpRulePacks = gateContract.makeRulePackLoader(GuardJsonpathError, "jsonpath");
463
- /**
464
- * @primitive b.guardJsonpath.loadRulePack
465
- * @signature b.guardJsonpath.loadRulePack(pack)
466
- * @since 0.7.13
467
- * @status stable
468
- * @related b.guardJsonpath.gate
469
- *
470
- * Register an operator-supplied rule pack with the guardJsonpath
471
- * registry. The pack is identified by `pack.id` (non-empty string)
472
- * and stored for later inspection / dispatch by gates that opt in
473
- * via `opts.rulePackId`. Returns the pack object unchanged on
474
- * success; throws `GuardJsonpathError("jsonpath.bad-opt")` when
475
- * `pack` is missing or `pack.id` is not a non-empty string.
476
- *
477
- * @example
478
- * var pack = b.guardJsonpath.loadRulePack({
479
- * id: "no-wildcards",
480
- * rules: [
481
- * { id: "wildcard", severity: "high",
482
- * detect: function (path) { return path.indexOf("[*]") !== -1; },
483
- * reason: "wildcard index forbidden in this context" },
484
- * ],
485
- * });
486
- * pack.id; // → "no-wildcards"
487
- */
488
- var loadRulePack = _jpRulePacks.load;
354
+ var INTEGRATION_FIXTURES = Object.freeze({
355
+ kind: "identifier",
356
+ benignBytes: Buffer.from("$.users[*].name", "utf8"),
357
+ hostileBytes: Buffer.from("$..[?(@.x)]", "utf8"),
358
+ benignIdentifier: "$.users[*].name",
359
+ hostileIdentifier: "$..[?(@.x)]",
360
+ });
489
361
 
490
- module.exports = {
491
- // ---- guard-* family registry exports ----
492
- NAME: "jsonpath",
493
- KIND: "identifier",
494
- INTEGRATION_FIXTURES: Object.freeze({
495
- kind: "identifier",
496
- benignBytes: Buffer.from("$.users[*].name", "utf8"),
497
- hostileBytes: Buffer.from("$..[?(@.x)]", "utf8"),
498
- benignIdentifier: "$.users[*].name",
499
- hostileIdentifier: "$..[?(@.x)]",
500
- }),
501
- // ---- primitive surface ----
502
- validate: validate,
503
- sanitize: sanitize,
504
- gate: gate,
505
- buildProfile: buildProfile,
506
- compliancePosture: compliancePosture,
507
- loadRulePack: loadRulePack,
508
- PROFILES: PROFILES,
509
- DEFAULTS: DEFAULTS,
510
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
511
- GuardJsonpathError: GuardJsonpathError,
512
- };
362
+ // Assembled from the gate-contract guard factory: error class, registry
363
+ // exports (NAME / KIND / INTEGRATION_FIXTURES), buildProfile /
364
+ // compliancePosture / loadRulePack wiring, plus the per-guard inspection
365
+ // surface (validate / sanitize). The gate is the factory default
366
+ // serve/audit-only/refuse chain, reading the path from
367
+ // `ctx.identifier` || `ctx.jsonpath` via `ctxFields`.
368
+ module.exports = gateContract.defineGuard({
369
+ name: "jsonpath",
370
+ kind: "identifier",
371
+ errorClass: GuardJsonpathError,
372
+ profiles: PROFILES,
373
+ defaults: DEFAULTS,
374
+ postures: COMPLIANCE_POSTURES,
375
+ integrationFixtures: INTEGRATION_FIXTURES,
376
+ validate: validate,
377
+ sanitize: sanitize,
378
+ ctxFields: ["identifier", "jsonpath"],
379
+ });
package/lib/guard-jwt.js CHANGED
@@ -545,152 +545,17 @@ function sanitize(input, opts) {
545
545
  // JWT shape can't be repaired — sanitize either passes through
546
546
  // valid input or throws.
547
547
  var issues = _detectIssues(input, opts);
548
- for (var i = 0; i < issues.length; i += 1) {
549
- if (issues[i].severity === "critical" || issues[i].severity === "high") {
550
- throw _err(issues[i].ruleId || "jwt.refused",
551
- "guardJwt.sanitize: " + issues[i].snippet);
552
- }
553
- }
548
+ gateContract.throwOnRefusalSeverity(issues, { errorClass: GuardJwtError, codePrefix: "jwt" });
554
549
  return input;
555
550
  }
556
551
 
557
- /**
558
- * @primitive b.guardJwt.gate
559
- * @signature b.guardJwt.gate(opts?)
560
- * @since 0.7.49
561
- * @status stable
562
- * @compliance hipaa, pci-dss, gdpr, soc2
563
- * @related b.guardJwt.validate, b.guardJwt.sanitize, b.middleware.bearerAuth
564
- *
565
- * Build a `gateContract.buildGuardGate`-shaped gate that pulls
566
- * `ctx.identifier` (or `ctx.token` / `ctx.jwt`) and dispatches to
567
- * `validate`. Returns `{ ok: true, action: "serve" }` when the
568
- * issue list is empty, `{ ok: true, action: "audit-only", issues }`
569
- * when only low-severity issues fire, and `{ ok: false, action:
570
- * "refuse", issues }` on any `critical` / `high` issue. Compose
571
- * into auth pipelines via `b.middleware.bearerAuth` so every
572
- * bearer token is shape-checked before signature verification.
573
- *
574
- * @opts
575
- * profile: "strict"|"balanced"|"permissive",
576
- * compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
577
- * name: string, // gate label for audit trails
578
- * ...: every guardJwt.validate opt is honored,
579
- *
580
- * @example
581
- * var jwtGate = b.guardJwt.gate({ profile: "strict" });
582
- * var rv = await jwtGate.check({
583
- * identifier:
584
- * "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
585
- * "eyJzdWIiOiJhdHRhY2tlciJ9.",
586
- * });
587
- * rv.action; // → "refuse"
588
- * rv.issues[0].ruleId; // → "jwt.alg-none"
589
- */
590
- function gate(opts) {
591
- opts = _resolveOpts(opts);
592
- return gateContract.buildGuardGate(
593
- opts.name || "guardJwt:" + (opts.profile || "default"),
594
- opts,
595
- async function (ctx) {
596
- var identifier = ctx && (ctx.identifier || ctx.token || ctx.jwt || "");
597
- if (!identifier) return { ok: true, action: "serve" };
598
- var rv = validate(identifier, opts);
599
- if (rv.issues.length === 0) return { ok: true, action: "serve" };
600
- var hasCritical = rv.issues.some(function (i) {
601
- return i.severity === "critical";
602
- });
603
- var hasHigh = rv.issues.some(function (i) {
604
- return i.severity === "high";
605
- });
606
- if (!hasCritical && !hasHigh) {
607
- return { ok: true, action: "audit-only", issues: rv.issues };
608
- }
609
- return { ok: false, action: "refuse", issues: rv.issues };
610
- });
611
- }
612
-
613
- /**
614
- * @primitive b.guardJwt.buildProfile
615
- * @signature b.guardJwt.buildProfile(opts)
616
- * @since 0.7.49
617
- * @status stable
618
- * @related b.guardJwt.gate, b.guardJwt.compliancePosture
619
- *
620
- * Compose a derived profile from one or more named bases plus
621
- * inline overrides. `opts.extends` is a profile name (`"strict"` /
622
- * `"balanced"` / `"permissive"`) or an array of names; later
623
- * entries shadow earlier ones, and inline `opts` keys win last.
624
- * Operators stage profile overlays here so the final shape is
625
- * traceable to a baseline rather than a hand-typed dictionary.
626
- *
627
- * @opts
628
- * extends: string|string[], // base profile name(s) to compose
629
- * ...: any guardJwt key, // inline override of resolved keys
630
- *
631
- * @example
632
- * var custom = b.guardJwt.buildProfile({
633
- * extends: "balanced",
634
- * algAllowlistPolicy: "reject",
635
- * allowedAlgs: ["ES256", "EdDSA"],
636
- * });
637
- * custom.algAllowlistPolicy; // → "reject"
638
- * custom.allowedAlgs.indexOf("ES256"); // → 0
639
- */
640
- var buildProfile = gateContract.makeProfileBuilder(PROFILES);
641
-
642
- /**
643
- * @primitive b.guardJwt.compliancePosture
644
- * @signature b.guardJwt.compliancePosture(name)
645
- * @since 0.7.49
646
- * @status stable
647
- * @compliance hipaa, pci-dss, gdpr, soc2
648
- * @related b.guardJwt.gate, b.guardJwt.buildProfile
649
- *
650
- * Look up a compliance-posture overlay by name (`"hipaa"` /
651
- * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
652
- * the posture object — the caller may mutate freely. Throws
653
- * `GuardJwtError("jwt.bad-posture")` on unknown name. Postures
654
- * extend the strict profile (or balanced for `gdpr`) with a
655
- * `forensicSnippetBytes` cap appropriate to the regime.
656
- *
657
- * @example
658
- * var posture = b.guardJwt.compliancePosture("hipaa");
659
- * posture.algNonePolicy; // → "reject"
660
- * posture.forensicSnippetBytes; // → 256
661
- */
662
- function compliancePosture(name) {
663
- return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
664
- _err, "jwt");
665
- }
666
-
667
- var _jwtRulePacks = gateContract.makeRulePackLoader(GuardJwtError, "jwt");
668
- /**
669
- * @primitive b.guardJwt.loadRulePack
670
- * @signature b.guardJwt.loadRulePack(pack)
671
- * @since 0.7.49
672
- * @status stable
673
- * @related b.guardJwt.gate
674
- *
675
- * Register an operator-supplied rule pack with the guard-jwt
676
- * registry. The pack is identified by `pack.id` (non-empty
677
- * string) and stored for later inspection / dispatch by gates
678
- * that opt in via `opts.rulePackId`. Returns the pack object
679
- * unchanged on success; throws `GuardJwtError("jwt.bad-opt")`
680
- * when `pack` is missing or `pack.id` is not a non-empty string.
681
- *
682
- * @example
683
- * var pack = b.guardJwt.loadRulePack({
684
- * id: "tenant-issuer-pin",
685
- * rules: [
686
- * { id: "iss-pin", severity: "high",
687
- * detect: function (claims) { return claims.iss !== "https://idp.example/"; },
688
- * reason: "tenant pins iss to a single IdP" },
689
- * ],
690
- * });
691
- * pack.id; // → "tenant-issuer-pin"
692
- */
693
- var loadRulePack = _jwtRulePacks.load;
552
+ // gate is the standard serve -> audit-only -> refuse chain over
553
+ // ctx.identifier || ctx.token || ctx.jwt (the KIND "identifier" ctx
554
+ // fields); gateContract.defineGuard supplies it as the default gate.
555
+ // buildProfile / compliancePosture / loadRulePack are assembled by
556
+ // gateContract.defineGuard below; their wiki sections render from the
557
+ // single-sourced @abiTemplate blocks in gate-contract.js, instantiated
558
+ // per guard by the page generator.
694
559
 
695
560
  /**
696
561
  * @primitive b.guardJwt.kidSafe
@@ -735,39 +600,44 @@ function kidSafe(kid) {
735
600
  return kid;
736
601
  }
737
602
 
738
- module.exports = {
739
- // ---- guard-* family registry exports ----
740
- NAME: "jwt",
741
- KIND: "identifier",
742
- INTEGRATION_FIXTURES: Object.freeze({
743
- kind: "identifier",
744
- // Benign: minimal v4 token with alg=ES256, valid JSON header / payload.
745
- benignBytes: Buffer.from(
746
- "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." +
747
- "eyJpc3MiOiJleGFtcGxlIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE3MDAwMDAwMDB9." +
748
- "sig", "utf8"),
749
- benignIdentifier:
750
- "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." +
751
- "eyJpc3MiOiJleGFtcGxlIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE3MDAwMDAwMDB9." +
752
- "sig",
753
- // Hostile: alg=none — universal refuse class.
754
- hostileBytes: Buffer.from(
755
- "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
756
- "eyJzdWIiOiJhdHRhY2tlciIsImV4cCI6OTk5OTk5OTk5OX0.", "utf8"),
757
- hostileIdentifier:
758
- "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
759
- "eyJzdWIiOiJhdHRhY2tlciIsImV4cCI6OTk5OTk5OTk5OX0.",
760
- }),
761
- // ---- primitive surface ----
762
- validate: validate,
763
- sanitize: sanitize,
764
- gate: gate,
765
- kidSafe: kidSafe,
766
- buildProfile: buildProfile,
767
- compliancePosture: compliancePosture,
768
- loadRulePack: loadRulePack,
769
- PROFILES: PROFILES,
770
- DEFAULTS: DEFAULTS,
771
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
772
- GuardJwtError: GuardJwtError,
773
- };
603
+ // ---- guard-* family registry exports ----
604
+ var INTEGRATION_FIXTURES = Object.freeze({
605
+ kind: "identifier",
606
+ // Benign: minimal v4 token with alg=ES256, valid JSON header / payload.
607
+ benignBytes: Buffer.from(
608
+ "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." +
609
+ "eyJpc3MiOiJleGFtcGxlIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE3MDAwMDAwMDB9." +
610
+ "sig", "utf8"),
611
+ benignIdentifier:
612
+ "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." +
613
+ "eyJpc3MiOiJleGFtcGxlIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE3MDAwMDAwMDB9." +
614
+ "sig",
615
+ // Hostile: alg=none — universal refuse class.
616
+ hostileBytes: Buffer.from(
617
+ "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
618
+ "eyJzdWIiOiJhdHRhY2tlciIsImV4cCI6OTk5OTk5OTk5OX0.", "utf8"),
619
+ hostileIdentifier:
620
+ "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
621
+ "eyJzdWIiOiJhdHRhY2tlciIsImV4cCI6OTk5OTk5OTk5OX0.",
622
+ });
623
+
624
+ // Assembled from the gate-contract guard factory. KIND "identifier"; the
625
+ // gate is the standard serve -> audit-only -> refuse chain over
626
+ // ctx.identifier || ctx.token || ctx.jwt, so the guard takes the factory
627
+ // default gate (no bespoke `gate` passed) and the factory supplies the
628
+ // error class, registry exports, buildProfile / compliancePosture /
629
+ // loadRulePack wiring, and the kidSafe extra.
630
+ module.exports = gateContract.defineGuard({
631
+ name: "jwt",
632
+ kind: "identifier",
633
+ errorClass: GuardJwtError,
634
+ profiles: PROFILES,
635
+ defaults: DEFAULTS,
636
+ postures: COMPLIANCE_POSTURES,
637
+ integrationFixtures: INTEGRATION_FIXTURES,
638
+ validate: validate,
639
+ sanitize: sanitize,
640
+ extra: {
641
+ kidSafe: kidSafe,
642
+ },
643
+ });
@@ -73,6 +73,7 @@
73
73
 
74
74
  var C = require("./constants");
75
75
  var { defineClass } = require("./framework-error");
76
+ var gateContract = require("./gate-contract");
76
77
 
77
78
  var GuardListIdError = defineClass("GuardListIdError", { alwaysPermanent: true });
78
79
 
@@ -102,11 +103,15 @@ var PROFILES = Object.freeze({
102
103
  },
103
104
  });
104
105
 
105
- var COMPLIANCE_POSTURES = Object.freeze({
106
- hipaa: "strict",
107
- "pci-dss": "strict",
108
- gdpr: "strict",
109
- soc2: "strict",
106
+ var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
107
+
108
+ var _resolveProfile = gateContract.makeProfileResolver({
109
+ profiles: PROFILES,
110
+ postures: COMPLIANCE_POSTURES,
111
+ defaults: DEFAULT_PROFILE,
112
+ errorClass: GuardListIdError,
113
+ codePrefix: "guard-list-id",
114
+ byObject: true,
110
115
  });
111
116
 
112
117
  // RFC 5322 §3.2.3 dot-atom-text shape — alphanumeric + select
@@ -261,21 +266,10 @@ function validate(headerValue, opts) {
261
266
  };
262
267
  }
263
268
 
264
- /**
265
- * @primitive b.guardListId.compliancePosture
266
- * @signature b.guardListId.compliancePosture(posture)
267
- * @since 0.9.40
268
- * @status stable
269
- *
270
- * Return the effective profile name for a compliance posture, or
271
- * `null` for unknown posture names.
272
- *
273
- * @example
274
- * b.guardListId.compliancePosture("hipaa"); // → "strict"
275
- */
276
- function compliancePosture(posture) {
277
- return COMPLIANCE_POSTURES[posture] || null;
278
- }
269
+ // compliancePosture is assembled by gateContract.defineParser below; its
270
+ // wiki section renders from the single-sourced @abiTemplate (defineParser)
271
+ // block in gate-contract.js, instantiated for this guard by the page
272
+ // generator.
279
273
 
280
274
  function _hasControlChar(s) {
281
275
  for (var i = 0; i < s.length; i += 1) {
@@ -296,24 +290,14 @@ function _refuse(reason) {
296
290
  };
297
291
  }
298
292
 
299
- function _resolveProfile(opts) {
300
- if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
301
- return PROFILES[COMPLIANCE_POSTURES[opts.posture]];
302
- }
303
- var p = opts.profile || DEFAULT_PROFILE;
304
- if (!PROFILES[p]) {
305
- throw new GuardListIdError("guard-list-id/bad-profile",
306
- "guardListId: unknown profile '" + p + "'");
307
- }
308
- return PROFILES[p];
309
- }
310
-
311
- module.exports = {
312
- validate: validate,
313
- compliancePosture: compliancePosture,
314
- PROFILES: PROFILES,
315
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
316
- GuardListIdError: GuardListIdError,
317
- NAME: "listId",
318
- KIND: "list-id",
319
- };
293
+ module.exports = gateContract.defineParser({
294
+ name: "listId",
295
+ entry: validate,
296
+ errorClass: GuardListIdError,
297
+ profiles: PROFILES,
298
+ postures: COMPLIANCE_POSTURES,
299
+ extra: {
300
+ NAME: "listId",
301
+ KIND: "list-id",
302
+ },
303
+ });
@@ -79,6 +79,7 @@
79
79
  var C = require("./constants");
80
80
  var { defineClass } = require("./framework-error");
81
81
  var safeUrl = require("./safe-url");
82
+ var gateContract = require("./gate-contract");
82
83
 
83
84
  var GuardListUnsubscribeError = defineClass("GuardListUnsubscribeError", { alwaysPermanent: true });
84
85
 
@@ -111,11 +112,15 @@ var PROFILES = Object.freeze({
111
112
  },
112
113
  });
113
114
 
114
- var COMPLIANCE_POSTURES = Object.freeze({
115
- hipaa: "strict",
116
- "pci-dss": "strict",
117
- gdpr: "strict",
118
- soc2: "strict",
115
+ var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
116
+
117
+ var _resolveProfile = gateContract.makeProfileResolver({
118
+ profiles: PROFILES,
119
+ postures: COMPLIANCE_POSTURES,
120
+ defaults: DEFAULT_PROFILE,
121
+ errorClass: GuardListUnsubscribeError,
122
+ codePrefix: "guard-list-unsubscribe",
123
+ byObject: true,
119
124
  });
120
125
 
121
126
  // RFC 8058 §2: Post header value MUST be exactly
@@ -328,21 +333,10 @@ function validate(headers, opts) {
328
333
  { uris: classified, hasHttpsUri: hasHttpsUri, hasMailtoUri: hasMailtoUri, postHeaderOk: postHeaderOk });
329
334
  }
330
335
 
331
- /**
332
- * @primitive b.guardListUnsubscribe.compliancePosture
333
- * @signature b.guardListUnsubscribe.compliancePosture(posture)
334
- * @since 0.9.39
335
- * @status stable
336
- *
337
- * Return the effective profile name for a compliance posture, or
338
- * `null` for unknown posture names.
339
- *
340
- * @example
341
- * b.guardListUnsubscribe.compliancePosture("hipaa"); // → "strict"
342
- */
343
- function compliancePosture(posture) {
344
- return COMPLIANCE_POSTURES[posture] || null;
345
- }
336
+ // compliancePosture is assembled by gateContract.defineParser below; its
337
+ // wiki section renders from the single-sourced @abiTemplate (defineParser)
338
+ // block in gate-contract.js, instantiated for this guard by the page
339
+ // generator.
346
340
 
347
341
  function _extractUris(raw, maxUris) {
348
342
  // RFC 2369 §3.1 — comma-separated `<URI>` items. Walk angle-
@@ -386,26 +380,16 @@ function _verdict(action, reason, extra) {
386
380
  };
387
381
  }
388
382
 
389
- function _resolveProfile(opts) {
390
- if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
391
- return PROFILES[COMPLIANCE_POSTURES[opts.posture]];
392
- }
393
- var p = opts.profile || DEFAULT_PROFILE;
394
- if (!PROFILES[p]) {
395
- throw new GuardListUnsubscribeError("guard-list-unsubscribe/bad-profile",
396
- "guardListUnsubscribe: unknown profile '" + p + "'");
397
- }
398
- return PROFILES[p];
399
- }
400
-
401
- module.exports = {
402
- validate: validate,
403
- compliancePosture: compliancePosture,
404
- PROFILES: PROFILES,
405
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
406
- ONE_CLICK_POST_VALUE: ONE_CLICK_POST_VALUE,
407
- DANGEROUS_SCHEMES: DANGEROUS_SCHEMES,
408
- GuardListUnsubscribeError: GuardListUnsubscribeError,
409
- NAME: "listUnsubscribe",
410
- KIND: "list-unsubscribe",
411
- };
383
+ module.exports = gateContract.defineParser({
384
+ name: "listUnsubscribe",
385
+ entry: validate,
386
+ errorClass: GuardListUnsubscribeError,
387
+ profiles: PROFILES,
388
+ postures: COMPLIANCE_POSTURES,
389
+ extra: {
390
+ ONE_CLICK_POST_VALUE: ONE_CLICK_POST_VALUE,
391
+ DANGEROUS_SCHEMES: DANGEROUS_SCHEMES,
392
+ NAME: "listUnsubscribe",
393
+ KIND: "list-unsubscribe",
394
+ },
395
+ });