@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.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -2
- package/index.js +4 -0
- package/lib/agent-envelope-mac.js +104 -0
- package/lib/agent-event-bus.js +105 -4
- package/lib/agent-posture-chain.js +8 -42
- package/lib/ai-content-detect.js +9 -10
- package/lib/api-key.js +107 -74
- package/lib/atomic-file.js +62 -4
- package/lib/audit-chain.js +47 -11
- package/lib/audit-sign.js +77 -2
- package/lib/audit-tools.js +79 -51
- package/lib/audit.js +249 -123
- package/lib/auth/openid-federation.js +108 -47
- package/lib/backup/index.js +13 -10
- package/lib/break-glass.js +202 -144
- package/lib/cache.js +174 -105
- package/lib/chain-writer.js +38 -16
- package/lib/cli.js +19 -14
- package/lib/cluster-provider-db.js +130 -104
- package/lib/cluster-storage.js +119 -22
- package/lib/cluster.js +119 -71
- package/lib/compliance.js +169 -4
- package/lib/consent.js +73 -24
- package/lib/constants.js +16 -11
- package/lib/crypto-field.js +474 -92
- package/lib/db-declare-row-policy.js +35 -22
- package/lib/db-file-lifecycle.js +3 -2
- package/lib/db-query.js +497 -255
- package/lib/db-schema.js +209 -44
- package/lib/db.js +176 -95
- package/lib/error-page.js +14 -1
- package/lib/external-db-migrate.js +229 -139
- package/lib/external-db.js +25 -15
- package/lib/file-upload.js +52 -7
- package/lib/framework-error.js +14 -1
- package/lib/framework-files.js +73 -0
- package/lib/framework-schema.js +695 -394
- package/lib/gate-contract.js +649 -1
- package/lib/guard-agent-registry.js +26 -44
- package/lib/guard-all.js +1 -0
- package/lib/guard-auth.js +42 -112
- package/lib/guard-cidr.js +33 -154
- package/lib/guard-csv.js +46 -113
- package/lib/guard-domain.js +34 -157
- package/lib/guard-dsn.js +27 -43
- package/lib/guard-email.js +47 -69
- package/lib/guard-envelope.js +19 -32
- package/lib/guard-event-bus-payload.js +24 -42
- package/lib/guard-event-bus-topic.js +25 -43
- package/lib/guard-filename.js +42 -106
- package/lib/guard-graphql.js +42 -123
- package/lib/guard-html.js +53 -108
- package/lib/guard-idempotency-key.js +24 -42
- package/lib/guard-image.js +46 -103
- package/lib/guard-imap-command.js +18 -32
- package/lib/guard-jmap.js +16 -30
- package/lib/guard-json.js +38 -108
- package/lib/guard-jsonpath.js +38 -171
- package/lib/guard-jwt.js +49 -179
- package/lib/guard-list-id.js +25 -41
- package/lib/guard-list-unsubscribe.js +27 -43
- package/lib/guard-mail-compose.js +24 -42
- package/lib/guard-mail-move.js +26 -44
- package/lib/guard-mail-query.js +28 -46
- package/lib/guard-mail-reply.js +24 -42
- package/lib/guard-mail-sieve.js +24 -42
- package/lib/guard-managesieve-command.js +17 -31
- package/lib/guard-markdown.js +37 -104
- package/lib/guard-message-id.js +26 -45
- package/lib/guard-mime.js +39 -151
- package/lib/guard-oauth.js +54 -135
- package/lib/guard-pdf.js +45 -101
- package/lib/guard-pop3-command.js +21 -31
- package/lib/guard-posture-chain.js +24 -42
- package/lib/guard-regex.js +33 -107
- package/lib/guard-saga-config.js +24 -42
- package/lib/guard-shell.js +42 -172
- package/lib/guard-smtp-command.js +48 -54
- package/lib/guard-snapshot-envelope.js +24 -42
- package/lib/guard-sql.js +1491 -0
- package/lib/guard-stream-args.js +24 -43
- package/lib/guard-svg.js +47 -65
- package/lib/guard-template.js +35 -172
- package/lib/guard-tenant-id.js +26 -45
- package/lib/guard-time.js +32 -154
- package/lib/guard-trace-context.js +25 -44
- package/lib/guard-uuid.js +32 -153
- package/lib/guard-xml.js +38 -113
- package/lib/guard-yaml.js +51 -163
- package/lib/http-client.js +37 -9
- package/lib/inbox.js +120 -107
- package/lib/legal-hold.js +107 -50
- package/lib/log-stream-cloudwatch.js +47 -31
- package/lib/log-stream-otlp.js +32 -18
- package/lib/mail-crypto-smime.js +2 -6
- package/lib/mail-greylist.js +2 -6
- package/lib/mail-helo.js +2 -6
- package/lib/mail-journal.js +85 -64
- package/lib/mail-rbl.js +2 -6
- package/lib/mail-scan.js +2 -6
- package/lib/mail-server-jmap.js +117 -12
- package/lib/mail-spam-score.js +2 -6
- package/lib/mail-store.js +287 -154
- package/lib/middleware/body-parser.js +71 -25
- package/lib/middleware/csrf-protect.js +19 -8
- package/lib/middleware/fetch-metadata.js +17 -7
- package/lib/middleware/idempotency-key.js +54 -38
- package/lib/middleware/rate-limit.js +102 -32
- package/lib/middleware/security-headers.js +21 -5
- package/lib/migrations.js +108 -66
- package/lib/network-heartbeat.js +7 -0
- package/lib/nonce-store.js +31 -9
- package/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/object-store/azure-blob.js +57 -3
- package/lib/object-store/sigv4.js +10 -0
- package/lib/observability.js +87 -0
- package/lib/otel-export.js +25 -1
- package/lib/outbox.js +136 -82
- package/lib/parsers/safe-xml.js +47 -7
- package/lib/pqc-agent.js +44 -0
- package/lib/pubsub-cluster.js +42 -20
- package/lib/queue-local.js +202 -139
- package/lib/queue-redis.js +9 -1
- package/lib/queue-sqs.js +6 -0
- package/lib/redact.js +68 -11
- package/lib/redis-client.js +160 -31
- package/lib/retention.js +82 -39
- package/lib/router.js +212 -5
- package/lib/safe-dns.js +29 -45
- package/lib/safe-ical.js +18 -33
- package/lib/safe-icap.js +27 -43
- package/lib/safe-sieve.js +21 -40
- package/lib/safe-sql.js +124 -3
- package/lib/safe-vcard.js +18 -33
- package/lib/scheduler.js +35 -12
- package/lib/seeders.js +122 -74
- package/lib/session-stores.js +42 -14
- package/lib/session.js +109 -72
- package/lib/sql.js +3885 -0
- package/lib/ssrf-guard.js +51 -4
- package/lib/static.js +177 -34
- package/lib/subject.js +55 -17
- package/lib/vault/index.js +3 -2
- package/lib/vault/passphrase-ops.js +3 -2
- package/lib/vault/rotate.js +104 -64
- package/lib/vendor-data.js +2 -0
- package/lib/websocket.js +35 -5
- package/package.json +1 -1
- 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(
|
|
457
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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 =
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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 =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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 =
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
+
});
|