@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
@@ -589,12 +589,8 @@ function sanitize(input, opts) {
589
589
  throw _err("markdown.bad-input", "sanitize requires string input");
590
590
  }
591
591
  var issues = _detectIssues(input, opts);
592
- for (var i = 0; i < issues.length; i += 1) {
593
- if (issues[i].severity === "critical") {
594
- throw _err(issues[i].ruleId || "markdown.refused",
595
- "guardMarkdown.sanitize: " + issues[i].snippet);
596
- }
597
- }
592
+ gateContract.throwOnRefusalSeverity(issues,
593
+ { errorClass: GuardMarkdownError, codePrefix: "markdown", severities: ["critical"] });
598
594
  return codepointClass.applyCharStripPolicies(input, opts);
599
595
  }
600
596
 
@@ -667,102 +663,39 @@ function gate(opts) {
667
663
  });
668
664
  }
669
665
 
670
- /**
671
- * @primitive b.guardMarkdown.buildProfile
672
- * @signature b.guardMarkdown.buildProfile(opts)
673
- * @since 0.7.16
674
- * @status stable
675
- * @related b.guardMarkdown.gate, b.guardMarkdown.compliancePosture
676
- *
677
- * Compose a derived profile from one or more named bases plus
678
- * inline overrides. `opts.extends` is a profile name or array of
679
- * names (later entries shadow earlier ones); inline keys win last.
680
- *
681
- * @opts
682
- * extends: string|string[], // base profile name(s) to compose
683
- * ...: any guard-markdown key, // inline override of resolved keys
684
- *
685
- * @example
686
- * var custom = b.guardMarkdown.buildProfile({
687
- * extends: "balanced",
688
- * dangerousTagPolicy: "strip",
689
- * schemeAllowlist: ["http", "https"],
690
- * });
691
- * custom.dangerousTagPolicy; // → "strip"
692
- * custom.schemeAllowlist.indexOf("mailto"); // → -1
693
- */
694
- var buildProfile = gateContract.makeProfileBuilder(PROFILES);
695
-
696
- /**
697
- * @primitive b.guardMarkdown.compliancePosture
698
- * @signature b.guardMarkdown.compliancePosture(name)
699
- * @since 0.7.16
700
- * @status stable
701
- * @compliance hipaa, pci-dss, gdpr, soc2
702
- * @related b.guardMarkdown.gate, b.guardMarkdown.buildProfile
703
- *
704
- * Look up a compliance-posture overlay by name (`"hipaa"` /
705
- * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
706
- * the posture object — the caller may mutate freely. Throws
707
- * `GuardMarkdownError("markdown.bad-posture")` on unknown name.
708
- *
709
- * @example
710
- * var posture = b.guardMarkdown.compliancePosture("hipaa");
711
- * posture.dangerousTagPolicy; // → "reject"
712
- */
713
- function compliancePosture(name) {
714
- return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "markdown");
715
- }
666
+ // buildProfile / compliancePosture / loadRulePack are assembled by
667
+ // gateContract.defineGuard below (makeProfileBuilder(PROFILES) /
668
+ // lookupCompliancePosture(_, COMPLIANCE_POSTURES) / makeRulePackLoader).
669
+ // Their wiki sections render from the single-sourced @abiTemplate blocks
670
+ // in gate-contract.js, instantiated per guard by the page generator.
671
+
672
+ var INTEGRATION_FIXTURES = Object.freeze({
673
+ kind: "content",
674
+ contentType: "text/markdown",
675
+ extension: ".md",
676
+ benignBytes: Buffer.from(
677
+ "# Title\n\nA [link](https://example.com) and *emphasis*.\n", "utf8"),
678
+ // Hostile: link with javascript: scheme CVE-2025-9540 class.
679
+ hostileBytes: Buffer.from(
680
+ "# x\n\n[click](javascript:alert(1))\n", "utf8"),
681
+ });
716
682
 
