@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
package/lib/guard-time.js CHANGED
@@ -423,164 +423,42 @@ function sanitize(input, opts) {
423
423
  throw _err("time.bad-input", "sanitize requires string input");
424
424
  }
425
425
  var issues = _detectIssues(input, opts);
426
- for (var i = 0; i < issues.length; i += 1) {
427
- if (issues[i].severity === "critical" || issues[i].severity === "high") {
428
- throw _err(issues[i].ruleId || "time.refused",
429
- "guardTime.sanitize: " + issues[i].snippet);
430
- }
431
- }
426
+ gateContract.throwOnRefusalSeverity(issues, { errorClass: GuardTimeError, codePrefix: "time" });
432
427
  // Normalize: lowercase the trailing `T` separator, uppercase the
433
428
  // `Z` UTC marker.
434
429
  return input.replace(/(\d) /, "$1T").replace(/z$/, "Z");
435
430
  }
436
431
 
437
- /**
438
- * @primitive b.guardTime.gate
439
- * @signature b.guardTime.gate(opts?)
440
- * @since 0.7.46
441
- * @status stable
442
- * @compliance hipaa, pci-dss, gdpr, soc2
443
- * @related b.guardTime.validate, b.guardTime.sanitize, b.guardAll.gate
444
- *
445
- * Build a guard gate whose async `check(ctx)` returns `{ ok, action, issues }`, consumable
446
- * by `b.guardAll`, audit pipelines, scheduling primitives, and
447
- * retention readers. The gate reads `ctx.identifier` (or
448
- * `ctx.timestamp` / `ctx.time`), runs `validate`, and maps
449
- * severity to action: zero issues `serve`; only low/medium
450
- * `audit-only`; any high/critical `refuse`.
451
- *
452
- * @opts
453
- * name: string, // gate label for audit / observability
454
- * profile: "strict"|"balanced"|"permissive",
455
- * compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
456
- * ...: same shape as b.guardTime.validate opts,
457
- *
458
- * @example
459
- * var g = b.guardTime.gate({ profile: "strict" });
460
- * var rv = await g.check({ identifier: "2026-05-05T12:34:56Z" });
461
- * rv.action; // → "serve"
462
- *
463
- * var bad = await g.check({ identifier: "2026-05-05 12:34:56" });
464
- * bad.action; // → "refuse"
465
- */
466
- function gate(opts) {
467
- opts = _resolveOpts(opts);
468
- return gateContract.buildGuardGate(
469
- opts.name || "guardTime:" + (opts.profile || "default"),
470
- opts,
471
- async function (ctx) {
472
- var identifier = ctx && (ctx.identifier || ctx.timestamp || ctx.time || "");
473
- if (!identifier) return { ok: true, action: "serve" };
474
- var rv = validate(identifier, opts);
475
- if (rv.issues.length === 0) return { ok: true, action: "serve" };
476
- var hasCritical = rv.issues.some(function (i) {
477
- return i.severity === "critical";
478
- });
479
- var hasHigh = rv.issues.some(function (i) {
480
- return i.severity === "high";
481
- });
482
- if (!hasCritical && !hasHigh) {
483
- return { ok: true, action: "audit-only", issues: rv.issues };
484
- }
485
- return { ok: false, action: "refuse", issues: rv.issues };
486
- });
487
- }
432
+ // gate / buildProfile / compliancePosture / loadRulePack are assembled by
433
+ // gateContract.defineGuard below; their wiki sections render from the
434
+ // single-sourced @abiTemplate (defineGuard) blocks in gate-contract.js,
435
+ // instantiated per guard by the page generator.
488
436
 
