@blamejs/core 0.15.11 → 0.15.13
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 +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +7 -23
- package/lib/acme.js +13 -16
- package/lib/agent-event-bus.js +5 -4
- package/lib/agent-idempotency.js +2 -5
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-capability.js +1 -6
- package/lib/ai-dp.js +1 -5
- package/lib/ai-input.js +2 -2
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-read.js +5 -17
- package/lib/archive-tar-read.js +5 -16
- package/lib/archive.js +2 -10
- package/lib/arg-parser.js +7 -6
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +108 -31
- package/lib/audit-chain.js +133 -53
- package/lib/audit-daily-review.js +24 -14
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +257 -0
- package/lib/audit.js +84 -0
- package/lib/auth/access-lock.js +5 -27
- package/lib/auth/dpop.js +23 -35
- package/lib/auth/fido-mds3.js +9 -15
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +13 -15
- package/lib/auth/lockout.js +6 -25
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +3 -3
- package/lib/auth/password.js +7 -1
- package/lib/auth/saml.js +37 -27
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +1 -1
- package/lib/auth/status-list.js +7 -7
- package/lib/auth/step-up.js +6 -12
- package/lib/auth-bot-challenge.js +6 -37
- package/lib/backup/bundle.js +11 -18
- package/lib/backup/index.js +14 -47
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/cache.js +7 -18
- package/lib/cbor.js +2 -1
- package/lib/cdn-cache-control.js +8 -9
- package/lib/cert.js +3 -10
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +5 -1
- package/lib/client-hints.js +10 -10
- package/lib/cloud-events.js +40 -31
- package/lib/cluster-storage.js +2 -38
- package/lib/cluster.js +4 -2
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +67 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +1 -11
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +2 -14
- package/lib/compliance.js +2 -9
- package/lib/config-drift.js +2 -12
- package/lib/content-digest.js +10 -8
- package/lib/cookies.js +5 -11
- package/lib/cose.js +19 -11
- package/lib/cra-report.js +1 -11
- package/lib/crypto-field.js +5 -10
- package/lib/crypto.js +120 -3
- package/lib/csp.js +235 -3
- package/lib/daemon.js +42 -41
- package/lib/data-act.js +19 -9
- package/lib/db-query.js +6 -40
- package/lib/db.js +34 -12
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dsr.js +8 -12
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +14 -26
- package/lib/fda-21cfr11.js +12 -8
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +869 -38
- package/lib/gdpr-ropa.js +4 -13
- package/lib/graphql-federation.js +1 -1
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +1 -0
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +16 -13
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +1 -1
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +18 -62
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +65 -117
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +8 -9
- package/lib/guard-json.js +87 -103
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +2 -1
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +5 -4
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +12 -13
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +8 -11
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +95 -140
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/http-client-cache.js +8 -13
- package/lib/http-client-cookie-jar.js +2 -4
- package/lib/http-client.js +8 -21
- package/lib/http-message-signature.js +26 -8
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +3 -21
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +6 -15
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +28 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-syslog.js +6 -0
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +24 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +3 -2
- package/lib/mail-auth.js +40 -66
- package/lib/mail-bimi.js +19 -39
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +6 -9
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +15 -26
- package/lib/mail-helo.js +2 -3
- package/lib/mail-journal.js +2 -1
- package/lib/mail-mdn.js +4 -3
- package/lib/mail-rbl.js +5 -8
- package/lib/mail-scan.js +2 -2
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +35 -51
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +89 -0
- package/lib/mail-spam-score.js +7 -8
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +11 -11
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +54 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +1 -1
- package/lib/mdoc.js +11 -12
- package/lib/metrics.js +32 -30
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +11 -22
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +79 -54
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +2 -10
- package/lib/middleware/csrf-protect.js +13 -2
- package/lib/middleware/daily-byte-quota.js +6 -26
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +7 -0
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/rate-limit.js +20 -28
- package/lib/middleware/scim-server.js +3 -2
- package/lib/middleware/security-headers.js +9 -1
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/tus-upload.js +12 -27
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +116 -36
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +97 -6
- package/lib/network-dns.js +22 -26
- package/lib/network-dnssec.js +16 -16
- package/lib/network-heartbeat.js +6 -5
- package/lib/network-proxy.js +3 -7
- package/lib/network-smtp-policy.js +29 -41
- package/lib/network-tls.js +74 -55
- package/lib/network.js +2 -6
- package/lib/nis2-report.js +1 -11
- package/lib/nonce-store.js +81 -3
- package/lib/notify.js +7 -12
- package/lib/numeric-bounds.js +22 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +5 -45
- package/lib/object-store/gcs.js +8 -71
- package/lib/object-store/http-put.js +1 -5
- package/lib/object-store/http-request.js +128 -0
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +9 -77
- package/lib/observability-otlp-exporter.js +9 -25
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +43 -37
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +2 -2
- package/lib/parsers/safe-yaml.js +7 -6
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/redact.js +2 -1
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +133 -6
- package/lib/restore.js +5 -22
- package/lib/retention.js +3 -5
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +12 -26
- package/lib/safe-json.js +7 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +12 -26
- package/lib/sandbox-worker.js +9 -2
- package/lib/sandbox.js +3 -2
- package/lib/scheduler.js +5 -12
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +9 -21
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +194 -6
- package/lib/sql.js +225 -78
- package/lib/sse.js +6 -10
- package/lib/static.js +136 -98
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +313 -17
- package/lib/tenant-quota.js +2 -20
- package/lib/tsa.js +11 -15
- package/lib/validate-opts.js +154 -8
- package/lib/vault/seal-pem-file.js +30 -48
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +2 -7
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +43 -18
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +7 -3
- package/lib/worm.js +2 -3
- package/lib/ws-client.js +8 -10
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -115,6 +115,8 @@
|
|
|
115
115
|
*/
|
|
116
116
|
|
|
117
117
|
var nodeFs = require("node:fs");
|
|
118
|
+
var pick = require("../pick");
|
|
119
|
+
var numericBounds = require("../numeric-bounds");
|
|
118
120
|
var os = require("node:os");
|
|
119
121
|
var nodePath = require("node:path");
|
|
120
122
|
var nodeCrypto = require("node:crypto");
|
|
@@ -164,7 +166,6 @@ var BodyParserError = defineClass("BodyParserError", { withStatusCode: true });
|
|
|
164
166
|
// Mirrors safe-json.js + safe-schema.js. Field names that match these
|
|
165
167
|
// are refused at the parse boundary regardless of which sub-parser is
|
|
166
168
|
// in play — consistent prototype-pollution defense across the framework.
|
|
167
|
-
var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
168
169
|
|
|
169
170
|
// Materialize a header/parameter map from request-derived [key, value]
|
|
170
171
|
// pairs WITHOUT a computed member write (`target[key] = value`). A
|
|
@@ -183,7 +184,7 @@ var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
|
183
184
|
function _mapFromPairs(pairs) {
|
|
184
185
|
var safe = [];
|
|
185
186
|
for (var i = 0; i < pairs.length; i++) {
|
|
186
|
-
if (
|
|
187
|
+
if (pick.isPoisonedKey(pairs[i][0])) continue;
|
|
187
188
|
safe.push(pairs[i]);
|
|
188
189
|
}
|
|
189
190
|
return Object.assign(Object.create(null), Object.fromEntries(safe));
|
|
@@ -256,17 +257,13 @@ function _contentType(req) {
|
|
|
256
257
|
// would slice through quoted commas/semicolons and corrupt the
|
|
257
258
|
// multipart boundary. Use the shared quote-aware splitter that
|
|
258
259
|
// tracks RFC 8941 §3.3.3 quoted-string state with backslash-escape.
|
|
259
|
-
var
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
var eq = p.indexOf("=");
|
|
263
|
-
if (eq === -1) continue;
|
|
264
|
-
var k = p.slice(0, eq).trim().toLowerCase();
|
|
265
|
-
var v = p.slice(eq + 1).trim();
|
|
260
|
+
var kvps = structuredFields.parseKeyValuePieces(
|
|
261
|
+
structuredFields.splitTopLevel(rest, ";"));
|
|
262
|
+
structuredFields.forEachKeyValue(kvps, function (key, v) {
|
|
266
263
|
var _unq = structuredFields.unquoteSfString(v);
|
|
267
264
|
if (_unq !== null) v = _unq;
|
|
268
|
-
paramPairs.push([
|
|
269
|
-
}
|
|
265
|
+
paramPairs.push([key, v]);
|
|
266
|
+
});
|
|
270
267
|
}
|
|
271
268
|
return { type: type, params: _mapFromPairs(paramPairs) };
|
|
272
269
|
}
|
|
@@ -375,6 +372,19 @@ function _detectSmuggling(req) {
|
|
|
375
372
|
return null;
|
|
376
373
|
}
|
|
377
374
|
|
|
375
|
+
// Generic, operator-safe status phrase for a client error body — used when the
|
|
376
|
+
// thrown error is NOT a framework-classified BodyParserError (or is a 5xx), so
|
|
377
|
+
// an internal exception's message (fs errno + tmp path, a parse hook's thrown
|
|
378
|
+
// detail) is never echoed to the client (CWE-209 / CodeQL js/stack-trace-exposure).
|
|
379
|
+
var _GENERIC_REASON = {};
|
|
380
|
+
_GENERIC_REASON[HTTP_STATUS.BAD_REQUEST] = "Bad Request";
|
|
381
|
+
_GENERIC_REASON[HTTP_STATUS.PAYLOAD_TOO_LARGE] = "Payload Too Large";
|
|
382
|
+
_GENERIC_REASON[HTTP_STATUS.UNSUPPORTED_MEDIA_TYPE] = "Unsupported Media Type";
|
|
383
|
+
_GENERIC_REASON[HTTP_STATUS.INTERNAL_SERVER_ERROR] = "Internal Server Error";
|
|
384
|
+
function _genericReason(status) {
|
|
385
|
+
return _GENERIC_REASON[status] || (status >= 500 ? "Internal Server Error" : "Bad Request");
|
|
386
|
+
}
|
|
387
|
+
|
|
378
388
|
function _writeError(res, status, message, code) {
|
|
379
389
|
if (res.headersSent) return;
|
|
380
390
|
var body = JSON.stringify({ error: message, code: code });
|
|
@@ -417,26 +427,17 @@ function _bufferBody(req, limit) {
|
|
|
417
427
|
return;
|
|
418
428
|
}
|
|
419
429
|
}
|
|
420
|
-
|
|
430
|
+
safeBuffer.collectStream(req, {
|
|
421
431
|
maxBytes: limit,
|
|
422
432
|
errorClass: BodyParserError,
|
|
423
433
|
sizeCode: "body-parser/too-large",
|
|
424
434
|
sizeMessage: "request body exceeds limit",
|
|
435
|
+
}).then(resolve, function (e) {
|
|
436
|
+
// The drain-overflow guard (a second line of defense behind the
|
|
437
|
+
// Content-Length pre-check above) carries the 413 status code.
|
|
438
|
+
if (e && e.isBodyParserError) e.statusCode = HTTP_STATUS.PAYLOAD_TOO_LARGE;
|
|
439
|
+
reject(e);
|
|
425
440
|
});
|
|
426
|
-
var done = false;
|
|
427
|
-
req.on("data", function (chunk) {
|
|
428
|
-
if (done) return;
|
|
429
|
-
try { collector.push(chunk); }
|
|
430
|
-
catch (e) {
|
|
431
|
-
done = true;
|
|
432
|
-
try { req.destroy(); } catch (_e) { /* socket already closed */ }
|
|
433
|
-
if (e && e.isBodyParserError) e.statusCode = HTTP_STATUS.PAYLOAD_TOO_LARGE;
|
|
434
|
-
reject(e);
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
req.on("end", function () { if (!done) { done = true; resolve(collector.result()); } });
|
|
439
|
-
req.on("error", function (e) { if (!done) { done = true; reject(e); } });
|
|
440
441
|
});
|
|
441
442
|
}
|
|
442
443
|
|
|
@@ -469,10 +470,13 @@ async function _parseJson(req, opts) {
|
|
|
469
470
|
}
|
|
470
471
|
if (typeof opts.parseHook === "function") {
|
|
471
472
|
try { parsed = opts.parseHook(parsed); }
|
|
472
|
-
catch (
|
|
473
|
+
catch (_e) {
|
|
473
474
|
throw new BodyParserError(
|
|
474
475
|
"body-parser/json-hook",
|
|
475
|
-
|
|
476
|
+
// Operator parseHook threw — surface a fixed, safe message; the hook's
|
|
477
|
+
// own thrown detail (which may carry secrets) is the operator's to log,
|
|
478
|
+
// never echoed to the client (CWE-209).
|
|
479
|
+
"request body rejected by parse hook",
|
|
476
480
|
true, HTTP_STATUS.BAD_REQUEST
|
|
477
481
|
);
|
|
478
482
|
}
|
|
@@ -504,7 +508,7 @@ async function _parseUrlencoded(req, opts) {
|
|
|
504
508
|
for (var i = 0; i < keys.length; i++) {
|
|
505
509
|
var k = keys[i][0];
|
|
506
510
|
var v = keys[i][1];
|
|
507
|
-
if (
|
|
511
|
+
if (pick.isPoisonedKey(k)) {
|
|
508
512
|
throw new BodyParserError(
|
|
509
513
|
"body-parser/urlencoded-poisoned-key",
|
|
510
514
|
"urlencoded body contains forbidden key '" + k + "' (prototype-pollution defense)",
|
|
@@ -628,10 +632,10 @@ function _parseMultipartHeaders(rawHeaders) {
|
|
|
628
632
|
true, HTTP_STATUS.BAD_REQUEST
|
|
629
633
|
);
|
|
630
634
|
}
|
|
631
|
-
var
|
|
632
|
-
if (
|
|
633
|
-
var k =
|
|
634
|
-
var v =
|
|
635
|
+
var khv = structuredFields.parseKeyValuePiece(line, ":");
|
|
636
|
+
if (khv.value === null) continue;
|
|
637
|
+
var k = khv.key;
|
|
638
|
+
var v = khv.value.trim();
|
|
635
639
|
for (var j = 0; j < v.length; j++) {
|
|
636
640
|
var c = v.charCodeAt(j);
|
|
637
641
|
if (c === 0 || c === 10 || c === 13) { // NUL/LF/CR forbidden in field-value (RFC 9110 §5.5)
|
|
@@ -720,12 +724,8 @@ function _parseHeaderParams(headerValue, filenameCharsets) {
|
|
|
720
724
|
// it last so Object.fromEntries' last-wins resolves it.
|
|
721
725
|
var paramPairs = [["_value", parts[0].trim().toLowerCase()]];
|
|
722
726
|
var extName = null;
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
var eq = p.indexOf("=");
|
|
726
|
-
if (eq === -1) continue;
|
|
727
|
-
var k = p.slice(0, eq).trim().toLowerCase();
|
|
728
|
-
var v = p.slice(eq + 1).trim();
|
|
727
|
+
var kvps = structuredFields.parseKeyValuePieces(parts, 1);
|
|
728
|
+
structuredFields.forEachKeyValue(kvps, function (k, v) {
|
|
729
729
|
var _unq = structuredFields.unquoteSfString(v);
|
|
730
730
|
if (_unq !== null) v = _unq;
|
|
731
731
|
if (k.charAt(k.length - 1) === "*") {
|
|
@@ -735,10 +735,10 @@ function _parseHeaderParams(headerValue, filenameCharsets) {
|
|
|
735
735
|
if (bareKey === "filename") extName = decoded;
|
|
736
736
|
paramPairs.push([bareKey, decoded]);
|
|
737
737
|
}
|
|
738
|
-
|
|
738
|
+
return;
|
|
739
739
|
}
|
|
740
740
|
paramPairs.push([k, v]);
|
|
741
|
-
}
|
|
741
|
+
});
|
|
742
742
|
if (extName !== null) paramPairs.push(["filename", extName]);
|
|
743
743
|
return _mapFromPairs(paramPairs);
|
|
744
744
|
}
|
|
@@ -936,7 +936,7 @@ async function _parseMultipart(req, opts, ctParams) {
|
|
|
936
936
|
// Read until \r\n\r\n.
|
|
937
937
|
var headEnd = pending.indexOf("\r\n\r\n");
|
|
938
938
|
if (headEnd === -1) {
|
|
939
|
-
if (pending
|
|
939
|
+
if (safeBuffer.byteLengthOf(pending) > C.BYTES.kib(16)) {
|
|
940
940
|
done(new BodyParserError("body-parser/multipart-headers-too-large",
|
|
941
941
|
"multipart part headers exceed 16KB", true, 413));
|
|
942
942
|
return;
|
|
@@ -970,7 +970,7 @@ async function _parseMultipart(req, opts, ctParams) {
|
|
|
970
970
|
"multipart part missing form-data Content-Disposition", true, HTTP_STATUS.BAD_REQUEST));
|
|
971
971
|
return;
|
|
972
972
|
}
|
|
973
|
-
if (
|
|
973
|
+
if (pick.isPoisonedKey(cd.name)) {
|
|
974
974
|
done(new BodyParserError("body-parser/multipart-poisoned-field",
|
|
975
975
|
"multipart field '" + cd.name + "' is forbidden (prototype-pollution defense)",
|
|
976
976
|
true, HTTP_STATUS.BAD_REQUEST));
|
|
@@ -1476,8 +1476,32 @@ function create(opts) {
|
|
|
1476
1476
|
}
|
|
1477
1477
|
var status = (e && typeof e.statusCode === "number") ? e.statusCode : HTTP_STATUS.BAD_REQUEST;
|
|
1478
1478
|
var code = (e && typeof e.code === "string") ? e.code : "body-parser/error";
|
|
1479
|
-
|
|
1480
|
-
|
|
1479
|
+
// Only a framework-classified 4xx BodyParserError carries a curated,
|
|
1480
|
+
// operator-safe message (malformed JSON, poisoned key, smuggling). Any
|
|
1481
|
+
// other thrown error — and every 5xx — gets a generic status phrase, so
|
|
1482
|
+
// an internal exception (fs errno + tmp path, a parse hook's thrown
|
|
1483
|
+
// secret) is never echoed to the client (CWE-209). The full diagnostic
|
|
1484
|
+
// stays on the audit chain, redacted by safeEmit.
|
|
1485
|
+
// Object(e) tolerates a non-object throw (throw null, or a string from
|
|
1486
|
+
// an operator parse hook) without a truthiness guard on the caught
|
|
1487
|
+
// binding — the caught value is always defined to CodeQL, so `e && …`
|
|
1488
|
+
// reads as a dead sub-condition; this keeps the null-safety explicit.
|
|
1489
|
+
var eo = Object(e);
|
|
1490
|
+
var clientMessage = (eo.isBodyParserError === true && status < 500 && typeof eo.message === "string")
|
|
1491
|
+
? eo.message
|
|
1492
|
+
: _genericReason(status);
|
|
1493
|
+
try {
|
|
1494
|
+
audit().safeEmit({
|
|
1495
|
+
action: "body-parser.error",
|
|
1496
|
+
outcome: status >= 500 ? "failure" : "denied",
|
|
1497
|
+
metadata: {
|
|
1498
|
+
status: status,
|
|
1499
|
+
code: code,
|
|
1500
|
+
message: (e && e.message) ? String(e.message).slice(0, 256) : "",
|
|
1501
|
+
},
|
|
1502
|
+
});
|
|
1503
|
+
} catch (_e) { /* audit best-effort — never mask the response */ }
|
|
1504
|
+
_writeError(res, status, clientMessage, code);
|
|
1481
1505
|
}
|
|
1482
1506
|
};
|
|
1483
1507
|
}
|
|
@@ -1501,9 +1525,11 @@ async function _parseJsonFromBuf(buf, opts) {
|
|
|
1501
1525
|
}
|
|
1502
1526
|
if (typeof opts.parseHook === "function") {
|
|
1503
1527
|
try { parsed = opts.parseHook(parsed); }
|
|
1504
|
-
catch (
|
|
1528
|
+
catch (_e) {
|
|
1529
|
+
// Operator parseHook threw — fixed safe message; the hook's thrown
|
|
1530
|
+
// detail (possible secrets) is never echoed to the client (CWE-209).
|
|
1505
1531
|
throw new BodyParserError("body-parser/json-hook",
|
|
1506
|
-
"
|
|
1532
|
+
"request body rejected by parse hook", true, HTTP_STATUS.BAD_REQUEST);
|
|
1507
1533
|
}
|
|
1508
1534
|
}
|
|
1509
1535
|
return parsed;
|
|
@@ -1612,12 +1638,9 @@ function _resolveStandaloneMultipartOpts(opts, ct) {
|
|
|
1612
1638
|
}
|
|
1613
1639
|
if (opts.maxFiles !== undefined) {
|
|
1614
1640
|
var mf = opts.maxFiles;
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
"parsers.multipart: opts.maxFiles must be a positive integer",
|
|
1619
|
-
true, HTTP_STATUS.BAD_REQUEST);
|
|
1620
|
-
}
|
|
1641
|
+
numericBounds.requirePositiveFiniteInt(mf,
|
|
1642
|
+
"parsers.multipart: opts.maxFiles", BodyParserError, "body-parser/bad-max-files",
|
|
1643
|
+
null, { permanent: true, statusCode: HTTP_STATUS.BAD_REQUEST });
|
|
1621
1644
|
resolved.fileCount = mf;
|
|
1622
1645
|
}
|
|
1623
1646
|
// Pass-through overrides for the multipart-specific knobs the middleware
|
|
@@ -1663,5 +1686,7 @@ module.exports = {
|
|
|
1663
1686
|
_contentType: _contentType,
|
|
1664
1687
|
_hasBody: _hasBody,
|
|
1665
1688
|
_sanitizeFilename: _sanitizeFilename,
|
|
1666
|
-
|
|
1689
|
+
// Sourced from the canonical pick primitive (no hand-rolled set); a fresh
|
|
1690
|
+
// Set so the long-standing `instanceof Set` shape of this export is kept.
|
|
1691
|
+
POISONED_KEYS: new Set(pick.POISONED_KEYS),
|
|
1667
1692
|
};
|
|
@@ -30,6 +30,7 @@ var defineClass = require("../framework-error").defineClass;
|
|
|
30
30
|
var lazyRequire = require("../lazy-require");
|
|
31
31
|
var validateOpts = require("../validate-opts");
|
|
32
32
|
var safeBuffer = require("../safe-buffer");
|
|
33
|
+
var requestHelpers = require("../request-helpers");
|
|
33
34
|
|
|
34
35
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
35
36
|
|
|
@@ -93,17 +94,13 @@ function create(opts) {
|
|
|
93
94
|
var actionBase = typeof opts.auditAction === "string" && opts.auditAction.length > 0
|
|
94
95
|
? opts.auditAction : "middleware.bot_disclose";
|
|
95
96
|
|
|
97
|
+
// null mountPaths = apply to every route; otherwise reuse the shared
|
|
98
|
+
// segment-boundary matcher (built once).
|
|
99
|
+
var _mountMatch = mountPaths
|
|
100
|
+
? requestHelpers.makeSkipMatcher({ skipPaths: mountPaths }, "middleware.botDisclose")
|
|
101
|
+
: null;
|
|
96
102
|
function _matches(req) {
|
|
97
|
-
|
|
98
|
-
var p = req.url || "";
|
|
99
|
-
var qpos = p.indexOf("?");
|
|
100
|
-
if (qpos !== -1) p = p.slice(0, qpos);
|
|
101
|
-
for (var i = 0; i < mountPaths.length; i++) {
|
|
102
|
-
var m = mountPaths[i];
|
|
103
|
-
if (typeof m === "string" && (p === m || p.indexOf(m + "/") === 0)) return true;
|
|
104
|
-
if (m instanceof RegExp && m.test(p)) return true;
|
|
105
|
-
}
|
|
106
|
-
return false;
|
|
103
|
+
return _mountMatch ? _mountMatch(req) : true;
|
|
107
104
|
}
|
|
108
105
|
|
|
109
106
|
function _emitAudit(action, outcome, metadata) {
|
|
@@ -144,21 +144,13 @@ function create(opts) {
|
|
|
144
144
|
var blockedAgents = DEFAULT_BLOCKED_AGENTS.concat((opts.blockedAgents || []).map(function (r, i) {
|
|
145
145
|
return _coerceAgentPattern(r, "middleware.botGuard: blockedAgents[" + i + "]");
|
|
146
146
|
}));
|
|
147
|
-
var skipPaths = opts.skipPaths || [];
|
|
148
147
|
var statusOnBlock = opts.statusOnBlock || 403;
|
|
149
148
|
var bodyOnBlock = opts.bodyOnBlock !== undefined ? opts.bodyOnBlock : "Forbidden";
|
|
150
149
|
var onDeny = typeof opts.onDeny === "function" ? opts.onDeny : null;
|
|
151
150
|
var problemMode = opts.problemDetails === true;
|
|
152
151
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
for (var i = 0; i < skipPaths.length; i++) {
|
|
156
|
-
if (typeof skipPaths[i] === "string" ? path.indexOf(skipPaths[i]) === 0 : skipPaths[i].test(path)) {
|
|
157
|
-
return true;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
152
|
+
// Path-exemption predicate (string-prefix or RegExp), validated at create().
|
|
153
|
+
var _shouldSkip = requestHelpers.makeSkipMatcher(opts, "middleware.botGuard");
|
|
162
154
|
|
|
163
155
|
function _looksLikeApi(req) {
|
|
164
156
|
var path = req.pathname || req.url || "/";
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
* }
|
|
68
68
|
*/
|
|
69
69
|
var lazyRequire = require("../lazy-require");
|
|
70
|
+
var pick = require("../pick");
|
|
70
71
|
var forms = require("../forms");
|
|
71
72
|
var requestHelpers = require("../request-helpers");
|
|
72
73
|
var validateOpts = require("../validate-opts");
|
|
@@ -111,7 +112,7 @@ function _parseCookieHeader(header) {
|
|
|
111
112
|
if (eq === -1) continue;
|
|
112
113
|
var k = p.slice(0, eq).trim();
|
|
113
114
|
if (k.length === 0) continue;
|
|
114
|
-
if (k
|
|
115
|
+
if (pick.isPoisonedKey(k)) continue;
|
|
115
116
|
if (seen.has(k)) continue; // first-occurrence wins
|
|
116
117
|
seen.add(k);
|
|
117
118
|
var v = p.slice(eq + 1).trim();
|
|
@@ -296,7 +297,7 @@ function create(opts) {
|
|
|
296
297
|
validateOpts(opts, [
|
|
297
298
|
"cookie", "tokenLookup", "fieldName", "headerName", "methods", "audit",
|
|
298
299
|
"trustProxy", "checkOrigin", "allowedOrigins", "requireJsonContentType",
|
|
299
|
-
"requireOrigin", "skipStateless", "onDeny", "problemDetails",
|
|
300
|
+
"requireOrigin", "skipStateless", "skipPaths", "skip", "onDeny", "problemDetails",
|
|
300
301
|
], "middleware.csrfProtect");
|
|
301
302
|
var onDeny = typeof opts.onDeny === "function" ? opts.onDeny : null;
|
|
302
303
|
var problemMode = opts.problemDetails === true;
|
|
@@ -368,6 +369,13 @@ function create(opts) {
|
|
|
368
369
|
// request always carries a Cookie header and is validated.
|
|
369
370
|
var skipStateless = opts.skipStateless === true;
|
|
370
371
|
|
|
372
|
+
// Per-path exemption (string-prefix / RegExp / skip predicate), validated at
|
|
373
|
+
// create(). Lets one route (an RFC 8058 List-Unsubscribe-Post endpoint, a
|
|
374
|
+
// webhook, a bearer-token API path on the same app) opt out of the
|
|
375
|
+
// double-submit check while the app-level mount stays in place — the token is
|
|
376
|
+
// still issued for any later browser flow.
|
|
377
|
+
var _shouldSkip = requestHelpers.makeSkipMatcher(opts, "middleware.csrfProtect");
|
|
378
|
+
|
|
371
379
|
// Cookie issuance config (only when opts.cookie is set).
|
|
372
380
|
var cookieCfg = null;
|
|
373
381
|
if (hasCookie) {
|
|
@@ -480,6 +488,9 @@ function create(opts) {
|
|
|
480
488
|
// (e.g. error response) still need req.csrfToken populated.
|
|
481
489
|
var expected = _issueIfNeeded(req, res);
|
|
482
490
|
|
|
491
|
+
// Exempt routes skip the double-submit check (token still issued above).
|
|
492
|
+
if (_shouldSkip(req)) return next();
|
|
493
|
+
|
|
483
494
|
if (methods.indexOf(req.method) === -1) return next();
|
|
484
495
|
|
|
485
496
|
// Stateless / token-authenticated requests are not CSRF-able — the
|
|
@@ -107,7 +107,6 @@ function create(opts) {
|
|
|
107
107
|
}
|
|
108
108
|
var bytesPerDay = opts.bytesPerDay;
|
|
109
109
|
var getKey = typeof opts.getKey === "function" ? opts.getKey : _defaultGetKey;
|
|
110
|
-
var auditOn = opts.audit !== false;
|
|
111
110
|
// onDeny is the canonical hook across the deny-path middleware
|
|
112
111
|
// family; onExceeded is the original name kept working as an alias.
|
|
113
112
|
var onDeny = typeof opts.onDeny === "function" ? opts.onDeny
|
|
@@ -127,33 +126,14 @@ function create(opts) {
|
|
|
127
126
|
});
|
|
128
127
|
var backend = quota._backend;
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (qpos !== -1) p = p.slice(0, qpos);
|
|
135
|
-
for (var i = 0; i < skipPaths.length; i++) {
|
|
136
|
-
if (typeof skipPaths[i] === "string" && p === skipPaths[i]) return true;
|
|
137
|
-
if (skipPaths[i] instanceof RegExp && skipPaths[i].test(p)) return true;
|
|
138
|
-
}
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
129
|
+
// exact:true preserves this guard's whole-path skip semantics (no descendant
|
|
130
|
+
// match); makeSkipMatcher strips the query + resolves url || originalUrl.
|
|
131
|
+
var _shouldSkip = requestHelpers().makeSkipMatcher(
|
|
132
|
+
{ skipPaths: skipPaths, exact: true }, "middleware.dailyByteQuota");
|
|
141
133
|
|
|
142
|
-
|
|
143
|
-
if (!auditOn) return;
|
|
144
|
-
try {
|
|
145
|
-
audit().safeEmit({
|
|
146
|
-
action: "middleware.daily_byte_quota." + action,
|
|
147
|
-
outcome: outcome,
|
|
148
|
-
metadata: metadata || {},
|
|
149
|
-
});
|
|
150
|
-
} catch (_e) { /* drop-silent — audit is best-effort */ }
|
|
151
|
-
}
|
|
134
|
+
var _emitAudit = audit().namespaced("middleware.daily_byte_quota", opts.audit);
|
|
152
135
|
|
|
153
|
-
|
|
154
|
-
try { observability().safeEvent("middleware.daily_byte_quota." + verb, n || 1, labels || {}); }
|
|
155
|
-
catch (_e) { /* drop-silent */ }
|
|
156
|
-
}
|
|
136
|
+
var _emitMetric = observability().namespaced("middleware.daily_byte_quota");
|
|
157
137
|
|
|
158
138
|
return async function dailyByteQuotaMiddleware(req, res, next) {
|
|
159
139
|
if (_shouldSkip(req)) return next();
|
|
@@ -147,6 +147,24 @@ function denyResponse(req, res, ctx) {
|
|
|
147
147
|
return undefined;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// methodNotAllowed(res, allow) — write the bare HTTP 405 refusal the
|
|
151
|
+
// single-file static-content middlewares (assetlinks / security.txt / web-app-
|
|
152
|
+
// manifest) share: an `Allow` header plus a `text/plain` "Method Not Allowed"
|
|
153
|
+
// body with an explicit Content-Length. Kept separate from denyResponse() (the
|
|
154
|
+
// hook-aware, problem+json-capable refusal path) so the static handlers emit a
|
|
155
|
+
// fixed, minimal response — `allow` is the comma-joined method list (e.g.
|
|
156
|
+
// "GET, HEAD").
|
|
157
|
+
function methodNotAllowed(res, allow) {
|
|
158
|
+
var bodyMsg = "Method Not Allowed";
|
|
159
|
+
res.writeHead(405, { // HTTP 405 status
|
|
160
|
+
"Allow": allow,
|
|
161
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
162
|
+
"Content-Length": Buffer.byteLength(bodyMsg),
|
|
163
|
+
});
|
|
164
|
+
res.end(bodyMsg);
|
|
165
|
+
}
|
|
166
|
+
|
|
150
167
|
module.exports = {
|
|
151
|
-
denyResponse:
|
|
168
|
+
denyResponse: denyResponse,
|
|
169
|
+
methodNotAllowed: methodNotAllowed,
|
|
152
170
|
};
|
|
@@ -165,6 +165,7 @@ function create(opts) {
|
|
|
165
165
|
"allowSameSite", "allowCrossSite", "allowMissing",
|
|
166
166
|
"allowedDest", "deniedDest", "allowStorageAccess", "strictDest",
|
|
167
167
|
"allowedNavigate", "methods", "audit", "onDeny", "problemDetails",
|
|
168
|
+
"skipPaths", "skip",
|
|
168
169
|
], "middleware.fetchMetadata");
|
|
169
170
|
validateOpts.optionalBoolean(opts.allowStorageAccess, "middleware.fetchMetadata: allowStorageAccess");
|
|
170
171
|
validateOpts.optionalBoolean(opts.strictDest, "middleware.fetchMetadata: strictDest");
|
|
@@ -174,6 +175,11 @@ function create(opts) {
|
|
|
174
175
|
_validateDestList(opts.deniedDest, "deniedDest");
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
// Per-path exemption (string-prefix / RegExp / skip predicate), validated at
|
|
179
|
+
// create() — exempt a webhook / cookieless edge-cached route from the
|
|
180
|
+
// fetch-metadata gate without disabling the app-level mount.
|
|
181
|
+
var _shouldSkip = requestHelpers.makeSkipMatcher(opts, "middleware.fetchMetadata");
|
|
182
|
+
|
|
177
183
|
var onDeny = typeof opts.onDeny === "function" ? opts.onDeny : null;
|
|
178
184
|
var problemMode = opts.problemDetails === true;
|
|
179
185
|
var allowSameSite = opts.allowSameSite !== false;
|
|
@@ -220,6 +226,7 @@ function create(opts) {
|
|
|
220
226
|
// Idempotent: a second fetch-metadata mount this request is a no-op.
|
|
221
227
|
if (req._fetchMetadataChecked) return next();
|
|
222
228
|
req._fetchMetadataChecked = true;
|
|
229
|
+
if (_shouldSkip(req)) return next();
|
|
223
230
|
if (methods.indexOf(req.method) === -1) return next();
|
|
224
231
|
|
|
225
232
|
var headers = req.headers || {};
|
|
@@ -53,7 +53,7 @@ var cryptoField = require("../crypto-field");
|
|
|
53
53
|
var vault = require("../vault");
|
|
54
54
|
var { defineClass } = require("../framework-error");
|
|
55
55
|
|
|
56
|
-
var
|
|
56
|
+
var auditEmit = require("../audit-emit");
|
|
57
57
|
var problemDetails = lazyRequire(function () { return require("../problem-details"); });
|
|
58
58
|
var C = require("../constants");
|
|
59
59
|
|
|
@@ -533,16 +533,8 @@ function dbStore(opts) {
|
|
|
533
533
|
}
|
|
534
534
|
|
|
535
535
|
function _validateStore(store, where) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
where + ": store must be an object", true);
|
|
539
|
-
}
|
|
540
|
-
if (typeof store.get !== "function" ||
|
|
541
|
-
typeof store.set !== "function" ||
|
|
542
|
-
typeof store.delete !== "function") {
|
|
543
|
-
throw new IdempotencyError("idempotency/bad-store",
|
|
544
|
-
where + ": store must implement { get, set, delete }", true);
|
|
545
|
-
}
|
|
536
|
+
validateOpts.requireMethods(store, ["get", "set", "delete"],
|
|
537
|
+
where + ": store", IdempotencyError, "idempotency/bad-store", true);
|
|
546
538
|
}
|
|
547
539
|
|
|
548
540
|
function _fingerprintRequest(req, bodyBytes, store) {
|
|
@@ -568,15 +560,7 @@ function _fingerprintRequest(req, bodyBytes, store) {
|
|
|
568
560
|
return nodeCrypto.createHash("sha3-256").update(preimage).digest("hex");
|
|
569
561
|
}
|
|
570
562
|
|
|
571
|
-
|
|
572
|
-
try {
|
|
573
|
-
audit().safeEmit({
|
|
574
|
-
action: action,
|
|
575
|
-
outcome: outcome || "success",
|
|
576
|
-
metadata: metadata,
|
|
577
|
-
});
|
|
578
|
-
} catch (_e) { /* best-effort */ }
|
|
579
|
-
}
|
|
563
|
+
var _emitAudit = auditEmit.emit;
|
|
580
564
|
|
|
581
565
|
/**
|
|
582
566
|
* @primitive b.middleware.idempotencyKey
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
*
|
|
57
57
|
* Audit: every limit hit emits system.ratelimit.block with the key + path.
|
|
58
58
|
*/
|
|
59
|
+
var boundedMap = require("../bounded-map");
|
|
59
60
|
var C = require("../constants");
|
|
60
61
|
var frameworkSchema = require("../framework-schema");
|
|
61
62
|
var lazyRequire = require("../lazy-require");
|
|
@@ -178,11 +179,14 @@ function _memoryTokenBucketBackend(opts) {
|
|
|
178
179
|
// microtask cost per request.
|
|
179
180
|
function take(key, _cost) {
|
|
180
181
|
var now = Date.now();
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
// Insert a fresh full bucket on first sight; only an already-present
|
|
183
|
+
// bucket refills (a brand-new bucket starts at `burst`, so refilling
|
|
184
|
+
// it would be a no-op clamped to `burst` anyway).
|
|
185
|
+
var existed = buckets.has(key);
|
|
186
|
+
var b = boundedMap.getOrInsert(buckets, key, function () {
|
|
187
|
+
return { tokens: burst, lastRefillAt: now };
|
|
188
|
+
});
|
|
189
|
+
if (existed) {
|
|
186
190
|
var elapsed = (now - b.lastRefillAt) / C.TIME.seconds(1);
|
|
187
191
|
b.tokens = Math.min(burst, b.tokens + elapsed * refillPerSecond);
|
|
188
192
|
b.lastRefillAt = now;
|
|
@@ -254,10 +258,15 @@ function _memoryFixedWindowBackend(opts) {
|
|
|
254
258
|
function take(key, _cost) {
|
|
255
259
|
var now = Date.now();
|
|
256
260
|
var windowStart = Math.floor(now / windowMs) * windowMs;
|
|
257
|
-
var c =
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
+
var c = boundedMap.getOrInsert(counters, key, function () {
|
|
262
|
+
return { windowStart: windowStart, count: 0 };
|
|
263
|
+
});
|
|
264
|
+
// A key carried over from a prior window re-seeds to the current
|
|
265
|
+
// window (count restarts) — getOrInsert only handles first-sight, so
|
|
266
|
+
// the rollover case resets the existing record in place.
|
|
267
|
+
if (c.windowStart !== windowStart) {
|
|
268
|
+
c.windowStart = windowStart;
|
|
269
|
+
c.count = 0;
|
|
261
270
|
}
|
|
262
271
|
c.count += 1;
|
|
263
272
|
if (c.count <= max) {
|
|
@@ -489,29 +498,12 @@ function create(opts) {
|
|
|
489
498
|
? opts.headerPrefix : "X-RateLimit-";
|
|
490
499
|
var limitHeader = headerPrefix + "Limit"; // allow:hand-rolled-sql — HTTP response-header name (X-RateLimit-Limit), not a SQL LIMIT clause
|
|
491
500
|
var remainingHeader = headerPrefix + "Remaining";
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
// Anything else would crash _shouldSkip with TypeError on the first request.
|
|
495
|
-
for (var sp = 0; sp < skipPaths.length; sp++) {
|
|
496
|
-
if (typeof skipPaths[sp] !== "string" && !(skipPaths[sp] instanceof RegExp)) {
|
|
497
|
-
throw new Error("middleware.rateLimit: skipPaths[" + sp +
|
|
498
|
-
"] must be a string prefix or RegExp, got " + typeof skipPaths[sp]);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
+
// Path-exemption predicate (string-prefix or RegExp), validated at create().
|
|
502
|
+
var _shouldSkip = requestHelpers.makeSkipMatcher(opts, "middleware.rateLimit");
|
|
501
503
|
var scope = opts.scope || "global";
|
|
502
504
|
|
|
503
505
|
var backend = _resolveBackend(opts);
|
|
504
506
|
|
|
505
|
-
function _shouldSkip(req) {
|
|
506
|
-
var path = req.pathname || req.url || "/";
|
|
507
|
-
for (var i = 0; i < skipPaths.length; i++) {
|
|
508
|
-
if (typeof skipPaths[i] === "string" ? path.indexOf(skipPaths[i]) === 0 : skipPaths[i].test(path)) {
|
|
509
|
-
return true;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
return false;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
507
|
function _writeBlocked(req, res, k, verdict) {
|
|
516
508
|
if (emitHeaders && typeof res.setHeader === "function") {
|
|
517
509
|
res.setHeader(limitHeader, String(verdict.limit));
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// + /Schemas surfaces backed by operator-supplied CRUD callbacks.
|
|
5
5
|
|
|
6
6
|
var framework_error = require("../framework-error");
|
|
7
|
+
var pick = require("../pick");
|
|
7
8
|
var validateOpts = require("../validate-opts");
|
|
8
9
|
var safeJson = require("../safe-json");
|
|
9
10
|
var safeBuffer = require("../safe-buffer");
|
|
@@ -516,7 +517,7 @@ function _walkBulkIdRefs(value, out) {
|
|
|
516
517
|
if (value && typeof value === "object") {
|
|
517
518
|
var keys = Object.keys(value);
|
|
518
519
|
for (var k = 0; k < keys.length; k++) {
|
|
519
|
-
if (keys[k]
|
|
520
|
+
if (pick.isPoisonedKey(keys[k])) continue;
|
|
520
521
|
_walkBulkIdRefs(value[keys[k]], out);
|
|
521
522
|
}
|
|
522
523
|
}
|
|
@@ -753,7 +754,7 @@ function _resolveBulkIdRefs(value, bulkIdMap) {
|
|
|
753
754
|
var out = {};
|
|
754
755
|
var keys = Object.keys(value);
|
|
755
756
|
for (var k = 0; k < keys.length; k++) {
|
|
756
|
-
if (keys[k]
|
|
757
|
+
if (pick.isPoisonedKey(keys[k])) continue;
|
|
757
758
|
out[keys[k]] = _resolveBulkIdRefs(value[keys[k]], bulkIdMap);
|
|
758
759
|
}
|
|
759
760
|
return out;
|
|
@@ -151,7 +151,15 @@ var PP_POLICY_RE =
|
|
|
151
151
|
/^[a-z][a-z0-9-]*=(?:\*|\([^)]*\)|self)$/;
|
|
152
152
|
function _validatePermissionsPolicy(value) {
|
|
153
153
|
if (typeof value !== "string" || value.length === 0) return;
|
|
154
|
-
|
|
154
|
+
// Split on the literal comma, then strip whitespace adjacent to each comma —
|
|
155
|
+
// the semantics of /\s*,\s*/ without that pattern's O(n^2) backtracking on a
|
|
156
|
+
// long comma-less run of whitespace. trimStart/trimEnd use the same Unicode
|
|
157
|
+
// whitespace set as \s.
|
|
158
|
+
var parts = String(value).split(",");
|
|
159
|
+
for (var s = 0; s < parts.length; s += 1) {
|
|
160
|
+
if (s > 0) parts[s] = parts[s].trimStart();
|
|
161
|
+
if (s < parts.length - 1) parts[s] = parts[s].trimEnd();
|
|
162
|
+
}
|
|
155
163
|
for (var i = 0; i < parts.length; i += 1) {
|
|
156
164
|
var p = parts[i];
|
|
157
165
|
if (!p) continue;
|