717
- var _markdownRulePacks = gateContract.makeRulePackLoader(GuardMarkdownError, "markdown");
718
- /**
719
- * @primitive b.guardMarkdown.loadRulePack
720
- * @signature b.guardMarkdown.loadRulePack(pack)
721
- * @since 0.7.16
722
- * @status stable
723
- * @related b.guardMarkdown.gate
724
- *
725
- * Register an operator-supplied rule pack with the guard-markdown
726
- * registry. The pack is identified by `pack.id` (non-empty
727
- * string) and stored for later inspection / dispatch by gates
728
- * that opt in via `opts.rulePackId`. Throws
729
- * `GuardMarkdownError("markdown.bad-opt")` when `pack` is missing
730
- * or `pack.id` is not a non-empty string.
731
- *
732
- * @example
733
- * var pack = b.guardMarkdown.loadRulePack({
734
- * id: "wiki-internal",
735
- * extraSchemeAllowlist: ["wiki"],
736
- * });
737
- * pack.id; // → "wiki-internal"
738
- */
739
- var loadRulePack = _markdownRulePacks.load;
740
-
741
- module.exports = {
742
- // ---- guard-* family registry exports ----
743
- NAME: "markdown",
744
- KIND: "content",
745
- MIME_TYPES: Object.freeze(["text/markdown", "text/x-markdown", "text/x-gfm"]),
746
- EXTENSIONS: Object.freeze([".md", ".markdown"]),
747
- INTEGRATION_FIXTURES: Object.freeze({
748
- kind: "content",
749
- contentType: "text/markdown",
750
- extension: ".md",
751
- benignBytes: Buffer.from(
752
- "# Title\n\nA [link](https://example.com) and *emphasis*.\n", "utf8"),
753
- // Hostile: link with javascript: scheme — CVE-2025-9540 class.
754
- hostileBytes: Buffer.from(
755
- "# x\n\n[click](javascript:alert(1))\n", "utf8"),
756
- }),
757
- // ---- primitive surface ----
758
- validate: validate,
759
- sanitize: sanitize,
760
- gate: gate,
761
- buildProfile: buildProfile,
762
- compliancePosture: compliancePosture,
763
- loadRulePack: loadRulePack,
764
- PROFILES: PROFILES,
765
- DEFAULTS: DEFAULTS,
766
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
767
- GuardMarkdownError: GuardMarkdownError,
768
- };
683
+ // Assembled from the gate-contract guard factory: error class, registry
684
+ // exports (NAME / KIND / MIME_TYPES / EXTENSIONS / INTEGRATION_FIXTURES),
685
+ // buildProfile / compliancePosture / loadRulePack wiring, plus the
686
+ // per-guard inspection surface (validate / sanitize). The bespoke `gate`
687
+ // carries markdown's sanitize-and-reemit chain unchanged.
688
+ module.exports = gateContract.defineGuard({
689
+ name: "markdown",
690
+ kind: "content",
691
+ errorClass: GuardMarkdownError,
692
+ profiles: PROFILES,
693
+ defaults: DEFAULTS,
694
+ postures: COMPLIANCE_POSTURES,
695
+ mimeTypes: ["text/markdown", "text/x-markdown", "text/x-gfm"],
696
+ extensions: [".md", ".markdown"],
697
+ integrationFixtures: INTEGRATION_FIXTURES,
698
+ validate: validate,
699
+ sanitize: sanitize,
700
+ gate: gate,
701
+ });
@@ -50,6 +50,7 @@
50
50
  */
51
51
 
52
52
  var { defineClass } = require("./framework-error");
53
+ var gateContract = require("./gate-contract");
53
54
 
54
55
  var GuardMessageIdError = defineClass("GuardMessageIdError", { alwaysPermanent: true });
55
56
 
@@ -61,12 +62,7 @@ var PROFILES = Object.freeze({
61
62
  permissive: { requireBrackets: false, maxBytes: 4096 }, // permissive cap, not bytes-as-storage
62
63
  });
63
64
 
64
- var COMPLIANCE_POSTURES = Object.freeze({
65
- hipaa: "strict",
66
- "pci-dss": "strict",
67
- gdpr: "strict",
68
- soc2: "strict",
69
- });
65
+ var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
70
66
 
71
67
  // Bidi codepoints refused — same set the framework's address-bidi
72
68
  // defense uses (RFC 5322 §3.6.4 doesn't speak EAI codepoints, but RTL
@@ -225,43 +221,28 @@ function validateList(value, opts) {
225
221
  return ids;
226
222
  }
227
223
 
