@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/router.js CHANGED
@@ -42,6 +42,7 @@ var lazyRequire = require("./lazy-require");
42
42
  var safeAsync = require("./safe-async");
43
43
  var safeEnv = require("./parsers/safe-env");
44
44
  var safeUrl = require("./safe-url");
45
+ var validateOpts = require("./validate-opts");
45
46
  var websocket = require("./websocket");
46
47
  var { boot } = require("./log");
47
48
  var { RouterError } = require("./framework-error");
@@ -301,6 +302,13 @@ class Router {
301
302
  constructor(opts) {
302
303
  opts = opts || {};
303
304
  this.routes = [];
305
+ // Registration-ordered middleware table. Each entry is
306
+ // `{ prefix, prefixSegments, fn }`: `prefix === null` is a global
307
+ // middleware (runs on every request); a non-null `prefix` is a
308
+ // path-scoped middleware that runs only when the request path
309
+ // matches the prefix on segment boundaries. Path-scoped and global
310
+ // entries interleave in registration order so a gate registered
311
+ // before a route still runs before it.
304
312
  this.middleware = [];
305
313
  // WebSocket routes are kept separate from HTTP routes — they're
306
314
  // matched on the upgrade / Extended CONNECT nodePath, not on a method
@@ -453,8 +461,23 @@ class Router {
453
461
  return conns.length;
454
462
  }
455
463
 
456
- use(fn) {
457
- this.middleware.push(fn);
464
+ // use(mw) global middleware (runs on every request)
465
+ // use(mw1, mw2, ...) — several global middlewares, in order
466
+ // use(prefix, mw1, mw2, ...) — path-scoped: mw runs only when the
467
+ // request path is at or beneath `prefix`
468
+ // on segment boundaries ("/admin" covers
469
+ // "/admin" + "/admin/x", not "/administrator")
470
+ // use([prefixA, prefixB], mw) — scoped to any of several prefixes
471
+ //
472
+ // Bad input throws at config time (a non-string / non-array prefix, a
473
+ // prefix that doesn't begin with "/", a non-function middleware) so an
474
+ // operator wiring typo surfaces at boot instead of silently dropping a
475
+ // security gate or 500-ing every request.
476
+ use() {
477
+ var entries = _normalizeUseArgs(Array.prototype.slice.call(arguments));
478
+ for (var i = 0; i < entries.length; i++) {
479
+ this.middleware.push(entries[i]);
480
+ }
458
481
  }
459
482
 
460
483
  // Internal: split a route registration's args into { spec, handlers }.
@@ -687,8 +710,24 @@ class Router {
687
710
  }
688
711
  req.query = Object.fromEntries(queryEntries);
689
712
 
690
- // Run middleware
691
- for (var mw of this.middleware) {
713
+ // Run middleware in registration order. Global entries
714
+ // (prefixSegmentsList === null) run on every request; path-scoped
715
+ // entries run only when req.pathname is at or beneath one of the
716
+ // mount's prefixes (segment-boundary match). A skipped scoped
717
+ // middleware does NOT short-circuit the chain — the next entry
718
+ // still runs.
719
+ for (var entry of this.middleware) {
720
+ if (entry.prefixSegmentsList !== null) {
721
+ var matched = false;
722
+ for (var pli = 0; pli < entry.prefixSegmentsList.length; pli++) {
723
+ if (_pathMatchesPrefix(entry.prefixSegmentsList[pli], req.pathname)) {
724
+ matched = true;
725
+ break;
726
+ }
727
+ }
728
+ if (!matched) continue;
729
+ }
730
+ var mw = entry.fn;
692
731
  var next = false;
693
732
  try {
694
733
  await mw(req, res, () => (next = true));
@@ -1206,6 +1245,152 @@ class Router {
1206
1245
  }
1207
1246
  }
1208
1247
 
1248
+ // ---- Path-scoped `use()` helpers ----
1249
+ //
1250
+ // These back `Router.use(prefix, mw)`. They live after the class
1251
+ // (hoisted function declarations are visible to the methods regardless
1252
+ // of source order) so the class methods sit contiguous with the
1253
+ // route-matching helpers above.
1254
+
1255
+ // Compile a `use(prefix, mw)` path prefix into the segment list the
1256
+ // matcher walks. A prefix is the literal-segment portion of a path
1257
+ // ("/admin", "/.well-known/jmap") — parameter segments (":id") are not
1258
+ // meaningful for a mounting prefix, so they're treated as literals.
1259
+ //
1260
+ // Normalization mirrors route-pattern handling: split on "/" and drop a
1261
+ // single trailing-slash artifact so "/admin" and "/admin/" mount the
1262
+ // same. The leading empty segment from the leading "/" is preserved so
1263
+ // the prefix anchors at the path root (a prefix that does not begin with
1264
+ // "/" is refused at the use() entry point).
1265
+ function _compilePrefix(prefix) {
1266
+ var segments = prefix.split("/");
1267
+ // A trailing "/" produces a final empty segment ("/admin/" → ["", "admin", ""]).
1268
+ // Drop it so the trailing slash doesn't force an extra path segment.
1269
+ if (segments.length > 1 && segments[segments.length - 1] === "") {
1270
+ segments.pop();
1271
+ }
1272
+ return segments;
1273
+ }
1274
+
1275
+ // True when `pathname` is at or beneath the mounting prefix, matching on
1276
+ // segment boundaries. "/admin" matches "/admin" and "/admin/x" but NOT
1277
+ // "/administrator" (Express-style segment semantics) — a substring
1278
+ // prefix that lands mid-segment is a no-match so a security gate scoped
1279
+ // to "/admin" never leaks onto a sibling path that merely shares a
1280
+ // textual prefix.
1281
+ function _pathMatchesPrefix(prefixSegments, pathname) {
1282
+ var pathSegments = pathname.split("/");
1283
+ if (pathSegments.length < prefixSegments.length) return false;
1284
+ // Compare segment-for-segment. This is routing metadata (public URL
1285
+ // path), not secret material, so an ordinary equality walk is correct
1286
+ // — no constant-time comparison is warranted.
1287
+ return prefixSegments.every(function (seg, i) {
1288
+ return pathSegments[i] === seg;
1289
+ });
1290
+ }
1291
+
1292
+ // Classify the first `use()` argument:
1293
+ // function → global mount (prefixes stays null)
1294
+ // string / string[] → path-scoped mount (one or more prefixes)
1295
+ // anything else → operator wiring typo, refused at config time
1296
+ // Returns the prefix array (or null for a global mount). Does not touch
1297
+ // the middleware functions — the caller validates those.
1298
+ function _usePrefixesFromFirstArg(first) {
1299
+ if (typeof first === "function") return null;
1300
+ if (typeof first !== "string" && !Array.isArray(first)) {
1301
+ throw new RouterError("router/use-bad-first-arg",
1302
+ "router.use: first argument must be a middleware function, a path " +
1303
+ "prefix string, or an array of prefix strings (got " +
1304
+ (first === null ? "null" : typeof first) + ")");
1305
+ }
1306
+ var prefixes = Array.isArray(first) ? first : [first];
1307
+ if (prefixes.length === 0) {
1308
+ throw new RouterError("router/use-empty-prefix-array",
1309
+ "router.use: path-prefix array must contain at least one prefix string");
1310
+ }
1311
+ // Array-of-non-empty-strings shape (the index-pointing throw on a
1312
+ // non-string / empty entry) is the shared validate-opts contract.
1313
+ validateOpts.optionalNonEmptyStringArray(
1314
+ prefixes, "router.use: path prefix", RouterError, "router/use-prefix-not-string");
1315
+ // Prefix-specific grammar: anchor at "/" + bounded length.
1316
+ for (var i = 0; i < prefixes.length; i++) {
1317
+ var grammarErr = _prefixGrammarError(prefixes[i]);
1318
+ if (grammarErr) throw grammarErr;
1319
+ }
1320
+ return prefixes;
1321
+ }
1322
+
1323
+ // Return a RouterError describing why `prefix` is not a valid mounting
1324
+ // prefix, or null when it is valid. Split out so the per-prefix grammar
1325
+ // check is one branch, not an inline throw-block in the loop.
1326
+ function _prefixGrammarError(prefix) {
1327
+ if (prefix.charAt(0) !== "/") {
1328
+ return new RouterError("router/use-prefix-not-absolute",
1329
+ "router.use: path prefix '" + prefix + "' must begin with '/'");
1330
+ }
1331
+ if (prefix.length > MAX_ROUTE_PATTERN_LEN) {
1332
+ return new RouterError("router/use-prefix-too-long",
1333
+ "router.use: path prefix exceeds " + MAX_ROUTE_PATTERN_LEN +
1334
+ " chars (got " + prefix.length + ")");
1335
+ }
1336
+ return null;
1337
+ }
1338
+
1339
+ // Index of the first non-function entry in `fns`, or -1 when all are
1340
+ // functions. Kept separate so the middleware-shape check is a scan, not
1341
+ // an inline throw inside the normalize flow.
1342
+ function _firstNonFunctionIndex(fns) {
1343
+ for (var i = 0; i < fns.length; i++) {
1344
+ if (typeof fns[i] !== "function") return i;
1345
+ }
1346
+ return -1;
1347
+ }
1348
+
1349
+ // Validate + normalize the `use()` arguments into middleware-table
1350
+ // entries. Config-time entry-point tier: a non-string / non-array-of-
1351
+ // strings prefix or a non-function middleware is an operator wiring
1352
+ // typo and throws so it surfaces at boot, not as a silent dropped gate
1353
+ // or a request-time 500. Returns one entry per middleware function:
1354
+ // { prefix: null, prefixSegmentsList: null, fn } — global
1355
+ // { prefix: "/admin", prefixSegmentsList: [[...], ...], fn } — scoped
1356
+ // A scoped entry matches when ANY of its prefixes match the request
1357
+ // path on segment boundaries; the fn runs at most once per request even
1358
+ // when two of its prefixes both match (nested prefixes), so a gate never
1359
+ // double-executes.
1360
+ function _normalizeUseArgs(args) {
1361
+ if (args.length === 0) {
1362
+ throw new RouterError("router/use-no-args",
1363
+ "router.use: requires at least one middleware function");
1364
+ }
1365
+ var prefixes = _usePrefixesFromFirstArg(args[0]);
1366
+ // Global mount uses every arg as a middleware; a scoped mount drops the
1367
+ // leading prefix arg and uses the rest.
1368
+ var fns = (prefixes === null) ? args : args.slice(1);
1369
+ if (fns.length === 0) {
1370
+ throw new RouterError("router/use-no-middleware",
1371
+ "router.use: path-scoped mount requires at least one middleware " +
1372
+ "function after the prefix");
1373
+ }
1374
+ var nonFn = _firstNonFunctionIndex(fns);
1375
+ if (nonFn !== -1) {
1376
+ throw new RouterError("router/use-middleware-not-function",
1377
+ "router.use: middleware at position " + nonFn +
1378
+ " must be a function (got " +
1379
+ (fns[nonFn] === null ? "null" : typeof fns[nonFn]) + ")");
1380
+ }
1381
+ // Pre-compile each prefix to its segment list once at registration so
1382
+ // dispatch only walks segments, never re-splits the prefix per request.
1383
+ var segmentsList = (prefixes === null) ? null : prefixes.map(_compilePrefix);
1384
+ var label = (prefixes === null) ? null
1385
+ : (prefixes.length === 1 ? prefixes[0] : prefixes.slice());
1386
+ // One entry per fn, preserving the order each middleware was passed —
1387
+ // a path-scoped mount with two middlewares interleaves them in
1388
+ // registration order just like two separate use() calls would.
1389
+ return fns.map(function (fn) {
1390
+ return { prefix: label, prefixSegmentsList: segmentsList, fn: fn };
1391
+ });
1392
+ }
1393
+
1209
1394
  /**
1210
1395
  * @primitive b.router.serveStatic
1211
1396
  * @signature b.router.serveStatic(dir)
@@ -1266,7 +1451,7 @@ function serveStatic(dir) {
1266
1451
  *
1267
1452
  * Builds a `Router` instance with the framework's security-on-by-
1268
1453
  * default posture. Returned object exposes `get / post / put / patch
1269
- * / delete` for route registration, `use(fn)` for global middleware,
1454
+ * / delete` for route registration, `use(...)` for middleware,
1270
1455
  * `ws(path, handler, opts?)` for WebSocket routes, `onNotFound(fn)`
1271
1456
  * and `onError(fn)` for fallthrough hooks, `inspectRoutes()` and
1272
1457
  * `openapi()` for introspection, `closeWebSockets({ timeoutMs })`
@@ -1274,6 +1459,21 @@ function serveStatic(dir) {
1274
1459
  * which boots an HTTP/2-capable TLS server (ALPN h2 + http/1.1) when
1275
1460
  * `tlsOptions` is provided, an HTTP/1.1 server otherwise.
1276
1461
  *
1462
+ * `use` has two forms. `use(mw)` (and `use(mw1, mw2, ...)`) mounts
1463
+ * global middleware that runs on every request. `use(prefix, mw1,
1464
+ * mw2, ...)` mounts path-scoped middleware that runs only when the
1465
+ * request path is at or beneath `prefix`, matched on segment
1466
+ * boundaries — `"/admin"` covers `"/admin"` and `"/admin/x"` but not
1467
+ * `"/administrator"`. The prefix may be an array of strings to scope a
1468
+ * gate to several path roots at once. Global and scoped middleware
1469
+ * interleave in registration order, so a gate registered before a
1470
+ * route still runs before it. A non-string / non-array prefix, a
1471
+ * prefix not beginning with `"/"`, or a non-function middleware throws
1472
+ * at registration time rather than dropping the gate or 500-ing every
1473
+ * request — scope a security middleware (`csrf`, `bearerAuth`,
1474
+ * `requireAal`, `requireMtls`) to a path with confidence it runs
1475
+ * exactly where mounted.
1476
+ *
1277
1477
  * @opts
1278
1478
  * tls0Rtt: "refuse" | "replay-cache", // RFC 8446 §8 anti-replay; default "refuse"
1279
1479
  * allowedRedirectOrigins: string[], // exact-match HTTPS origins for cross-origin res.redirect()
@@ -1286,6 +1486,13 @@ function serveStatic(dir) {
1286
1486
  * router.get("/users/:id", function (req, res) {
1287
1487
  * res.json({ id: req.params.id });
1288
1488
  * });
1489
+ *
1490
+ * // Global middleware — runs on every request.
1491
+ * router.use(b.middleware.securityHeaders());
1492
+ *
1493
+ * // Path-scoped middleware — the step-up gate runs only under /admin.
1494
+ * router.use("/admin", b.middleware.requireAal({ minimum: "AAL2" }));
1495
+ *
1289
1496
  * router.listen(3000);
1290
1497
  */
1291
1498
  function create(opts) {
package/lib/safe-dns.js CHANGED
@@ -57,6 +57,7 @@
57
57
 
58
58
  var C = require("./constants");
59
59
  var { defineClass } = require("./framework-error");
60
+ var gateContract = require("./gate-contract");
60
61
 
61
62
  var SafeDnsError = defineClass("SafeDnsError", { alwaysPermanent: true });
62
63
 
@@ -153,11 +154,15 @@ var PROFILES = Object.freeze({
153
154
  },
154
155
  });
155
156
 
156
- var COMPLIANCE_POSTURES = Object.freeze({
157
- hipaa: "strict",
158
- "pci-dss": "strict",
159
- gdpr: "strict",
160
- soc2: "strict",
157
+ var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
158
+
159
+ var _resolveProfile = gateContract.makeProfileResolver({
160
+ profiles: PROFILES,
161
+ postures: COMPLIANCE_POSTURES,
162
+ defaults: DEFAULT_PROFILE,
163
+ errorClass: SafeDnsError,
164
+ codePrefix: "safe-dns",
165
+ byObject: true,
161
166
  });
162
167
 
163
168
  /**
@@ -349,22 +354,6 @@ function checkCnameChainDepth(depth, opts) {
349
354
  }
350
355
  }
351
356
 
352
- /**
353
- * @primitive b.safeDns.compliancePosture
354
- * @signature b.safeDns.compliancePosture(posture)
355
- * @since 0.9.31
356
- * @status stable
357
- *
358
- * Return the effective profile name for a compliance posture, or
359
- * `null` for unknown posture names (operator typo surfaces here).
360
- *
361
- * @example
362
- * b.safeDns.compliancePosture("hipaa"); // → "strict"
363
- */
364
- function compliancePosture(posture) {
365
- return COMPLIANCE_POSTURES[posture] || null;
366
- }
367
-
368
357
  function _readName(state, pointerDepth) {
369
358
  if (pointerDepth > state.caps.maxPointerDepth) {
370
359
  throw new SafeDnsError("safe-dns/oversize-pointer-depth",
@@ -639,27 +628,22 @@ function _decodeOpt(rr, caps) {
639
628
  };
640
629
  }
641
630
 
642
- function _resolveProfile(opts) {
643
- if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
644
- return PROFILES[COMPLIANCE_POSTURES[opts.posture]];
645
- }
646
- var p = opts.profile || DEFAULT_PROFILE;
647
- if (!PROFILES[p]) {
648
- throw new SafeDnsError("safe-dns/bad-profile",
649
- "safeDns: unknown profile '" + p + "' (valid: strict / balanced / permissive)");
650
- }
651
- return PROFILES[p];
652
- }
653
-
654
- module.exports = {
655
- parseResponse: parseResponse,
656
- boundEdns0: boundEdns0,
657
- checkCnameChainDepth: checkCnameChainDepth,
658
- compliancePosture: compliancePosture,
659
- PROFILES: PROFILES,
660
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
661
- RTYPE_NAMES: RTYPE_NAMES,
662
- SafeDnsError: SafeDnsError,
663
- NAME: "dns",
664
- KIND: "dns-response",
665
- };
631
+ // compliancePosture is assembled by gateContract.defineParser below; its
632
+ // wiki section renders from the single-sourced @abiTemplate (defineParser)
633
+ // block in gate-contract.js, instantiated for this guard by the page
634
+ // generator.
635
+ module.exports = gateContract.defineParser({
636
+ name: "dns",
637
+ entry: parseResponse,
638
+ entryName: "parseResponse",
639
+ errorClass: SafeDnsError,
640
+ profiles: PROFILES,
641
+ postures: COMPLIANCE_POSTURES,
642
+ extra: {
643
+ boundEdns0: boundEdns0,
644
+ checkCnameChainDepth: checkCnameChainDepth,
645
+ RTYPE_NAMES: RTYPE_NAMES,
646
+ NAME: "dns",
647
+ KIND: "dns-response",
648
+ },
649
+ });
package/lib/safe-ical.js CHANGED
@@ -81,6 +81,7 @@
81
81
 
82
82
  var C = require("./constants");
83
83
  var { defineClass } = require("./framework-error");
84
+ var gateContract = require("./gate-contract");
84
85
 
85
86
  var SafeIcalError = defineClass("SafeIcalError", { alwaysPermanent: true });
86
87
 
@@ -116,12 +117,7 @@ var PROFILES = Object.freeze({
116
117
  }),
117
118
  });
118
119
 
119
- var COMPLIANCE_POSTURES = Object.freeze({
120
- hipaa: "strict",
121
- "pci-dss": "strict",
122
- gdpr: "strict",
123
- soc2: "strict",
124
- });
120
+ var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
125
121
 
126
122
  // Property-name allowlist per RFC 5545 §8.7 (Property Registry) +
127
123
  // RFC 5546 §4.3 (iTIP additions) + RFC 7986 §5 (new calendar
@@ -273,24 +269,6 @@ function parse(text, opts) {
273
269
  : { vcalendar: vcalendars[0], vcalendars: vcalendars };
274
270
  }
275
271
 
276
- /**
277
- * @primitive b.safeIcal.compliancePosture
278
- * @signature b.safeIcal.compliancePosture(name)
279
- * @since 0.9.81
280
- * @status stable
281
- * @related b.safeIcal.parse
282
- *
283
- * Map a compliance-posture name to its profile. Returns the profile
284
- * string for a known posture, `null` for unknown names.
285
- *
286
- * @example
287
- * b.safeIcal.compliancePosture("hipaa"); // → "strict"
288
- * b.safeIcal.compliancePosture("loose"); // → null
289
- */
290
- function compliancePosture(name) {
291
- return COMPLIANCE_POSTURES[name] || null;
292
- }
293
-
294
272
  // ---- Profile / opt resolution ----
295
273
 
296
274
  function _resolveCaps(opts) {
@@ -623,12 +601,19 @@ function _preview(s) {
623
601
  return s.length > 64 ? s.slice(0, 64) + "..." : s; // log-preview length cap
624
602
  }
625
603
 
626
- module.exports = {
627
- parse: parse,
628
- compliancePosture: compliancePosture,
629
- PROFILES: PROFILES,
630
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
631
- KNOWN_PROPERTIES: KNOWN_PROPERTIES,
632
- KNOWN_COMPONENTS: KNOWN_COMPONENTS,
633
- SafeIcalError: SafeIcalError,
634
- };
604
+ // compliancePosture is assembled by gateContract.defineParser below; its
605
+ // wiki section renders from the single-sourced @abiTemplate (defineParser)
606
+ // block in gate-contract.js, instantiated for this guard by the page
607
+ // generator.
608
+ module.exports = gateContract.defineParser({
609
+ name: "ical",
610
+ entry: parse,
611
+ entryName: "parse",
612
+ errorClass: SafeIcalError,
613
+ profiles: PROFILES,
614
+ postures: COMPLIANCE_POSTURES,
615
+ extra: {
616
+ KNOWN_PROPERTIES: KNOWN_PROPERTIES,
617
+ KNOWN_COMPONENTS: KNOWN_COMPONENTS,
618
+ },
619
+ });
package/lib/safe-icap.js CHANGED
@@ -77,6 +77,7 @@
77
77
 
78
78
  var C = require("./constants");
79
79
  var { defineClass } = require("./framework-error");
80
+ var gateContract = require("./gate-contract");
80
81
 
81
82
  var SafeIcapError = defineClass("SafeIcapError", { alwaysPermanent: true });
82
83
 
@@ -131,11 +132,15 @@ var PROFILES = Object.freeze({
131
132
  },
132
133
  });
133
134
 
134
- var COMPLIANCE_POSTURES = Object.freeze({
135
- hipaa: "strict",
136
- "pci-dss": "strict",
137
- gdpr: "strict",
138
- soc2: "strict",
135
+ var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
136
+
137
+ var _resolveProfile = gateContract.makeProfileResolver({
138
+ profiles: PROFILES,
139
+ postures: COMPLIANCE_POSTURES,
140
+ defaults: DEFAULT_PROFILE,
141
+ errorClass: SafeIcapError,
142
+ codePrefix: "safe-icap",
143
+ byObject: true,
139
144
  });
140
145
 
141
146
  /**
@@ -257,22 +262,6 @@ function parse(buf, opts) {
257
262
  };
258
263
  }
259
264
 
260
- /**
261
- * @primitive b.safeIcap.compliancePosture
262
- * @signature b.safeIcap.compliancePosture(posture)
263
- * @since 0.9.81
264
- * @status stable
265
- *
266
- * Return the effective profile name for a compliance posture, or
267
- * `null` for unknown posture names.
268
- *
269
- * @example
270
- * b.safeIcap.compliancePosture("hipaa"); // → "strict"
271
- */
272
- function compliancePosture(posture) {
273
- return COMPLIANCE_POSTURES[posture] || null;
274
- }
275
-
276
265
  // ---- internals ----
277
266
 
278
267
  function _findHeaderEnd(buf, maxHeaderBytes) {
@@ -479,25 +468,20 @@ function _detectThreat(statusCode, headers) {
479
468
  return { found: found, name: name };
480
469
  }
481
470
 
482
- function _resolveProfile(opts) {
483
- if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
484
- return PROFILES[COMPLIANCE_POSTURES[opts.posture]];
485
- }
486
- var p = opts.profile || DEFAULT_PROFILE;
487
- if (!PROFILES[p]) {
488
- throw new SafeIcapError("safe-icap/bad-profile",
489
- "safeIcap: unknown profile '" + p + "' (valid: strict / balanced / permissive)");
490
- }
491
- return PROFILES[p];
492
- }
493
-
494
- module.exports = {
495
- parse: parse,
496
- compliancePosture: compliancePosture,
497
- PROFILES: PROFILES,
498
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
499
- ALLOWED_STATUS: ALLOWED_STATUS,
500
- SafeIcapError: SafeIcapError,
501
- NAME: "icap",
502
- KIND: "icap-response",
503
- };
471
+ // compliancePosture is assembled by gateContract.defineParser below; its
472
+ // wiki section renders from the single-sourced @abiTemplate (defineParser)
473
+ // block in gate-contract.js, instantiated for this guard by the page
474
+ // generator.
475
+ module.exports = gateContract.defineParser({
476
+ name: "icap",
477
+ entry: parse,
478
+ entryName: "parse",
479
+ errorClass: SafeIcapError,
480
+ profiles: PROFILES,
481
+ postures: COMPLIANCE_POSTURES,
482
+ extra: {
483
+ ALLOWED_STATUS: ALLOWED_STATUS,
484
+ NAME: "icap",
485
+ KIND: "icap-response",
486
+ },
487
+ });
package/lib/safe-sieve.js CHANGED
@@ -49,6 +49,7 @@
49
49
  */
50
50
 
51
51
  var { defineClass } = require("./framework-error");
52
+ var gateContract = require("./gate-contract");
52
53
 
53
54
  var SafeSieveError = defineClass("SafeSieveError", { alwaysPermanent: true });
54
55
 
@@ -82,12 +83,7 @@ var PROFILES = Object.freeze({
82
83
  }),
83
84
  });
84
85
 
85
- var COMPLIANCE_POSTURES = Object.freeze({
86
- hipaa: "strict",
87
- "pci-dss": "strict",
88
- gdpr: "strict",
89
- soc2: "strict",
90
- });
86
+ var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
91
87
 
92
88
  // RFC 5228 §1.2 capability identifiers. Each entry lists whether the
93
89
  // framework's v0.9.55 interpreter implements the capability. Unknown
@@ -648,37 +644,22 @@ function validate(script, opts) {
648
644
  }
649
645
  }
650
646
 
651
- /**
652
- * @primitive b.safeSieve.compliancePosture
653
- * @signature b.safeSieve.compliancePosture(name)
654
- * @since 0.9.55
655
- * @status stable
656
- * @related b.safeSieve.parse, b.safeSieve.validate
657
- *
658
- * Look up the recommended profile name for a compliance posture
659
- * (`hipaa` / `pci-dss` / `gdpr` / `soc2`). Returns `"strict"` for any
660
- * known posture, `null` for unknown names. Operator-facing primitives
661
- * that thread `compliancePosture` opt through to safeSieve compose
662
- * this for the explicit-cast pattern when they need the name string
663
- * (rather than relying on `_resolveOpts` to do the lookup).
664
- *
665
- * @example
666
- * b.safeSieve.compliancePosture("hipaa"); // → "strict"
667
- * b.safeSieve.compliancePosture("loose"); // → null
668
- */
669
- function compliancePosture(name) {
670
- return COMPLIANCE_POSTURES[name] || null;
671
- }
672
-
673
- module.exports = {
674
- parse: parse,
675
- validate: validate,
676
- compliancePosture: compliancePosture,
677
- KNOWN_CAPABILITIES: KNOWN_CAPABILITIES,
678
- PROFILES: PROFILES,
679
- COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
680
- SafeSieveError: SafeSieveError,
681
- // Internal exports for the interpreter at lib/mail-sieve.js.
682
- _tokenize: _tokenize,
683
- _resolveCaps: _resolveCaps,
684
- };
647
+ // compliancePosture is assembled by gateContract.defineParser below; its
648
+ // wiki section renders from the single-sourced @abiTemplate (defineParser)
649
+ // block in gate-contract.js, instantiated for this guard by the page
650
+ // generator.
651
+ module.exports = gateContract.defineParser({
652
+ name: "sieve",
653
+ entry: parse,
654
+ entryName: "parse",
655
+ errorClass: SafeSieveError,
656
+ profiles: PROFILES,
657
+ postures: COMPLIANCE_POSTURES,
658
+ extra: {
659
+ validate: validate,
660
+ KNOWN_CAPABILITIES: KNOWN_CAPABILITIES,
661
+ // Internal exports for the interpreter at lib/mail-sieve.js.
662
+ _tokenize: _tokenize,
663
+ _resolveCaps: _resolveCaps,
664
+ },
665
+ });