489
- /**
490
- * @primitive b.guardTime.buildProfile
491
- * @signature b.guardTime.buildProfile(opts)
492
- * @since 0.7.46
493
- * @status stable
494
- * @related b.guardTime.gate, b.guardTime.compliancePosture
495
- *
496
- * Compose a derived profile from one or more named bases plus
497
- * inline overrides. `opts.extends` is a profile name or array of
498
- * names (later entries shadow earlier ones); inline keys win last.
499
- *
500
- * @opts
501
- * extends: string|string[], // base profile name(s) to compose
502
- * ...: any guard-time key, // inline override of resolved keys
503
- *
504
- * @example
505
- * var custom = b.guardTime.buildProfile({
506
- * extends: "balanced",
507
- * leapSecondPolicy: "audit",
508
- * maxYear: 2200,
509
- * });
510
- * custom.naiveDatetimePolicy; // → "audit"
511
- * custom.maxYear; // → 2200
512
- */
513
- var buildProfile = gateContract.makeProfileBuilder(PROFILES);
514
-
515
- /**
516
- * @primitive b.guardTime.compliancePosture
517
- * @signature b.guardTime.compliancePosture(name)
518
- * @since 0.7.46
519
- * @status stable
520
- * @compliance hipaa, pci-dss, gdpr, soc2
521
- * @related b.guardTime.gate, b.guardTime.buildProfile
522
- *
523
- * Look up a compliance-posture overlay by name (`"hipaa"` /
524
- * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
525
- * the posture object — the caller may mutate freely. Throws
526
- * `GuardTimeError("time.bad-posture")` on unknown name.
527
- *
528
- * @example
529
- * var posture = b.guardTime.compliancePosture("hipaa");
530
- * posture.naiveDatetimePolicy; // → "reject"
531
- */
532
- function compliancePosture(name) {
533
- return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
534
- _err, "time");
535
- }
536
-
537
- var _timeRulePacks = gateContract.makeRulePackLoader(GuardTimeError, "time");
538
- /**
539
- * @primitive b.guardTime.loadRulePack
540
- * @signature b.guardTime.loadRulePack(pack)
541
- * @since 0.7.46
542
- * @status stable
543
- * @related b.guardTime.gate
544
- *
545
- * Register an operator-supplied rule pack with the guard-time
546
- * registry. The pack is identified by `pack.id` (non-empty
547
- * string) and stored for later inspection / dispatch by gates
548
- * that opt in via `opts.rulePackId`. Throws
549
- * `GuardTimeError("time.bad-opt")` when `pack` is missing or
550
- * `pack.id` is not a non-empty string.
551
- *
552
- * @example
553
- * var pack = b.guardTime.loadRulePack({
554
- * id: "audit-window",
555
- * minYear: 2020,
556
- * maxYear: 2030,
557
- * });
558
- * pack.id; // → "audit-window"
559
- */
560
- var loadRulePack = _timeRulePacks.load;
437
+ var INTEGRATION_FIXTURES = Object.freeze({
438
+ kind: "identifier",
439
+ benignBytes: Buffer.from("2026-05-05T12:34:56Z", "utf8"),
440
+ hostileBytes: Buffer.from("2026-05-05 12:34:56", "utf8"),
441
+ benignIdentifier: "2026-05-05T12:34:56Z",
442
+ // Hostile: naive datetime (space separator + no offset) — refused
443
+ // at strict (cross-region ambiguity class).
444
+ hostileIdentifier: "2026-05-05 12:34:56",
445
+ });
561
446
 
562
- module.exports = {
563
- // ---- guard-* family registry exports ----
564
- NAME: "time",
565
- KIND: "identifier",
566
- INTEGRATION_FIXTURES: Object.freeze({
567
- kind: "identifier",
568
- benignBytes: Buffer.from("2026-05-05T12:34:56Z", "utf8"),
569
- hostileBytes: Buffer.from("2026-05-05 12:34:56", "utf8"),
570
- benignIdentifier: "2026-05-05T12:34:56Z",
571
- // Hostile: naive datetime (space separator + no offset) — refused
572
- // at strict (cross-region ambiguity class).
573
- hostileIdentifier: "2026-05-05 12:34:56",
574
- }),
575
- // ---- primitive surface ----
576
- validate: validate,
577
- sanitize: sanitize,
578
- gate: gate,
579
- buildProfile: buildProfile,
580
- compliancePosture: compliancePosture,
581
- loadRulePack: loadRulePack,
582
- PROFILES: PROFILES,
583
- DEFAULTS: DEFAULTS,
584
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
585
- GuardTimeError: GuardTimeError,
586
- };
447
+ // Assembled from the gate-contract guard factory: error class, registry
448
+ // exports (NAME / KIND / INTEGRATION_FIXTURES), buildProfile /
449
+ // compliancePosture / loadRulePack wiring, plus the per-guard inspection
450
+ // surface (validate / sanitize). The gate is the factory default — the
451
+ // standard serve -> audit-only -> refuse chain — reading
452
+ // ctx.identifier / ctx.timestamp / ctx.time via ctxFields.
453
+ module.exports = gateContract.defineGuard({
454
+ name: "time",
455
+ kind: "identifier",
456
+ errorClass: GuardTimeError,
457
+ profiles: PROFILES,
458
+ defaults: DEFAULTS,
459
+ postures: COMPLIANCE_POSTURES,
460
+ integrationFixtures: INTEGRATION_FIXTURES,
461
+ validate: validate,
462
+ sanitize: sanitize,
463
+ ctxFields: ["identifier", "timestamp", "time"],
464
+ });
@@ -27,6 +27,7 @@
27
27
  */