228
- /**
229
- * @primitive b.guardMessageId.compliancePosture
230
- * @signature b.guardMessageId.compliancePosture(posture)
231
- * @since 0.9.19
232
- * @status stable
233
- *
234
- * Return the effective profile for a given compliance posture.
235
- * Composed by `b.compliance.set` to surface "what posture is active
236
- * for which guard" in audit rows.
237
- *
238
- * @example
239
- * b.guardMessageId.compliancePosture("hipaa"); // → "strict"
240
- * b.guardMessageId.compliancePosture("unknown"); // → null
241
- */
242
- function compliancePosture(posture) {
243
- return COMPLIANCE_POSTURES[posture] || null;
244
- }
245
-
246
- function _resolveProfile(opts) {
247
- if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
248
- return COMPLIANCE_POSTURES[opts.posture];
249
- }
250
- var p = opts.profile || DEFAULT_PROFILE;
251
- if (!PROFILES[p]) {
252
- throw new GuardMessageIdError("message-id/bad-profile",
253
- "guardMessageId: unknown profile '" + p + "' (use strict / balanced / permissive)");
254
- }
255
- return p;
256
- }
224
+ // compliancePosture is assembled by gateContract.defineParser below; its
225
+ // wiki section renders from the single-sourced @abiTemplate (defineParser)
226
+ // block in gate-contract.js, instantiated for this guard by the page
227
+ // generator.
228
+
229
+ var _resolveProfile = gateContract.makeProfileResolver({
230
+ profiles: PROFILES,
231
+ postures: COMPLIANCE_POSTURES,
232
+ defaults: DEFAULT_PROFILE,
233
+ errorClass: GuardMessageIdError,
234
+ codePrefix: "message-id",
235
+ });
257
236
 
258
- module.exports = {
259
- validate: validate,
260
- validateList: validateList,
261
- compliancePosture: compliancePosture,
262
- PROFILES: PROFILES,
263
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
264
- GuardMessageIdError: GuardMessageIdError,
265
- NAME: "messageId",
266
- KIND: "identifier",
267
- };
237
+ module.exports = gateContract.defineParser({
238
+ name: "message-id",
239
+ entry: validate,
240
+ errorClass: GuardMessageIdError,
241
+ profiles: PROFILES,
242
+ postures: COMPLIANCE_POSTURES,
243
+ extra: {
244
+ validateList: validateList,
245
+ NAME: "messageId",
246
+ KIND: "identifier",
247
+ },
248
+ });
package/lib/guard-mime.js CHANGED
@@ -443,12 +443,9 @@ function sanitize(input, opts) {
443
443
  throw _err("mime.bad-input", "sanitize requires string input");
444
444
  }
445
445
  var issues = _detectIssues(input, opts);
446
- for (var i = 0; i < issues.length; i += 1) {
447
- if (issues[i].severity === "critical" || issues[i].severity === "high") {
448
- throw _err(issues[i].ruleId || "mime.refused",
449
- "guardMime.sanitize: " + issues[i].snippet);
450
- }
451
- }
446
+ gateContract.throwOnRefusalSeverity(issues, {
447
+ errorClass: GuardMimeError, codePrefix: "mime",
448
+ });
452
449
  // Normalize: lowercase the type/subtype; preserve parameter case
453
450
  // because some parameter values are case-significant (e.g. boundary
454
451
  // tokens in multipart/form-data).
@@ -459,151 +456,42 @@ function sanitize(input, opts) {
459
456
  }, canonical);
460
457
  }
461
458
 
