@blamejs/core 0.6.69 → 0.7.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 +11 -1
- package/index.js +2 -5
- package/lib/api-key.js +24 -55
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +20 -16
- package/lib/app.js +6 -5
- package/lib/archive.js +58 -50
- package/lib/atomic-file.js +11 -5
- package/lib/audit-chain.js +12 -4
- package/lib/audit-sign.js +38 -13
- package/lib/audit-tools.js +4 -4
- package/lib/audit.js +8 -4
- package/lib/auth/jwt.js +3 -2
- package/lib/auth/lockout.js +3 -13
- package/lib/auth/oauth.js +27 -17
- package/lib/auth/passkey.js +1 -5
- package/lib/auth/password.js +23 -13
- package/lib/backup/bundle.js +2 -4
- package/lib/backup/crypto.js +8 -6
- package/lib/backup/index.js +18 -14
- package/lib/backup/manifest.js +6 -2
- package/lib/break-glass.js +12 -7
- package/lib/bundler.js +32 -21
- package/lib/cache-redis.js +8 -3
- package/lib/cache.js +39 -59
- package/lib/canonical-json.js +17 -2
- package/lib/chain-writer.js +2 -2
- package/lib/cli-helpers.js +9 -4
- package/lib/cli.js +27 -5
- package/lib/cluster-provider-db.js +2 -1
- package/lib/cluster-storage.js +71 -11
- package/lib/cluster.js +30 -25
- package/lib/config-drift.js +3 -4
- package/lib/constants.js +8 -3
- package/lib/cookies.js +16 -4
- package/lib/credential-hash.js +4 -7
- package/lib/crypto.js +9 -9
- package/lib/csv.js +11 -8
- package/lib/db-declare-row-policy.js +4 -9
- package/lib/db-declare-view.js +3 -6
- package/lib/db-query.js +9 -3
- package/lib/db-schema.js +75 -10
- package/lib/db.js +116 -70
- package/lib/deprecate.js +3 -8
- package/lib/dev.js +30 -19
- package/lib/dual-control.js +2 -4
- package/lib/error-page.js +32 -39
- package/lib/external-db-migrate.js +27 -20
- package/lib/external-db.js +11 -3
- package/lib/file-type.js +16 -8
- package/lib/forms.js +48 -12
- package/lib/framework-error.js +1 -2
- package/lib/{internal-sha1-hibp.js → framework-sha1-hibp.js} +1 -1
- package/lib/handlers.js +1 -1
- package/lib/http-client-cookie-jar.js +25 -18
- package/lib/http-client.js +46 -23
- package/lib/i18n.js +6 -16
- package/lib/jobs.js +5 -1
- package/lib/log-stream-cloudwatch.js +31 -30
- package/lib/log-stream-local.js +12 -3
- package/lib/log-stream-otlp-grpc.js +29 -23
- package/lib/log-stream-otlp.js +13 -19
- package/lib/log-stream-syslog.js +27 -22
- package/lib/log-stream-webhook.js +8 -17
- package/lib/log-stream.js +13 -2
- package/lib/log.js +83 -5
- package/lib/mail-bounce.js +22 -15
- package/lib/mail-dkim.js +3 -6
- package/lib/mail.js +62 -34
- package/lib/metrics.js +61 -25
- package/lib/middleware/api-encrypt.js +15 -13
- package/lib/middleware/body-parser.js +46 -39
- package/lib/middleware/bot-guard.js +25 -4
- package/lib/middleware/compression.js +26 -11
- package/lib/middleware/cors.js +6 -5
- package/lib/middleware/csp-nonce.js +17 -4
- package/lib/middleware/csrf-protect.js +1 -1
- package/lib/middleware/db-role-for.js +7 -30
- package/lib/middleware/health.js +42 -8
- package/lib/middleware/index.js +55 -36
- package/lib/middleware/rate-limit.js +11 -8
- package/lib/middleware/request-id.js +10 -2
- package/lib/middleware/request-log.js +3 -2
- package/lib/middleware/require-auth.js +5 -2
- package/lib/middleware/sse.js +5 -4
- package/lib/migration-files.js +37 -0
- package/lib/migrations.js +42 -17
- package/lib/mtls-ca.js +38 -17
- package/lib/mtls-engine-default.js +15 -7
- package/lib/network-dns.js +49 -20
- package/lib/network-heartbeat.js +7 -7
- package/lib/network-nts.js +70 -48
- package/lib/network-proxy.js +34 -15
- package/lib/network-tls.js +7 -5
- package/lib/network.js +3 -3
- package/lib/notify.js +14 -36
- package/lib/ntp-check.js +7 -6
- package/lib/numeric-bounds.js +35 -3
- package/lib/object-store/azure-blob-bucket-ops.js +73 -15
- package/lib/object-store/azure-blob.js +31 -24
- package/lib/object-store/gcs-bucket-ops.js +41 -17
- package/lib/object-store/gcs.js +41 -34
- package/lib/object-store/http-put.js +2 -11
- package/lib/object-store/http-request.js +38 -0
- package/lib/object-store/index.js +1 -1
- package/lib/object-store/sigv4-bucket-ops.js +33 -37
- package/lib/object-store/sigv4.js +50 -30
- package/lib/observability.js +14 -3
- package/lib/otel-export.js +16 -14
- package/lib/pagination.js +23 -8
- package/lib/parsers/index.js +11 -5
- package/lib/parsers/safe-env.js +43 -14
- package/lib/parsers/safe-ini.js +29 -10
- package/lib/parsers/safe-toml.js +55 -25
- package/lib/parsers/safe-xml.js +22 -11
- package/lib/parsers/safe-yaml.js +55 -17
- package/lib/permissions.js +24 -33
- package/lib/pqc-agent.js +4 -2
- package/lib/pqc-gate.js +28 -15
- package/lib/protobuf-encoder.js +9 -3
- package/lib/protocol-dispatcher.js +29 -12
- package/lib/pubsub-redis.js +25 -8
- package/lib/pubsub.js +84 -12
- package/lib/queue-local.js +13 -4
- package/lib/queue-redis.js +2 -2
- package/lib/queue-sqs.js +21 -16
- package/lib/queue.js +8 -4
- package/lib/redis-client.js +13 -10
- package/lib/request-helpers.js +33 -2
- package/lib/restore-bundle.js +2 -4
- package/lib/restore-rollback.js +4 -1
- package/lib/restore.js +12 -10
- package/lib/retry.js +58 -9
- package/lib/router.js +96 -27
- package/lib/safe-async.js +97 -4
- package/lib/safe-buffer.js +47 -0
- package/lib/safe-json.js +18 -9
- package/lib/safe-schema.js +54 -18
- package/lib/safe-url.js +6 -2
- package/lib/scheduler.js +25 -14
- package/lib/security-assert.js +31 -26
- package/lib/seeders.js +71 -60
- package/lib/session.js +13 -7
- package/lib/slug.js +35 -8
- package/lib/ssrf-guard.js +58 -43
- package/lib/static.js +11 -7
- package/lib/storage.js +2 -1
- package/lib/template.js +17 -7
- package/lib/testing.js +28 -8
- package/lib/time.js +26 -5
- package/lib/totp.js +18 -9
- package/lib/tracing.js +15 -5
- package/lib/uuid.js +47 -20
- package/lib/validate-opts.js +213 -0
- package/lib/vault/index.js +36 -27
- package/lib/vault/rotate.js +41 -22
- package/lib/vault/wrap.js +26 -19
- package/lib/webhook.js +37 -68
- package/lib/websocket.js +60 -46
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
- package/lib/auth/totp.js +0 -17
- package/lib/object-store/retry.js +0 -15
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
*/
|
|
19
19
|
var { Readable } = require("stream");
|
|
20
20
|
var { ObjectStoreError } = require("../framework-error");
|
|
21
|
-
var httpClient = require("../http-client");
|
|
22
21
|
var safeUrl = require("../safe-url");
|
|
22
|
+
var sharedRequest = require("./http-request");
|
|
23
23
|
var authHeader = require("../auth-header");
|
|
24
24
|
|
|
25
25
|
var _err = ObjectStoreError.factory;
|
|
@@ -36,16 +36,7 @@ function _authHeaders(authConfig) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function _request(method, url, body, headers, opts) {
|
|
39
|
-
return
|
|
40
|
-
method: method,
|
|
41
|
-
url: url,
|
|
42
|
-
headers: headers,
|
|
43
|
-
body: body,
|
|
44
|
-
idleTimeoutMs: opts && opts.timeoutMs,
|
|
45
|
-
errorClass: ObjectStoreError,
|
|
46
|
-
allowedProtocols: opts && opts.allowedProtocols,
|
|
47
|
-
...((opts && opts.allowInternal !== undefined) ? { allowInternal: opts.allowInternal } : {}),
|
|
48
|
-
});
|
|
39
|
+
return sharedRequest(method, url, headers, body, opts);
|
|
49
40
|
}
|
|
50
41
|
|
|
51
42
|
function _keyToUrl(baseUrl, key) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared HTTP-request helper for object-store backends.
|
|
4
|
+
*
|
|
5
|
+
* Every protocol backend (azure-blob / gcs / sigv4 / http-put)
|
|
6
|
+
* previously rolled an identical wrapper around httpClient.request that
|
|
7
|
+
* threaded the same five opts: idleTimeoutMs / maxResponseBytes /
|
|
8
|
+
* errorClass / allowedProtocols / allowInternal. Centralized here so a
|
|
9
|
+
* future change to the call shape (new opt, different error class
|
|
10
|
+
* default, etc.) is a one-file change.
|
|
11
|
+
*
|
|
12
|
+
* var sharedRequest = require("./http-request");
|
|
13
|
+
* sharedRequest("PUT", url, headers, body, { timeoutMs: 5000 });
|
|
14
|
+
*
|
|
15
|
+
* The errorClass defaults to ObjectStoreError; backends that need a
|
|
16
|
+
* different class pass `errorClass: SomeError` in opts.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
var httpClient = require("../http-client");
|
|
20
|
+
var { ObjectStoreError } = require("../framework-error");
|
|
21
|
+
|
|
22
|
+
function request(method, url, headers, body, opts) {
|
|
23
|
+
opts = opts || {};
|
|
24
|
+
var req = {
|
|
25
|
+
method: method,
|
|
26
|
+
url: url,
|
|
27
|
+
headers: headers,
|
|
28
|
+
body: body,
|
|
29
|
+
idleTimeoutMs: opts.timeoutMs,
|
|
30
|
+
errorClass: opts.errorClass || ObjectStoreError,
|
|
31
|
+
allowedProtocols: opts.allowedProtocols,
|
|
32
|
+
};
|
|
33
|
+
if (opts.maxResponseBytes !== undefined) req.maxResponseBytes = opts.maxResponseBytes;
|
|
34
|
+
if (opts.allowInternal !== undefined) req.allowInternal = opts.allowInternal;
|
|
35
|
+
return httpClient.request(req);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = request;
|
|
@@ -35,7 +35,7 @@ var gcsProto = require("./gcs");
|
|
|
35
35
|
var gcsBucketOps = require("./gcs-bucket-ops");
|
|
36
36
|
var azureBlobProto = require("./azure-blob");
|
|
37
37
|
var azureBlobBucketOps = require("./azure-blob-bucket-ops");
|
|
38
|
-
var retryHelper = require("
|
|
38
|
+
var retryHelper = require("../retry");
|
|
39
39
|
var protocolDispatcher = require("../protocol-dispatcher");
|
|
40
40
|
var { ObjectStoreError } = require("../framework-error");
|
|
41
41
|
|
|
@@ -49,20 +49,31 @@
|
|
|
49
49
|
* with codes (BUCKET_INVALID_NAME, INVALID_LIFECYCLE, INVALID_CORS_RULE,
|
|
50
50
|
* BUCKET_ALREADY_OWNED, BUCKET_NOT_EMPTY, etc.).
|
|
51
51
|
*/
|
|
52
|
-
var { URL } = require("url");
|
|
53
52
|
var nodeCrypto = require("crypto");
|
|
54
53
|
var C = require("../constants");
|
|
55
54
|
var requestHelpers = require("../request-helpers");
|
|
56
55
|
var sigv4 = require("./sigv4");
|
|
56
|
+
var sharedRequest = require("./http-request");
|
|
57
57
|
var safeXml = require("../parsers/safe-xml");
|
|
58
58
|
var safeUrl = require("../safe-url");
|
|
59
59
|
var template = require("../template");
|
|
60
|
-
var httpClient = require("../http-client");
|
|
61
60
|
var validateOpts = require("../validate-opts");
|
|
62
61
|
var { ObjectStoreError } = require("../framework-error");
|
|
63
62
|
|
|
64
63
|
var _err = ObjectStoreError.factory;
|
|
65
64
|
|
|
65
|
+
// Internal URL builder — endpoint + path string from validated config.
|
|
66
|
+
// Routes through safeUrl.parse so the protocol allowlist + length cap
|
|
67
|
+
// apply uniformly. Cap is generous since presigned-style URLs with many
|
|
68
|
+
// query params can approach 8 KB.
|
|
69
|
+
function _internalUrl(input, allowedProtocols) {
|
|
70
|
+
return safeUrl.parse(input, {
|
|
71
|
+
allowedProtocols: allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
|
|
72
|
+
errorClass: ObjectStoreError,
|
|
73
|
+
maxUrlLength: C.BYTES.kib(32),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
// S3 bucket-name rules (general purpose). Source: AWS docs
|
|
67
78
|
// "Bucket naming rules". Lowercase letters, digits, hyphens; 3..63
|
|
68
79
|
// chars; no consecutive dots; cannot end in -s3alias / -ol-s3 etc.
|
|
@@ -76,7 +87,9 @@ function _validateBucketName(name) {
|
|
|
76
87
|
"bucket name must be a string of length 3..63, got " +
|
|
77
88
|
(typeof name === "string" ? "length " + name.length : typeof name), true);
|
|
78
89
|
}
|
|
79
|
-
|
|
90
|
+
// Length is bounded above (3..63) before the regex runs so the
|
|
91
|
+
// pattern can't be driven against an unbounded input.
|
|
92
|
+
if (name.length > 63 || !BUCKET_NAME_RE.test(name)) {
|
|
80
93
|
throw _err("BUCKET_INVALID_NAME",
|
|
81
94
|
"bucket name '" + name + "' violates S3 naming rules " +
|
|
82
95
|
"(lowercase, digits, hyphens, dots — no leading/trailing punct)", true);
|
|
@@ -116,9 +129,11 @@ function _buildLifecycleXml(rules) {
|
|
|
116
129
|
throw _err("INVALID_LIFECYCLE",
|
|
117
130
|
"setLifecycle: rules must be a non-empty array", true);
|
|
118
131
|
}
|
|
119
|
-
|
|
132
|
+
// S3 spec hard cap on lifecycle rules per bucket.
|
|
133
|
+
var MAX_LIFECYCLE_RULES = 1000;
|
|
134
|
+
if (rules.length > MAX_LIFECYCLE_RULES) {
|
|
120
135
|
throw _err("INVALID_LIFECYCLE",
|
|
121
|
-
"setLifecycle: maximum
|
|
136
|
+
"setLifecycle: maximum " + MAX_LIFECYCLE_RULES + " rules per bucket (S3 spec)", true);
|
|
122
137
|
}
|
|
123
138
|
var body = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
124
139
|
body += '<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">';
|
|
@@ -388,7 +403,7 @@ function _validateObjectKey(key) {
|
|
|
388
403
|
if (typeof key !== "string" || key.length === 0) {
|
|
389
404
|
throw _err("INVALID_KEY", "object key must be a non-empty string", true);
|
|
390
405
|
}
|
|
391
|
-
if (key.length >
|
|
406
|
+
if (key.length > C.BYTES.kib(1)) {
|
|
392
407
|
throw _err("INVALID_KEY", "object key exceeds 1024 bytes (S3 limit)", true);
|
|
393
408
|
}
|
|
394
409
|
}
|
|
@@ -442,17 +457,10 @@ function create(config) {
|
|
|
442
457
|
var auditSuccess = config.auditSuccess !== false;
|
|
443
458
|
var auditFailures = config.auditFailures !== false;
|
|
444
459
|
|
|
445
|
-
|
|
446
|
-
if (!audit) return;
|
|
447
|
-
try {
|
|
448
|
-
audit.safeEmit(Object.assign({ action: action }, info));
|
|
449
|
-
} catch (_e) { /* audit best-effort — never break the call */ }
|
|
450
|
-
}
|
|
460
|
+
var _emit = validateOpts.makeAuditEmitter(audit);
|
|
451
461
|
|
|
452
462
|
function _emitEvent(name, value, labels) {
|
|
453
|
-
if (
|
|
454
|
-
try { observability.event(name, value, labels || {}); }
|
|
455
|
-
catch (_e) { /* observability best-effort */ }
|
|
463
|
+
if (observability) observability.safeEvent(name, value, labels || {});
|
|
456
464
|
}
|
|
457
465
|
|
|
458
466
|
function _actor(callerOpts) {
|
|
@@ -483,17 +491,15 @@ function create(config) {
|
|
|
483
491
|
}
|
|
484
492
|
|
|
485
493
|
function _bucketUrl(name, query) {
|
|
486
|
-
var
|
|
494
|
+
var ub = _internalUrl(endpoint, allowedProtocols);
|
|
487
495
|
if (pathStyle) {
|
|
488
|
-
|
|
496
|
+
ub.pathname = "/" + name + "/";
|
|
489
497
|
} else {
|
|
490
|
-
var ub = new URL(endpoint);
|
|
491
498
|
ub.hostname = name + "." + ub.hostname;
|
|
492
499
|
ub.pathname = "/";
|
|
493
|
-
base = ub.toString();
|
|
494
|
-
if (base.endsWith("/")) base = base.slice(0, -1) + "/";
|
|
495
500
|
}
|
|
496
|
-
|
|
501
|
+
var base = ub.toString();
|
|
502
|
+
return _internalUrl(_appendQuery(base, query), allowedProtocols);
|
|
497
503
|
}
|
|
498
504
|
|
|
499
505
|
function _objectUrl(name, key, query) {
|
|
@@ -501,21 +507,20 @@ function create(config) {
|
|
|
501
507
|
// separators in the key are preserved (S3 treats keys with slashes
|
|
502
508
|
// as flat names, not directories).
|
|
503
509
|
var encKey = key.split("/").map(encodeURIComponent).join("/");
|
|
504
|
-
var
|
|
510
|
+
var uo = _internalUrl(endpoint, allowedProtocols);
|
|
505
511
|
if (pathStyle) {
|
|
506
|
-
|
|
512
|
+
uo.pathname = "/" + name + "/" + encKey;
|
|
507
513
|
} else {
|
|
508
|
-
var uo = new URL(endpoint);
|
|
509
514
|
uo.hostname = name + "." + uo.hostname;
|
|
510
515
|
uo.pathname = "/" + encKey;
|
|
511
|
-
base = uo.toString();
|
|
512
516
|
}
|
|
513
|
-
|
|
517
|
+
var base = uo.toString();
|
|
518
|
+
return _internalUrl(_appendQuery(base, query), allowedProtocols);
|
|
514
519
|
}
|
|
515
520
|
|
|
516
521
|
function _serviceUrl(query) {
|
|
517
522
|
// ListBuckets — service-level, no bucket prefix.
|
|
518
|
-
var u =
|
|
523
|
+
var u = _internalUrl(endpoint, allowedProtocols);
|
|
519
524
|
u.pathname = "/";
|
|
520
525
|
if (query) {
|
|
521
526
|
Object.keys(query).forEach(function (k) { u.searchParams.set(k, query[k]); });
|
|
@@ -538,16 +543,7 @@ function create(config) {
|
|
|
538
543
|
}
|
|
539
544
|
|
|
540
545
|
function _request(method, url, headers, body) {
|
|
541
|
-
return
|
|
542
|
-
method: method,
|
|
543
|
-
url: url,
|
|
544
|
-
headers: headers,
|
|
545
|
-
body: body,
|
|
546
|
-
idleTimeoutMs: reqOpts.timeoutMs,
|
|
547
|
-
errorClass: ObjectStoreError,
|
|
548
|
-
allowedProtocols: reqOpts.allowedProtocols,
|
|
549
|
-
...((reqOpts.allowInternal !== undefined) ? { allowInternal: reqOpts.allowInternal } : {}),
|
|
550
|
-
});
|
|
546
|
+
return sharedRequest(method, url, headers, body, reqOpts);
|
|
551
547
|
}
|
|
552
548
|
|
|
553
549
|
// ---- create ----
|
|
@@ -27,17 +27,31 @@ var nodeCrypto = require("crypto");
|
|
|
27
27
|
var { URL } = require("url");
|
|
28
28
|
var { Readable } = require("stream");
|
|
29
29
|
var safeXml = require("../parsers/safe-xml");
|
|
30
|
+
var sharedRequest = require("./http-request");
|
|
30
31
|
var C = require("../constants");
|
|
32
|
+
var nb = require("../numeric-bounds");
|
|
31
33
|
var { ObjectStoreError } = require("../framework-error");
|
|
32
|
-
var httpClient = require("../http-client");
|
|
33
34
|
var safeUrl = require("../safe-url");
|
|
34
35
|
|
|
36
|
+
// Internal URL builder — endpoint + path string from validated config /
|
|
37
|
+
// framework-encoded keys. Routes through safeUrl.parse so the protocol
|
|
38
|
+
// allowlist + length cap apply uniformly. Presigned URLs with many
|
|
39
|
+
// query params can approach the 8 KB default; raise the cap to 32 KB
|
|
40
|
+
// so a worst-case multipart UploadPart URL still fits.
|
|
41
|
+
function _internalUrl(input, allowedProtocols) {
|
|
42
|
+
return safeUrl.parse(input, {
|
|
43
|
+
allowedProtocols: allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
|
|
44
|
+
errorClass: ObjectStoreError,
|
|
45
|
+
maxUrlLength: C.BYTES.kib(32),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
// S3 list responses are commonly 1000-key paginated payloads — well above
|
|
36
50
|
// the parser's default 1 MiB / 10K element ceilings. These overrides
|
|
37
51
|
// give headroom for normal responses without uncapping the parser.
|
|
38
52
|
var LIST_PARSE_OPTS = {
|
|
39
53
|
maxBytes: C.BYTES.mib(8),
|
|
40
|
-
maxElements: 50000,
|
|
54
|
+
maxElements: C.BYTES.bytes(50000),
|
|
41
55
|
};
|
|
42
56
|
|
|
43
57
|
function _arrayify(value) {
|
|
@@ -53,8 +67,8 @@ var ALGORITHM = "AWS4-HMAC-SHA256";
|
|
|
53
67
|
|
|
54
68
|
// Presigned-URL expiry bounds. The SigV4 spec caps query-string-signed
|
|
55
69
|
// URLs at 7 days; below 1 second is effectively unsignable.
|
|
56
|
-
var PRESIGN_DEFAULT_EXPIRES_SECONDS = 15
|
|
57
|
-
var PRESIGN_MAX_EXPIRES_SECONDS = 7
|
|
70
|
+
var PRESIGN_DEFAULT_EXPIRES_SECONDS = C.TIME.minutes(15) / C.TIME.seconds(1); // 15 minutes
|
|
71
|
+
var PRESIGN_MAX_EXPIRES_SECONDS = C.TIME.days(7) / C.TIME.seconds(1); // 7 days (SigV4 hard cap)
|
|
58
72
|
var PRESIGN_MIN_EXPIRES_SECONDS = 1;
|
|
59
73
|
|
|
60
74
|
var _err = ObjectStoreError.factory;
|
|
@@ -152,9 +166,11 @@ function deriveSigningKey(secretAccessKey, dateStamp, region, service) {
|
|
|
152
166
|
return hmacSha256(kService, "aws4_request");
|
|
153
167
|
}
|
|
154
168
|
|
|
169
|
+
// SigV4 ISO compact format: YYYYMMDDTHHMMSSZ — slice indices fixed
|
|
170
|
+
// by AWS spec.
|
|
155
171
|
function _formatAmzDate(d) {
|
|
156
172
|
var iso = d.toISOString().replace(/[-:]/g, "");
|
|
157
|
-
return iso.slice(0, 8) + "T" + iso.slice(9, 15) + "Z";
|
|
173
|
+
return iso.slice(0, C.BYTES.bytes(8)) + "T" + iso.slice(9, 15) + "Z";
|
|
158
174
|
}
|
|
159
175
|
function _formatDateStamp(d) {
|
|
160
176
|
return d.toISOString().slice(0, 10).replace(/-/g, "");
|
|
@@ -164,7 +180,12 @@ function signRequest(opts) {
|
|
|
164
180
|
var date = opts.date || new Date();
|
|
165
181
|
var amzDate = _formatAmzDate(date);
|
|
166
182
|
var dateStamp = _formatDateStamp(date);
|
|
167
|
-
|
|
183
|
+
// signRequest is called by callers signing requests to operator-
|
|
184
|
+
// configured endpoints (cloudwatch, sqs, sns, custom S3-API stores).
|
|
185
|
+
// Honor opts.allowedProtocols so a test-fixture http:// endpoint still
|
|
186
|
+
// signs correctly while production https:// stays the default.
|
|
187
|
+
var url = opts.url instanceof URL ? opts.url
|
|
188
|
+
: _internalUrl(opts.url, opts.allowedProtocols);
|
|
168
189
|
// service defaults to s3 for back-compat — every call site predating
|
|
169
190
|
// v0.6.25 was object-store / S3. Other AWS services (logs, sqs, sns,
|
|
170
191
|
// kinesis, etc.) pass opts.service explicitly. The credentialScope
|
|
@@ -200,19 +221,7 @@ function signRequest(opts) {
|
|
|
200
221
|
|
|
201
222
|
// ---- HTTP request helper ----
|
|
202
223
|
|
|
203
|
-
|
|
204
|
-
return httpClient.request({
|
|
205
|
-
method: method,
|
|
206
|
-
url: url,
|
|
207
|
-
headers: headers,
|
|
208
|
-
body: body,
|
|
209
|
-
idleTimeoutMs: opts && opts.timeoutMs,
|
|
210
|
-
maxResponseBytes: opts && opts.maxResponseBytes,
|
|
211
|
-
errorClass: ObjectStoreError,
|
|
212
|
-
allowedProtocols: opts && opts.allowedProtocols,
|
|
213
|
-
...((opts && opts.allowInternal !== undefined) ? { allowInternal: opts.allowInternal } : {}),
|
|
214
|
-
});
|
|
215
|
-
}
|
|
224
|
+
var _request = sharedRequest;
|
|
216
225
|
|
|
217
226
|
// ---- Multipart-upload constants ----
|
|
218
227
|
|
|
@@ -223,7 +232,7 @@ function _request(method, url, headers, body, opts) {
|
|
|
223
232
|
var MIN_PART_SIZE_BYTES = C.BYTES.mib(5);
|
|
224
233
|
// S3 spec ceiling on part count. CompleteMultipartUpload rejects
|
|
225
234
|
// uploads with more than 10000 parts.
|
|
226
|
-
var MAX_PARTS = 10000;
|
|
235
|
+
var MAX_PARTS = C.BYTES.bytes(10000);
|
|
227
236
|
// Auto-multipart trigger: buffered bodies under this stay single-PUT.
|
|
228
237
|
// Streams always go multipart since size isn't known up-front.
|
|
229
238
|
var DEFAULT_MULTIPART_THRESHOLD_BYTES = C.BYTES.mib(64);
|
|
@@ -395,9 +404,9 @@ function create(config) {
|
|
|
395
404
|
if (key.indexOf("\0") !== -1) throw _err("INVALID_KEY", "null byte in key", true);
|
|
396
405
|
var encoded = key.split("/").map(function (s) { return awsUriEncode(s, true); }).join("/");
|
|
397
406
|
if (pathStyle) {
|
|
398
|
-
return
|
|
407
|
+
return _internalUrl(endpoint + "/" + config.bucket + "/" + encoded, allowedProtocols);
|
|
399
408
|
}
|
|
400
|
-
var u =
|
|
409
|
+
var u = _internalUrl(endpoint, allowedProtocols);
|
|
401
410
|
u.hostname = config.bucket + "." + u.hostname;
|
|
402
411
|
u.pathname = "/" + encoded;
|
|
403
412
|
return u;
|
|
@@ -406,9 +415,9 @@ function create(config) {
|
|
|
406
415
|
function _bucketUrl(searchParams) {
|
|
407
416
|
var u;
|
|
408
417
|
if (pathStyle) {
|
|
409
|
-
u =
|
|
418
|
+
u = _internalUrl(endpoint + "/" + config.bucket + "/", allowedProtocols);
|
|
410
419
|
} else {
|
|
411
|
-
u =
|
|
420
|
+
u = _internalUrl(endpoint, allowedProtocols);
|
|
412
421
|
u.hostname = config.bucket + "." + u.hostname;
|
|
413
422
|
u.pathname = "/";
|
|
414
423
|
}
|
|
@@ -506,7 +515,7 @@ function create(config) {
|
|
|
506
515
|
// still presents `uploads=` per AWS spec) so the signature path is
|
|
507
516
|
// unchanged.
|
|
508
517
|
var url = _keyToUrl(key);
|
|
509
|
-
var initiateUrl =
|
|
518
|
+
var initiateUrl = _internalUrl(url.href + (url.search ? "&" : "?") + "uploads", allowedProtocols);
|
|
510
519
|
var initiateExtra = {
|
|
511
520
|
"Content-Type": contentType,
|
|
512
521
|
"Content-Length": "0",
|
|
@@ -530,7 +539,7 @@ function create(config) {
|
|
|
530
539
|
// 2. Upload parts (concurrency-bounded)
|
|
531
540
|
uploadedEtags = await _bounded(parts, partConcurrency, async function (partBuf, idx) {
|
|
532
541
|
var partNumber = idx + 1;
|
|
533
|
-
var partUrl =
|
|
542
|
+
var partUrl = _internalUrl(url.href, allowedProtocols);
|
|
534
543
|
partUrl.searchParams.set("partNumber", String(partNumber));
|
|
535
544
|
partUrl.searchParams.set("uploadId", uploadId);
|
|
536
545
|
var partHeaders = _makeSigned("PUT", partUrl, sha256Hex(partBuf), {
|
|
@@ -546,7 +555,7 @@ function create(config) {
|
|
|
546
555
|
});
|
|
547
556
|
|
|
548
557
|
// 3. Complete
|
|
549
|
-
var completeUrl =
|
|
558
|
+
var completeUrl = _internalUrl(url.href, allowedProtocols);
|
|
550
559
|
completeUrl.searchParams.set("uploadId", uploadId);
|
|
551
560
|
var completeBody = Buffer.from(_buildCompleteMultipartXml(uploadedEtags), "utf8");
|
|
552
561
|
var completeHeaders = _makeSigned("POST", completeUrl, sha256Hex(completeBody), {
|
|
@@ -575,7 +584,7 @@ function create(config) {
|
|
|
575
584
|
// Failures here are silently swallowed — the caller's original
|
|
576
585
|
// error is what they need to see, not a secondary cleanup error.
|
|
577
586
|
try {
|
|
578
|
-
var abortUrl =
|
|
587
|
+
var abortUrl = _internalUrl(url.href, allowedProtocols);
|
|
579
588
|
abortUrl.searchParams.set("uploadId", uploadId);
|
|
580
589
|
var abortHeaders = _makeSigned("DELETE", abortUrl, sha256Hex(Buffer.alloc(0)));
|
|
581
590
|
await _request("DELETE", abortUrl, abortHeaders, null, reqOpts);
|
|
@@ -648,7 +657,11 @@ function create(config) {
|
|
|
648
657
|
// hint into real server-side enforcement.
|
|
649
658
|
var headers = { host: url.host };
|
|
650
659
|
if (opts.contentType) headers["content-type"] = opts.contentType;
|
|
651
|
-
|
|
660
|
+
// SigV4 SignedHeaders: lowercase, semicolon-joined, AWS-spec order.
|
|
661
|
+
var signedHeaderKeys = [];
|
|
662
|
+
for (var hk in headers) signedHeaderKeys.push(hk);
|
|
663
|
+
signedHeaderKeys.sort();
|
|
664
|
+
var signedHeadersStr = signedHeaderKeys.join(";");
|
|
652
665
|
|
|
653
666
|
// Query-string SigV4: presigning carries auth in the URL itself.
|
|
654
667
|
// The set order doesn't matter — canonicalQueryString sorts the
|
|
@@ -707,7 +720,12 @@ function create(config) {
|
|
|
707
720
|
"POST-form policy enforces body size via the content-length-range condition; " +
|
|
708
721
|
"use presignedUploadUrl if size enforcement is not needed", true);
|
|
709
722
|
}
|
|
710
|
-
|
|
723
|
+
if (opts.minBytes !== undefined && !nb.isNonNegativeFiniteInt(opts.minBytes)) {
|
|
724
|
+
throw _err("INVALID_MIN_BYTES",
|
|
725
|
+
"presignedUploadPolicy: minBytes must be a non-negative finite integer; got " +
|
|
726
|
+
nb.shape(opts.minBytes), true);
|
|
727
|
+
}
|
|
728
|
+
var minBytes = opts.minBytes !== undefined ? opts.minBytes : 0;
|
|
711
729
|
var expiresIn = opts.expiresIn != null ? opts.expiresIn : PRESIGN_DEFAULT_EXPIRES_SECONDS;
|
|
712
730
|
if (typeof expiresIn !== "number" ||
|
|
713
731
|
expiresIn < PRESIGN_MIN_EXPIRES_SECONDS ||
|
|
@@ -830,6 +848,8 @@ module.exports = {
|
|
|
830
848
|
canonicalHeaders: canonicalHeaders,
|
|
831
849
|
awsUriEncode: awsUriEncode,
|
|
832
850
|
sha256Hex: sha256Hex,
|
|
851
|
+
formatAmzDate: _formatAmzDate,
|
|
852
|
+
formatDateStamp: _formatDateStamp,
|
|
833
853
|
SERVICE: SERVICE,
|
|
834
854
|
ALGORITHM: ALGORITHM,
|
|
835
855
|
};
|
package/lib/observability.js
CHANGED
|
@@ -133,8 +133,19 @@ function event(name, value, labels) {
|
|
|
133
133
|
_safeMetricsTap(name, value, labels);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// safeEvent — wraps `event` in a try/catch so callers on hot paths
|
|
137
|
+
// (per-request observability emits) can't crash the request that
|
|
138
|
+
// triggered them when the metrics registry has a misconfigured
|
|
139
|
+
// counter or label name. Replaces the per-file `_emitEvent` helper
|
|
140
|
+
// that 7+ modules previously duplicated.
|
|
141
|
+
function safeEvent(name, value, labels) {
|
|
142
|
+
try { event(name, value, labels); }
|
|
143
|
+
catch (_e) { /* hot-path observability sink — drops silent on internal throws */ }
|
|
144
|
+
}
|
|
145
|
+
|
|
136
146
|
module.exports = {
|
|
137
|
-
tap:
|
|
138
|
-
event:
|
|
139
|
-
|
|
147
|
+
tap: tap,
|
|
148
|
+
event: event,
|
|
149
|
+
safeEvent: safeEvent,
|
|
150
|
+
setTap: setTap,
|
|
140
151
|
};
|
package/lib/otel-export.js
CHANGED
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
* hand-rolled wire format with no compelling benefit at this scope.
|
|
41
41
|
*/
|
|
42
42
|
var C = require("./constants");
|
|
43
|
+
var canonicalJson = require("./canonical-json");
|
|
44
|
+
var defaultHttpClient = require("./http-client");
|
|
43
45
|
var safeAsync = require("./safe-async");
|
|
44
46
|
var validateOpts = require("./validate-opts");
|
|
45
47
|
var { defineClass } = require("./framework-error");
|
|
@@ -77,13 +79,19 @@ function _attrsToOtlp(attrs) {
|
|
|
77
79
|
return out;
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
// Stable key per (name, attrs) so tap calls aggregate.
|
|
82
|
+
// Stable key per (name, attrs) so tap calls aggregate. Routes the
|
|
83
|
+
// sort through canonical-json so the framework has one canonical-sort
|
|
84
|
+
// source of truth for hash-input-shaped keys (the OTLP attribute
|
|
85
|
+
// encoding _attrsToOtlp emits the wire-format separately).
|
|
81
86
|
function _bucketKey(name, attrs) {
|
|
82
87
|
if (!attrs) return name + "|";
|
|
83
|
-
|
|
84
|
-
var
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
// Coerce to strings so canonical-json doesn't reject odd value types.
|
|
89
|
+
var coerced = {};
|
|
90
|
+
var rawKeys = Object.keys(attrs);
|
|
91
|
+
for (var i = 0; i < rawKeys.length; i++) {
|
|
92
|
+
coerced[rawKeys[i]] = String(attrs[rawKeys[i]]);
|
|
93
|
+
}
|
|
94
|
+
return name + "|" + canonicalJson.stringify(coerced);
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
function create(opts) {
|
|
@@ -92,14 +100,8 @@ function create(opts) {
|
|
|
92
100
|
"endpoint", "headers", "serviceName", "intervalMs",
|
|
93
101
|
"httpClient", "resourceAttributes", "scope",
|
|
94
102
|
], "otelExport.create");
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"create: endpoint must be a non-empty URL");
|
|
98
|
-
}
|
|
99
|
-
if (typeof opts.serviceName !== "string" || opts.serviceName.length === 0) {
|
|
100
|
-
throw new OtelExportError("otel-export/bad-service-name",
|
|
101
|
-
"create: serviceName must be a non-empty string");
|
|
102
|
-
}
|
|
103
|
+
validateOpts.requireNonEmptyString(opts.endpoint, "create: endpoint", OtelExportError, "otel-export/bad-endpoint");
|
|
104
|
+
validateOpts.requireNonEmptyString(opts.serviceName, "create: serviceName", OtelExportError, "otel-export/bad-service-name");
|
|
103
105
|
var endpoint = opts.endpoint;
|
|
104
106
|
var serviceName = opts.serviceName;
|
|
105
107
|
var headers = opts.headers || {};
|
|
@@ -108,7 +110,7 @@ function create(opts) {
|
|
|
108
110
|
throw new OtelExportError("otel-export/bad-interval",
|
|
109
111
|
"create: intervalMs must be a non-negative finite number");
|
|
110
112
|
}
|
|
111
|
-
var httpClient = opts.httpClient ||
|
|
113
|
+
var httpClient = opts.httpClient || defaultHttpClient;
|
|
112
114
|
var scopeName = (opts.scope && opts.scope.name) || "blamejs";
|
|
113
115
|
var scopeVersion = (opts.scope && opts.scope.version) || "0.5.x";
|
|
114
116
|
var resourceAttrs = Object.assign({ "service.name": serviceName },
|
package/lib/pagination.js
CHANGED
|
@@ -96,16 +96,21 @@
|
|
|
96
96
|
*/
|
|
97
97
|
|
|
98
98
|
var nodeCrypto = require("node:crypto");
|
|
99
|
+
var C = require("./constants");
|
|
99
100
|
var canonicalJson = require("./canonical-json");
|
|
100
101
|
var crypto = require("./crypto");
|
|
102
|
+
var nb = require("./numeric-bounds");
|
|
103
|
+
var safeJson = require("./safe-json");
|
|
104
|
+
var safeSql = require("./safe-sql");
|
|
101
105
|
var { defineClass } = require("./framework-error");
|
|
102
106
|
|
|
103
107
|
var PaginationError = defineClass("PaginationError", { alwaysPermanent: true });
|
|
104
108
|
|
|
105
109
|
var CURSOR_VERSION = 1;
|
|
106
|
-
var TAG_BYTES = 16;
|
|
110
|
+
var TAG_BYTES = C.BYTES.bytes(16); // 128-bit HMAC tag truncated from SHA3-512
|
|
107
111
|
var DEFAULT_LIMIT = 25;
|
|
108
112
|
var DEFAULT_MAX_LIMIT = 100;
|
|
113
|
+
var MAX_CURSOR_BYTES = C.BYTES.kib(8);
|
|
109
114
|
|
|
110
115
|
// Canonical JSON via the shared lib/canonical-json walker with
|
|
111
116
|
// bufferAs: "reject" — cursor state must contain only plain data, so
|
|
@@ -185,7 +190,7 @@ function decodeCursor(token, secret) {
|
|
|
185
190
|
"cursor HMAC verification failed (tampered or wrong secret)");
|
|
186
191
|
}
|
|
187
192
|
var state;
|
|
188
|
-
try { state =
|
|
193
|
+
try { state = safeJson.parse(json, { maxBytes: MAX_CURSOR_BYTES }); }
|
|
189
194
|
catch (_e) {
|
|
190
195
|
throw new PaginationError("pagination/bad-cursor", "cursor state JSON malformed");
|
|
191
196
|
}
|
|
@@ -200,8 +205,12 @@ function decodeCursor(token, secret) {
|
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
function _resolveLimit(opts) {
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
nb.requirePositiveFiniteIntIfPresent(opts.max,
|
|
209
|
+
"max", PaginationError, "pagination/bad-opt");
|
|
210
|
+
nb.requirePositiveFiniteIntIfPresent(opts.default,
|
|
211
|
+
"default", PaginationError, "pagination/bad-opt");
|
|
212
|
+
var max = opts.max || DEFAULT_MAX_LIMIT;
|
|
213
|
+
var def = opts.default || DEFAULT_LIMIT;
|
|
205
214
|
var requested = parseInt(opts.limit, 10);
|
|
206
215
|
if (isNaN(requested) || requested < 1) requested = def;
|
|
207
216
|
if (requested > max) requested = max;
|
|
@@ -247,9 +256,13 @@ function _normalizeOrderBy(opts) {
|
|
|
247
256
|
"orderBy must be a string, array, or omitted; got " + typeof raw);
|
|
248
257
|
}
|
|
249
258
|
for (var i = 0; i < entries.length; i++) {
|
|
250
|
-
if (
|
|
259
|
+
if (typeof entries[i].column !== "string" ||
|
|
260
|
+
entries[i].column.length === 0 ||
|
|
261
|
+
entries[i].column.length > safeSql.MAX_IDENTIFIER_LENGTH ||
|
|
262
|
+
!safeSql.DEFAULT_IDENTIFIER_RE.test(entries[i].column)) {
|
|
251
263
|
throw new PaginationError("pagination/bad-orderby",
|
|
252
|
-
"orderBy column must match
|
|
264
|
+
"orderBy column must match " + safeSql.DEFAULT_IDENTIFIER_RE +
|
|
265
|
+
" and be 1.." + safeSql.MAX_IDENTIFIER_LENGTH + " chars, got " +
|
|
253
266
|
JSON.stringify(entries[i].column));
|
|
254
267
|
}
|
|
255
268
|
}
|
|
@@ -405,9 +418,11 @@ async function offset(query, opts) {
|
|
|
405
418
|
var orderBy = typeof opts.orderBy === "string" && opts.orderBy.length > 0 ? opts.orderBy : "_id";
|
|
406
419
|
// Same identifier-only check on offset() as cursor() — orderBy passes
|
|
407
420
|
// through to the db Query; throw at call site to prevent SQL injection.
|
|
408
|
-
if (
|
|
421
|
+
if (orderBy.length > safeSql.MAX_IDENTIFIER_LENGTH ||
|
|
422
|
+
!safeSql.DEFAULT_IDENTIFIER_RE.test(orderBy)) {
|
|
409
423
|
throw new PaginationError("pagination/bad-orderby",
|
|
410
|
-
"offset: orderBy must match
|
|
424
|
+
"offset: orderBy must match " + safeSql.DEFAULT_IDENTIFIER_RE +
|
|
425
|
+
" and be 1.." + safeSql.MAX_IDENTIFIER_LENGTH + " chars, got " +
|
|
411
426
|
JSON.stringify(orderBy));
|
|
412
427
|
}
|
|
413
428
|
var direction = (opts.direction === "desc") ? "desc" : "asc";
|
package/lib/parsers/index.js
CHANGED
|
@@ -65,10 +65,16 @@
|
|
|
65
65
|
* Error types: each parser exports its own *SafeError class with .code
|
|
66
66
|
* matching the format (xml/..., toml/..., ini/..., yaml/..., env/...).
|
|
67
67
|
*/
|
|
68
|
+
var safeEnv = require("./safe-env");
|
|
69
|
+
var safeIni = require("./safe-ini");
|
|
70
|
+
var safeToml = require("./safe-toml");
|
|
71
|
+
var safeXml = require("./safe-xml");
|
|
72
|
+
var safeYaml = require("./safe-yaml");
|
|
73
|
+
|
|
68
74
|
module.exports = {
|
|
69
|
-
xml:
|
|
70
|
-
toml:
|
|
71
|
-
yaml:
|
|
72
|
-
env:
|
|
73
|
-
ini:
|
|
75
|
+
xml: safeXml,
|
|
76
|
+
toml: safeToml,
|
|
77
|
+
yaml: safeYaml,
|
|
78
|
+
env: safeEnv,
|
|
79
|
+
ini: safeIni,
|
|
74
80
|
};
|