28
28
 
29
29
  var { defineClass } = require("./framework-error");
30
+ var gateContract = require("./gate-contract");
30
31
 
31
32
  var GuardTraceContextError = defineClass("GuardTraceContextError", { alwaysPermanent: true });
32
33
 
@@ -38,15 +39,18 @@ var PROFILES = Object.freeze({
38
39
  permissive: { allowedVersions: ["*"], maxTracestateEntries: 64, maxTracestateBytes: 1024 },
39
40
  });
40
41
 
41
- var COMPLIANCE_POSTURES = Object.freeze({
42
- hipaa: "strict",
43
- "pci-dss": "strict",
44
- gdpr: "strict",
45
- soc2: "strict",
46
- });
42
+ var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
47
43
 
48
44
  var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/; // allow:regex-no-length-cap — length-bound inline before test
49
45
 
46
+ var _resolveProfile = gateContract.makeProfileResolver({
47
+ profiles: PROFILES,
48
+ postures: COMPLIANCE_POSTURES,
49
+ defaults: DEFAULT_PROFILE,
50
+ errorClass: GuardTraceContextError,
51
+ codePrefix: "trace-context",
52
+ });
53
+
50
54
  /**
51
55
  * @primitive b.guardTraceContext.validate
52
56
  * @signature b.guardTraceContext.validate(ctx, opts?)
@@ -132,41 +136,18 @@ function validate(ctx, opts) {
132
136
  return ctx;
133
137
  }
134
138
 
135
- /**
136
- * @primitive b.guardTraceContext.compliancePosture
137
- * @signature b.guardTraceContext.compliancePosture(posture)
138
- * @since 0.9.29
139
- * @status stable
140
- *
141
- * Return the effective profile for a given compliance posture name.
142
- * Returns `null` for unknown posture names so operator typos surface
143
- * here instead of silently falling through to the default profile.
144
- *
145
- * @example
146
- * b.guardTraceContext.compliancePosture("hipaa"); // returns "strict"
147
- */
148
- function compliancePosture(posture) {
149
- return COMPLIANCE_POSTURES[posture] || null;
150
- }
151
-
152
- function _resolveProfile(opts) {
153
- if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
154
- return COMPLIANCE_POSTURES[opts.posture];
155
- }
156
- var p = opts.profile || DEFAULT_PROFILE;
157
- if (!PROFILES[p]) {
158
- throw new GuardTraceContextError("trace-context/bad-profile",
159
- "guardTraceContext: unknown profile '" + p + "'");
160
- }
161
- return p;
162
- }
163
-
164
- module.exports = {
165
- validate: validate,
166
- compliancePosture: compliancePosture,
167
- PROFILES: PROFILES,
168
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
169
- GuardTraceContextError: GuardTraceContextError,
170
- NAME: "traceContext",
171
- KIND: "trace-context",
172
- };
139
+ // compliancePosture is assembled by gateContract.defineParser below; its
140
+ // wiki section renders from the single-sourced @abiTemplate (defineParser)
141
+ // block in gate-contract.js, instantiated for this guard by the page
142
+ // generator.
143
+ module.exports = gateContract.defineParser({
144
+ name: "trace-context",
145
+ entry: validate,
146
+ errorClass: GuardTraceContextError,
147
+ profiles: PROFILES,
148
+ postures: COMPLIANCE_POSTURES,
149
+ extra: {
150
+ NAME: "traceContext",
151
+ KIND: "trace-context",
152
+ },
153
+ });
package/lib/guard-uuid.js CHANGED
@@ -382,12 +382,7 @@ function sanitize(input, opts) {
382
382
  throw _err("uuid.bad-input", "sanitize requires string input");
383
383
  }