462
- /**
463
- * @primitive b.guardMime.gate
464
- * @signature b.guardMime.gate(opts?)
465
- * @since 0.7.47
466
- * @status stable
467
- * @compliance hipaa, pci-dss, gdpr, soc2
468
- * @related b.guardMime.validate, b.guardMime.sanitize, b.guardAll.gate
469
- *
470
- * Build a guard gate whose async `check(ctx)` returns `{ ok, action, issues }`, consumable
471
- * by `b.guardAll`, `b.staticServe`, `b.fileUpload`, and any other
472
- * host that integrates the guard contract. The gate reads
473
- * `ctx.identifier` (or `ctx.mime`), runs `validate`, and maps
474
- * severity to action: zero issues `serve`; only low/medium
475
- * `audit-only`; any high/critical `refuse`.
476
- *
477
- * @opts
478
- * name: string, // gate label for audit / observability
479
- * profile: "strict"|"balanced"|"permissive",
480
- * compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
481
- * ...: same shape as b.guardMime.validate opts,
482
- *
483
- * @example
484
- * var g = b.guardMime.gate({ profile: "strict" });
485
- * var rv = await g.check({ identifier: "application/json" });
486
- * rv.action; // → "serve"
487
- *
488
- * var bad = await g.check({ identifier: "application/x-msdownload" });
489
- * bad.action; // → "refuse"
490
- */
491
- function gate(opts) {
492
- opts = _resolveOpts(opts);
493
- return gateContract.buildGuardGate(
494
- opts.name || "guardMime:" + (opts.profile || "default"),
495
- opts,
496
- async function (ctx) {
497
- var identifier = ctx && (ctx.identifier || ctx.mime || "");
498
- if (!identifier) return { ok: true, action: "serve" };
499
- var rv = validate(identifier, opts);
500
- if (rv.issues.length === 0) return { ok: true, action: "serve" };
501
- var hasCritical = rv.issues.some(function (i) {
502
- return i.severity === "critical";
503
- });
504
- var hasHigh = rv.issues.some(function (i) {
505
- return i.severity === "high";
506
- });
507
- if (!hasCritical && !hasHigh) {
508
- return { ok: true, action: "audit-only", issues: rv.issues };
509
- }
510
- return { ok: false, action: "refuse", issues: rv.issues };
511
- });
512
- }
513
-
514
- /**
515
- * @primitive b.guardMime.buildProfile
516
- * @signature b.guardMime.buildProfile(opts)
517
- * @since 0.7.47
518
- * @status stable
519
- * @related b.guardMime.gate, b.guardMime.compliancePosture
520
- *
521
- * Compose a derived profile from one or more named bases plus
522
- * inline overrides. `opts.extends` is a profile name or array of
523
- * names (later entries shadow earlier ones); inline keys win last.
524
- *
525
- * @opts
526
- * extends: string|string[], // base profile name(s) to compose
527
- * ...: any guard-mime key, // inline override of resolved keys
528
- *
529
- * @example
530
- * var custom = b.guardMime.buildProfile({
531
- * extends: "balanced",
532
- * vendorTreePolicy: "audit",
533
- * });
534
- * custom.bidiPolicy; // → "reject"
535
- * custom.vendorTreePolicy; // → "audit"
536
- */
537
- var buildProfile = gateContract.makeProfileBuilder(PROFILES);
459
+ // The request-boundary gate is the gate-contract factory default: it reads
460
+ // `ctx.identifier` (or `ctx.mime`), runs `validate`, and maps severity to
461
+ // action — `serve` (no issue) / `audit-only` (info / warn) / `refuse` (any
462
+ // high / critical). Its wiki section renders from the single-sourced
463
+ // `@abiTemplate gate` block in gate-contract.js.
538
464
 
539
- /**
540
- * @primitive b.guardMime.compliancePosture
541
- * @signature b.guardMime.compliancePosture(name)
542
- * @since 0.7.47
543
- * @status stable
544
- * @compliance hipaa, pci-dss, gdpr, soc2
545
- * @related b.guardMime.gate, b.guardMime.buildProfile
546
- *
547
- * Look up a compliance-posture overlay by name (`"hipaa"` /
548
- * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
549
- * the posture object — the caller may mutate freely. Throws
550
- * `GuardMimeError("mime.bad-posture")` on unknown name.
551
- *
552
- * @example
553
- * var posture = b.guardMime.compliancePosture("pci-dss");
554
- * posture.riskyTypesPolicy; // → "reject"
555
- */
556
- function compliancePosture(name) {
557
- return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
558
- _err, "mime");
559
- }
465
+ // buildProfile / compliancePosture / loadRulePack are assembled by
466
+ // gateContract.defineGuard below (makeProfileBuilder(PROFILES) /
467
+ // lookupCompliancePosture(_, COMPLIANCE_POSTURES) / makeRulePackLoader).
468
+ // Their wiki sections render from the single-sourced @abiTemplate blocks
469
+ // in gate-contract.js, instantiated per guard by the page generator.
560
470
 