384
384
  var issues = _detectIssues(input, opts);
385
- for (var i = 0; i < issues.length; i += 1) {
386
- if (issues[i].severity === "critical" || issues[i].severity === "high") {
387
- throw _err(issues[i].ruleId || "uuid.refused",
388
- "guardUuid.sanitize: " + issues[i].snippet);
389
- }
390
- }
385
+ gateContract.throwOnRefusalSeverity(issues, { errorClass: GuardUuidError, codePrefix: "uuid" });
391
386
  // Safe transforms: lowercase + strip braces / urn prefix → canonical
392
387
  // hyphenated form.
393
388
  var form = _classifyForm(input);
@@ -398,151 +393,35 @@ function sanitize(input, opts) {
398
393
  hex.slice(20); // UUID hex slice positions
399
394
  }
400
395
 
401
- /**
402
- * @primitive b.guardUuid.gate
403
- * @signature b.guardUuid.gate(opts?)
404
- * @since 0.7.44
405
- * @status stable
406
- * @compliance hipaa, pci-dss, gdpr, soc2
407
- * @related b.guardUuid.validate, b.guardUuid.sanitize, b.guardAll.gate
408
- *
409
- * Build a guard gate whose async `check(ctx)` returns `{ ok, action, issues }`, consumable
410
- * by `b.guardAll`, ID validators, and any host that handles
411
- * UUID-shaped tokens. The gate reads `ctx.identifier` (or
412
- * `ctx.uuid`), runs `validate`, and maps severity to action: zero
413
- * issues `serve`; only low/medium `audit-only`; any high/critical
414
- * `refuse`.
415
- *
416
- * @opts
417
- * name: string, // gate label for audit / observability
418
- * profile: "strict"|"balanced"|"permissive",
419
- * compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
420
- * ...: same shape as b.guardUuid.validate opts,
421
- *
422
- * @example
423
- * var g = b.guardUuid.gate({ profile: "strict" });
424
- * var rv = await g.check({ identifier: "550e8400-e29b-41d4-a716-446655440000" });
425
- * rv.action; // → "serve"
426
- *
427
- * var bad = await g.check({ identifier: "{550e8400-e29b-41d4-a716-446655440000}" });
428
- * bad.action; // → "refuse"
429
- */
430
- function gate(opts) {
431
- opts = _resolveOpts(opts);
432
- return gateContract.buildGuardGate(
433
- opts.name || "guardUuid:" + (opts.profile || "default"),
434
- opts,
435
- async function (ctx) {
436
- var identifier = ctx && (ctx.identifier || ctx.uuid || "");
437
- if (!identifier) return { ok: true, action: "serve" };
438
- var rv = validate(identifier, opts);
439
- if (rv.issues.length === 0) return { ok: true, action: "serve" };
440
- var hasCritical = rv.issues.some(function (i) {
441
- return i.severity === "critical";
442
- });
443
- var hasHigh = rv.issues.some(function (i) {
444
- return i.severity === "high";
445
- });
446
- if (!hasCritical && !hasHigh) {
447
- return { ok: true, action: "audit-only", issues: rv.issues };
448
- }
449
- return { ok: false, action: "refuse", issues: rv.issues };
450
- });
451
- }
452
-
453
- /**
454
- * @primitive b.guardUuid.buildProfile
455
- * @signature b.guardUuid.buildProfile(opts)
456
- * @since 0.7.44
457
- * @status stable
458
- * @related b.guardUuid.gate, b.guardUuid.compliancePosture
459
- *
460
- * Compose a derived profile from one or more named bases plus
461
- * inline overrides. `opts.extends` is a profile name or array of
462
- * names (later entries shadow earlier ones); inline keys win last.
463
- *
464
- * @opts
465
- * extends: string|string[], // base profile name(s) to compose
466
- * ...: any guard-uuid key, // inline override of resolved keys
467
- *
468
- * @example
469
- * var custom = b.guardUuid.buildProfile({
470
- * extends: "balanced",
471
- * formatPolicy: "hyphenated-only",
472
- * nilPolicy: "audit",
473
- * });
474
- * custom.formatPolicy; // → "hyphenated-only"
475
- * custom.nilPolicy; // → "audit"
476
- */
477
- var buildProfile = gateContract.makeProfileBuilder(PROFILES);
478
-
479
- /**
480
- * @primitive b.guardUuid.compliancePosture
481
- * @signature b.guardUuid.compliancePosture(name)
482
- * @since 0.7.44
483
- * @status stable
484
- * @compliance hipaa, pci-dss, gdpr, soc2
485
- * @related b.guardUuid.gate, b.guardUuid.buildProfile
486
- *
487
- * Look up a compliance-posture overlay by name (`"hipaa"` /
488
- * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
489
- * the posture object — the caller may mutate freely. Throws
490
- * `GuardUuidError("uuid.bad-posture")` on unknown name.
491
- *
492
- * @example
493
- * var posture = b.guardUuid.compliancePosture("hipaa");
494
- * posture.nilPolicy; // → "reject"
495
- */
496
- function compliancePosture(name) {
497
- return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
498
- _err, "uuid");
499
- }
500
-
501
- var _uuidRulePacks = gateContract.makeRulePackLoader(GuardUuidError, "uuid");
502
- /**
503
- * @primitive b.guardUuid.loadRulePack
504
- * @signature b.guardUuid.loadRulePack(pack)
505
- * @since 0.7.44
506
- * @status stable
507
- * @related b.guardUuid.gate
508
- *
509
- * Register an operator-supplied rule pack with the guard-uuid
510
- * registry. The pack is identified by `pack.id` (non-empty
511
- * string) and stored for later inspection / dispatch by gates
512
- * that opt in via `opts.rulePackId`. Throws
513
- * `GuardUuidError("uuid.bad-opt")` when `pack` is missing or
514
- * `pack.id` is not a non-empty string.
515
- *
516
- * @example
517
- * var pack = b.guardUuid.loadRulePack({
518
- * id: "v7-only",
519
- * allowedVersions: [7],
520
- * });
521
- * pack.id; // → "v7-only"
522
- */
523
- var loadRulePack = _uuidRulePacks.load;
396
+ // gate / buildProfile / compliancePosture / loadRulePack are assembled by
397
+ // gateContract.defineGuard below; their wiki sections render from the
398
+ // single-sourced @abiTemplate (defineGuard) blocks in gate-contract.js,
399
+ // instantiated per guard by the page generator.
400
+
401
+ var INTEGRATION_FIXTURES = Object.freeze({
402
+ kind: "identifier",
403
+ benignBytes: Buffer.from("550e8400-e29b-41d4-a716-446655440000", "utf8"),
404
+ hostileBytes: Buffer.from("00000000-0000-0000-0000-000000000000", "utf8"),
405
+ benignIdentifier: "550e8400-e29b-41d4-a716-446655440000",
406
+ // Hostile: nil UUID refused at strict (sentinel-leak class).
407
+ hostileIdentifier: "00000000-0000-0000-0000-000000000000",
408
+ });
524
409
 
525
- module.exports = {
526
- // ---- guard-* family registry exports ----
527
- NAME: "uuid",
528
- KIND: "identifier",
529
- INTEGRATION_FIXTURES: Object.freeze({
530
- kind: "identifier",
531
- benignBytes: Buffer.from("550e8400-e29b-41d4-a716-446655440000", "utf8"),
532
- hostileBytes: Buffer.from("00000000-0000-0000-0000-000000000000", "utf8"),
533
- benignIdentifier: "550e8400-e29b-41d4-a716-446655440000",
534
- // Hostile: nil UUID — refused at strict (sentinel-leak class).
535
- hostileIdentifier: "00000000-0000-0000-0000-000000000000",
536
- }),
537
- // ---- primitive surface ----
538
- validate: validate,
539
- sanitize: sanitize,
540
- gate: gate,
541
- buildProfile: buildProfile,
542
- compliancePosture: compliancePosture,
543
- loadRulePack: loadRulePack,
544
- PROFILES: PROFILES,
545
- DEFAULTS: DEFAULTS,
546
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
547
- GuardUuidError: GuardUuidError,
548
- };
410
+ // Assembled from the gate-contract guard factory: error class, registry
411
+ // exports (NAME / KIND / INTEGRATION_FIXTURES), buildProfile /
412
+ // compliancePosture / loadRulePack wiring, plus the per-guard inspection
413
+ // surface (validate / sanitize). The gate is the factory default — the
414
+ // standard serve -> audit-only -> refuse chain — reading ctx.identifier ||
415
+ // ctx.uuid via ctxFields.
416
+ module.exports = gateContract.defineGuard({
417
+ name: "uuid",
418
+ kind: "identifier",
419
+ errorClass: GuardUuidError,
420
+ profiles: PROFILES,
421
+ defaults: DEFAULTS,
422
+ postures: COMPLIANCE_POSTURES,
423
+ integrationFixtures: INTEGRATION_FIXTURES,
424
+ validate: validate,
425
+ sanitize: sanitize,
426
+ ctxFields: ["identifier", "uuid"],
427
+ });
package/lib/guard-xml.js CHANGED
@@ -478,12 +478,8 @@ function sanitize(input, opts) {
478
478
  // shapes (DOCTYPE / ENTITY / external / parameter-entity) have no
479
479
  // safe sanitization; throw.
480
480
  var issues = _detectIssues(input, opts);
481
- for (var i = 0; i < issues.length; i += 1) {
482
- if (issues[i].severity === "critical") {
483
- throw _err(issues[i].ruleId || "xml.refused",
484
- "guardXml.sanitize: " + issues[i].snippet);
485
- }
486
- }
481
+ gateContract.throwOnRefusalSeverity(issues,
482
+ { errorClass: GuardXmlError, codePrefix: "xml", severities: ["critical"] });
487
483
  // Strip character-class threats per policy via the shared helper.
488
484
  return codepointClass.applyCharStripPolicies(input, opts);
489
485
  }