561
- var _mimeRulePacks = gateContract.makeRulePackLoader(GuardMimeError, "mime");
562
- /**
563
- * @primitive b.guardMime.loadRulePack
564
- * @signature b.guardMime.loadRulePack(pack)
565
- * @since 0.7.47
566
- * @status stable
567
- * @related b.guardMime.gate
568
- *
569
- * Register an operator-supplied rule pack with the guard-mime
570
- * registry. The pack is identified by `pack.id` (non-empty
571
- * string) and stored for later inspection / dispatch by gates
572
- * that opt in via `opts.rulePackId`. Returns the pack object
573
- * unchanged on success; throws `GuardMimeError("mime.bad-opt")`
574
- * when `pack` is missing or `pack.id` is not a non-empty string.
575
- *
576
- * @example
577
- * var pack = b.guardMime.loadRulePack({
578
- * id: "operator-deny-flash",
579
- * deny: ["application/x-shockwave-flash"],
580
- * });
581
- * pack.id; // → "operator-deny-flash"
582
- */
583
- var loadRulePack = _mimeRulePacks.load;
471
+ var INTEGRATION_FIXTURES = Object.freeze({
472
+ kind: "identifier",
473
+ benignBytes: Buffer.from("application/json", "utf8"),
474
+ hostileBytes: Buffer.from("application/x-msdownload", "utf8"),
475
+ benignIdentifier: "application/json",
476
+ // Hostile: risky-type — refused at strict (executable script-host
477
+ // class).
478
+ hostileIdentifier: "application/x-msdownload",
479
+ });
584
480
 
585
- module.exports = {
586
- // ---- guard-* family registry exports ----
587
- NAME: "mime",
588
- KIND: "identifier",
589
- INTEGRATION_FIXTURES: Object.freeze({
590
- kind: "identifier",
591
- benignBytes: Buffer.from("application/json", "utf8"),
592
- hostileBytes: Buffer.from("application/x-msdownload", "utf8"),
593
- benignIdentifier: "application/json",
594
- // Hostile: risky-type — refused at strict (executable script-host
595
- // class).
596
- hostileIdentifier: "application/x-msdownload",
597
- }),
598
- // ---- primitive surface ----
599
- validate: validate,
600
- sanitize: sanitize,
601
- gate: gate,
602
- buildProfile: buildProfile,
603
- compliancePosture: compliancePosture,
604
- loadRulePack: loadRulePack,
605
- PROFILES: PROFILES,
606
- DEFAULTS: DEFAULTS,
607
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
608
- GuardMimeError: GuardMimeError,
609
- };
481
+ // Assembled from the gate-contract guard factory: error class, registry
482
+ // exports (NAME / KIND / INTEGRATION_FIXTURES), buildProfile /
483
+ // compliancePosture / loadRulePack wiring, plus the per-guard inspection
484
+ // surface (validate / sanitize). The gate is the factory default chain,
485
+ // dispatched to `ctx.identifier` / `ctx.mime` via ctxFields.
486
+ module.exports = gateContract.defineGuard({
487
+ name: "mime",
488
+ kind: "identifier",
489
+ errorClass: GuardMimeError,
490
+ profiles: PROFILES,
491
+ defaults: DEFAULTS,
492
+ postures: COMPLIANCE_POSTURES,
493
+ integrationFixtures: INTEGRATION_FIXTURES,
494
+ validate: validate,
495
+ sanitize: sanitize,
496
+ ctxFields: ["identifier", "mime"],
497
+ });
@@ -89,8 +89,6 @@ var { GuardOauthError } = require("./framework-error");
89
89
  var observability = lazyRequire(function () { return require("./observability"); });
90
90
  void observability;
91
91
 
92
- var _err = GuardOauthError.factory;
93
-
94
92
  var SCOPE_TOKEN_RE = /^[\x21\x23-\x5b\x5d-\x7e]+$/; // RFC 6749 §3.3 scope-token charset
95
93
  var DEFAULT_RESPONSE_TYPES = Object.freeze(["code"]);
96
94
 
@@ -446,12 +444,7 @@ function sanitize(input, opts) {
446
444
  // OAuth flows can't be repaired — sanitize either passes through
447
445
  // valid input or throws.
448
446
  var issues = _detectIssues(input, opts);
449
- for (var i = 0; i < issues.length; i += 1) {
450
- if (issues[i].severity === "critical" || issues[i].severity === "high") {
451
- throw _err(issues[i].ruleId || "oauth.refused",
452
- "guardOauth.sanitize: " + issues[i].snippet);
453
- }
454
- }
447
+ gateContract.throwOnRefusalSeverity(issues, { errorClass: GuardOauthError, codePrefix: "oauth" });
455
448
  return input;
456
449
  }
457
450
 
@@ -519,132 +512,58 @@ function gate(opts) {
519
512
  });
520
513
  }
521
514
 
522
- /**
523
- * @primitive b.guardOauth.buildProfile
524
- * @signature b.guardOauth.buildProfile(opts)
525
- * @since 0.7.49
526
- * @status stable
527
- * @related b.guardOauth.gate, b.guardOauth.compliancePosture
528
- *
529
- * Compose a derived profile from one or more named bases plus
530
- * inline overrides. `opts.extends` is a profile name (`"strict"` /
531
- * `"balanced"` / `"permissive"`) or an array of names; later
532
- * entries shadow earlier ones, and inline `opts` keys win last.
533
- * Operators stage profile overlays here so the final shape is
534
- * traceable to a baseline rather than a hand-typed dictionary.
535
- *
536
- * @opts
537
- * extends: string|string[], // base profile name(s) to compose
538
- * ...: any guardOauth key, // inline override of resolved keys
539
- *
540
- * @example
541
- * var custom = b.guardOauth.buildProfile({
542
- * extends: "balanced",
543
- * pkcePolicy: "require-s256",
544
- * allowedResponseTypes: ["code"],
545
- * });
546
- * custom.pkcePolicy; // → "require-s256"
547
- * custom.allowedResponseTypes.length; // → 1
548
- */
549
- var buildProfile = gateContract.makeProfileBuilder(PROFILES);
550
-
551
- /**
552
- * @primitive b.guardOauth.compliancePosture
553
- * @signature b.guardOauth.compliancePosture(name)
554
- * @since 0.7.49
555
- * @status stable
556
- * @compliance hipaa, pci-dss, gdpr, soc2
557
- * @related b.guardOauth.gate, b.guardOauth.buildProfile
558
- *
559
- * Look up a compliance-posture overlay by name (`"hipaa"` /
560
- * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
561
- * the posture object — the caller may mutate freely. Throws
562
- * `GuardOauthError("oauth.bad-posture")` on unknown name.
563
- * Postures extend the strict profile (or balanced for `gdpr`)
564
- * with a `forensicSnippetBytes` cap appropriate to the regime.
565
- *
566
- * @example
567
- * var posture = b.guardOauth.compliancePosture("pci-dss");
568
- * posture.pkcePolicy; // → "require-s256"
569
- * posture.forensicSnippetBytes; // → 256
570
- */
571
- function compliancePosture(name) {
572
- return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
573
- _err, "oauth");
574
- }
515
+ // buildProfile / compliancePosture / loadRulePack are assembled by
516
+ // gateContract.defineGuard below — their wiki sections render from the
517
+ // single-sourced @abiTemplate blocks in gate-contract.js.
575
518
 