@@ -556,111 +552,40 @@ function gate(opts) {
556
552
  });
557
553
  }
558
554
 
559
- /**
560
- * @primitive b.guardXml.buildProfile
561
- * @signature b.guardXml.buildProfile(opts)
562
- * @since 0.7.15
563
- * @status stable
564
- * @related b.guardXml.gate, b.guardXml.compliancePosture
565
- *
566
- * Compose a derived profile from one or more named bases plus
567
- * inline overrides. `opts.extends` is a profile name (`"strict"` /
568
- * `"balanced"` / `"permissive"`) or an array of names; later entries
569
- * shadow earlier ones. Inline `opts` keys win last. Used to keep
570
- * operator-defined profiles traceable to a baseline rather than re-
571
- * typing every key.
572
- *
573
- * @opts
574
- * extends: string|string[], // base profile name(s) to compose
575
- * ...: any guard-xml key, // inline override of resolved keys
576
- *
577
- * @example
578
- * var custom = b.guardXml.buildProfile({
579
- * extends: "balanced",
580
- * cdataPolicy: "reject",
581
- * maxElements: 4096,
582
- * });
583
- * custom.cdataPolicy; // → "reject"
584
- * custom.maxElements; // → 4096
585
- */
586
- var buildProfile = gateContract.makeProfileBuilder(PROFILES);
587
-
588
- /**
589
- * @primitive b.guardXml.compliancePosture
590
- * @signature b.guardXml.compliancePosture(name)
591
- * @since 0.7.15
592
- * @status stable
593
- * @compliance hipaa, pci-dss, gdpr, soc2
594
- * @related b.guardXml.gate, b.guardXml.buildProfile
595
- *
596
- * Look up a compliance-posture overlay by name (`"hipaa"` /
597
- * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of the
598
- * posture object — the caller may mutate freely. Throws
599
- * `GuardXmlError("xml.bad-posture")` on unknown name.
600
- *
601
- * @example
602
- * var posture = b.guardXml.compliancePosture("hipaa");
603
- * posture.doctypePolicy; // → "reject"
604
- * posture.forensicSnippetBytes; // → 256
605
- */
606
- function compliancePosture(name) {
607
- return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "xml");
608
- }
555
+ // buildProfile / compliancePosture / loadRulePack are assembled by
556
+ // gateContract.defineGuard below; their wiki sections render from the
557
+ // single-sourced @abiTemplate (defineGuard) blocks in gate-contract.js,
558
+ // instantiated per guard by the page generator.
559
+
560
+ var INTEGRATION_FIXTURES = Object.freeze({
561
+ kind: "content",
562
+ contentType: "application/xml",
563
+ extension: ".xml",
564
+ benignBytes: Buffer.from('<?xml version="1.0"?><root><x>1</x></root>', "utf8"),
565
+ // Hostile: DOCTYPE with internal-subset entity declaration (XXE +
566
+ // billion-laughs vector CVE-2026-24400 / CVE-2024-25062 class).
567
+ hostileBytes: Buffer.from(
568
+ '<?xml version="1.0"?>\n<!DOCTYPE root [<!ENTITY xx "yy">]>\n<root/>',
569
+ "utf8"),
570
+ });
609
571
 