576
- var _oauthRulePacks = gateContract.makeRulePackLoader(GuardOauthError, "oauth");
577
- /**
578
- * @primitive b.guardOauth.loadRulePack
579
- * @signature b.guardOauth.loadRulePack(pack)
580
- * @since 0.7.49
581
- * @status stable
582
- * @related b.guardOauth.gate
583
- *
584
- * Register an operator-supplied rule pack with the guard-oauth
585
- * registry. The pack is identified by `pack.id` (non-empty
586
- * string) and stored for later inspection / dispatch by gates
587
- * that opt in via `opts.rulePackId`. Returns the pack object
588
- * unchanged on success; throws `GuardOauthError("oauth.bad-opt")`
589
- * when `pack` is missing or `pack.id` is not a non-empty string.
590
- *
591
- * @example
592
- * var pack = b.guardOauth.loadRulePack({
593
- * id: "scope-narrow",
594
- * rules: [
595
- * { id: "no-admin", severity: "high",
596
- * detect: function (flow) { return /\badmin\b/.test(flow.scope || ""); },
597
- * reason: "tenant forbids admin scope on user-flow callbacks" },
598
- * ],
599
- * });
600
- * pack.id; // → "scope-narrow"
601
- */
602
- var loadRulePack = _oauthRulePacks.load;
519
+ // ---- adaptive integration-test fixtures (consumed by layer-5 host harness) ----
520
+ var INTEGRATION_FIXTURES = Object.freeze({
521
+ kind: "oauth-flow",
522
+ benignBytes: Buffer.from(JSON.stringify({
523
+ response_type: "code",
524
+ redirect_uri: "https://app.example.com/callback",
525
+ state: "csrf-rand-1",
526
+ scope: "openid profile",
527
+ code_challenge: "abc123def456ghi789jkl012mno345pqr678", // base64url-shaped fixture
528
+ code_challenge_method: "S256",
529
+ }), "utf8"),
530
+ hostileBytes: Buffer.from(JSON.stringify({
531
+ response_type: "code",
532
+ redirect_uri: "https://attacker.example/callback",
533
+ // state missing — CSRF class
534
+ scope: "openid",
535
+ }), "utf8"),
536
+ benignOauthFlow: {
537
+ response_type: "code",
538
+ redirect_uri: "https://app.example.com/callback",
539
+ state: "csrf-rand-1",
540
+ scope: "openid profile",
541
+ code_challenge: "abc123def456ghi789jkl012mno345pqr678", // base64url-shaped fixture
542
+ code_challenge_method: "S256",
543
+ },
544
+ hostileOauthFlow: {
545
+ response_type: "code",
546
+ redirect_uri: "https://attacker.example/callback",
547
+ // state missing → state-missing refuse
548
+ scope: "openid",
549
+ },
550
+ });
603
551
 
604
- module.exports = {
605
- // ---- guard-* family registry exports ----
606
- NAME: "oauth",
607
- KIND: "oauth-flow",
608
- INTEGRATION_FIXTURES: Object.freeze({
609
- kind: "oauth-flow",
610
- benignBytes: Buffer.from(JSON.stringify({
611
- response_type: "code",
612
- redirect_uri: "https://app.example.com/callback",
613
- state: "csrf-rand-1",
614
- scope: "openid profile",
615
- code_challenge: "abc123def456ghi789jkl012mno345pqr678", // base64url-shaped fixture
616
- code_challenge_method: "S256",
617
- }), "utf8"),
618
- hostileBytes: Buffer.from(JSON.stringify({
619
- response_type: "code",
620
- redirect_uri: "https://attacker.example/callback",
621
- // state missing — CSRF class
622
- scope: "openid",
623
- }), "utf8"),
624
- benignOauthFlow: {
625
- response_type: "code",
626
- redirect_uri: "https://app.example.com/callback",
627
- state: "csrf-rand-1",
628
- scope: "openid profile",
629
- code_challenge: "abc123def456ghi789jkl012mno345pqr678", // base64url-shaped fixture
630
- code_challenge_method: "S256",
631
- },
632
- hostileOauthFlow: {
633
- response_type: "code",
634
- redirect_uri: "https://attacker.example/callback",
635
- // state missing → state-missing refuse
636
- scope: "openid",
637
- },
638
- }),
639
- // ---- primitive surface ----
640
- validate: validate,
641
- sanitize: sanitize,
642
- gate: gate,
643
- buildProfile: buildProfile,
644
- compliancePosture: compliancePosture,
645
- loadRulePack: loadRulePack,
646
- PROFILES: PROFILES,
647
- DEFAULTS: DEFAULTS,
648
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
649
- GuardOauthError: GuardOauthError,
650
- };
552
+ // Assembled from the gate-contract guard factory: error class, registry
553
+ // exports (NAME / KIND / INTEGRATION_FIXTURES), buildProfile /
554
+ // compliancePosture / loadRulePack wiring, plus the per-guard inspection
555
+ // surface (validate / sanitize / bespoke gate) passed through verbatim.
556
+ // The custom KIND ("oauth-flow") is accepted because the bespoke gate
557
+ // reads its own ctx fields (ctx.oauthFlow / ctx.flow).
558
+ module.exports = gateContract.defineGuard({
559
+ name: "oauth",
560
+ kind: "oauth-flow",
561
+ errorClass: GuardOauthError,
562
+ profiles: PROFILES,
563
+ defaults: DEFAULTS,
564
+ postures: COMPLIANCE_POSTURES,
565
+ integrationFixtures: INTEGRATION_FIXTURES,
566
+ validate: validate,
567
+ sanitize: sanitize,
568
+ gate: gate,
569
+ });