610
- var _xmlRulePacks = gateContract.makeRulePackLoader(GuardXmlError, "xml");
611
- /**
612
- * @primitive b.guardXml.loadRulePack
613
- * @signature b.guardXml.loadRulePack(pack)
614
- * @since 0.7.15
615
- * @status stable
616
- * @related b.guardXml.gate
617
- *
618
- * Register an operator-supplied rule pack with the guard-xml
619
- * registry. The pack is identified by `pack.id` (non-empty string)
620
- * and stored for later inspection / dispatch by gates that opt in
621
- * via `opts.rulePackId`. Returns the pack object unchanged on
622
- * success; throws `GuardXmlError("xml.bad-opt")` when `pack` is
623
- * missing or `pack.id` is not a non-empty string.
624
- *
625
- * @example
626
- * var pack = b.guardXml.loadRulePack({
627
- * id: "soap-envelope",
628
- * rules: [
629
- * { id: "must-have-envelope", severity: "high",
630
- * detect: function (text) { return text.indexOf("<soap:Envelope") === -1; },
631
- * reason: "SOAP request missing soap:Envelope root" },
632
- * ],
633
- * });
634
- * pack.id; // → "soap-envelope"
635
- */
636
- var loadRulePack = _xmlRulePacks.load;
637
-
638
- module.exports = {
639
- // ---- guard-* family registry exports ----
640
- NAME: "xml",
641
- KIND: "content",
642
- MIME_TYPES: Object.freeze(["application/xml", "text/xml"]),
643
- EXTENSIONS: Object.freeze([".xml"]),
644
- INTEGRATION_FIXTURES: Object.freeze({
645
- kind: "content",
646
- contentType: "application/xml",
647
- extension: ".xml",
648
- benignBytes: Buffer.from('<?xml version="1.0"?><root><x>1</x></root>', "utf8"),
649
- // Hostile: DOCTYPE with internal-subset entity declaration (XXE +
650
- // billion-laughs vector — CVE-2026-24400 / CVE-2024-25062 class).
651
- hostileBytes: Buffer.from(
652
- '<?xml version="1.0"?>\n<!DOCTYPE root [<!ENTITY xx "yy">]>\n<root/>',
653
- "utf8"),
654
- }),
655
- // ---- primitive surface ----
656
- validate: validate,
657
- sanitize: sanitize,
658
- gate: gate,
659
- buildProfile: buildProfile,
660
- compliancePosture: compliancePosture,
661
- loadRulePack: loadRulePack,
662
- PROFILES: PROFILES,
663
- DEFAULTS: DEFAULTS,
664
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
665
- GuardXmlError: GuardXmlError,
666
- };
572
+ // Assembled from the gate-contract guard factory: error class, registry
573
+ // exports (NAME / KIND / MIME_TYPES / EXTENSIONS / INTEGRATION_FIXTURES),
574
+ // buildProfile / compliancePosture / loadRulePack wiring, plus the
575
+ // per-guard inspection surface (validate / sanitize / bespoke gate)
576
+ // passed through verbatim. The bespoke `gate` carries XML's
577
+ // per-policy canSanitize matrix unchanged.
578
+ module.exports = gateContract.defineGuard({
579
+ name: "xml",
580
+ kind: "content",
581
+ errorClass: GuardXmlError,
582
+ profiles: PROFILES,
583
+ defaults: DEFAULTS,
584
+ postures: COMPLIANCE_POSTURES,
585
+ mimeTypes: ["application/xml", "text/xml"],
586
+ extensions: [".xml"],
587
+ integrationFixtures: INTEGRATION_FIXTURES,
588
+ validate: validate,
589
+ sanitize: sanitize,
590
+ gate: gate,
591
+ });