@blamejs/core 0.6.70 → 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 +4 -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 +23 -26
- 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 +4 -3
- 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 +26 -15
- 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
|
@@ -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
|
};
|
package/lib/parsers/safe-env.js
CHANGED
|
@@ -56,13 +56,26 @@
|
|
|
56
56
|
|
|
57
57
|
var C = require("../constants");
|
|
58
58
|
var atomicFile = require("../atomic-file");
|
|
59
|
+
var lazyRequire = require("../lazy-require");
|
|
60
|
+
var numericBounds = require("../numeric-bounds");
|
|
59
61
|
var safeBuffer = require("../safe-buffer");
|
|
60
62
|
var safeJson = require("../safe-json");
|
|
61
63
|
var { FrameworkError } = require("../framework-error");
|
|
62
64
|
var { boot } = require("../log");
|
|
63
65
|
|
|
66
|
+
// Lazy require for audit — eager-loading audit at top of file forms a
|
|
67
|
+
// load cycle: vault.init → passphrase-source → safe-env → audit →
|
|
68
|
+
// (transitively) vault, which leaves safe-env's module.exports
|
|
69
|
+
// half-built when vault first reaches readVar. Defer audit resolution
|
|
70
|
+
// until the first emit-driven call.
|
|
71
|
+
var auditModule = lazyRequire(function () { return require("../audit"); });
|
|
72
|
+
|
|
64
73
|
var log = boot("env");
|
|
65
74
|
|
|
75
|
+
// parseInt radix — named so the call site doesn't carry a bare 16
|
|
76
|
+
// integer literal that reads as a byte count.
|
|
77
|
+
var RADIX_HEX = 0x10;
|
|
78
|
+
|
|
66
79
|
class SafeEnvError extends FrameworkError {
|
|
67
80
|
constructor(message, code, line) {
|
|
68
81
|
super(line != null ? message + " at line " + line : message);
|
|
@@ -75,7 +88,7 @@ class SafeEnvError extends FrameworkError {
|
|
|
75
88
|
|
|
76
89
|
var DEFAULTS = {
|
|
77
90
|
maxBytes: C.BYTES.kib(64),
|
|
78
|
-
maxKeys:
|
|
91
|
+
maxKeys: 1_000,
|
|
79
92
|
keyShape: /^[A-Z_][A-Z0-9_]*$/,
|
|
80
93
|
applyToProcess: false,
|
|
81
94
|
allowOverwrite: false,
|
|
@@ -89,10 +102,21 @@ var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
|
89
102
|
|
|
90
103
|
function parse(input, opts) {
|
|
91
104
|
opts = opts || {};
|
|
92
|
-
|
|
105
|
+
// Validate operator-supplied numeric opts via lib/numeric-bounds —
|
|
106
|
+
// Infinity / NaN / negative / non-integer all bypass the `> 0` shape
|
|
107
|
+
// and silently lift the DoS cap they were meant to enforce.
|
|
108
|
+
if (opts.maxBytes !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxBytes)) {
|
|
109
|
+
throw new SafeEnvError("env.parse: maxBytes must be a positive finite integer; got " +
|
|
110
|
+
numericBounds.shape(opts.maxBytes), "env/bad-opt");
|
|
111
|
+
}
|
|
112
|
+
if (opts.maxKeys !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxKeys)) {
|
|
113
|
+
throw new SafeEnvError("env.parse: maxKeys must be a positive finite integer; got " +
|
|
114
|
+
numericBounds.shape(opts.maxKeys), "env/bad-opt");
|
|
115
|
+
}
|
|
116
|
+
var maxBytes = opts.maxBytes !== undefined
|
|
93
117
|
? Math.min(opts.maxBytes, C.BYTES.mib(1)) : DEFAULTS.maxBytes;
|
|
94
|
-
var maxKeys =
|
|
95
|
-
? Math.min(opts.maxKeys,
|
|
118
|
+
var maxKeys = opts.maxKeys !== undefined
|
|
119
|
+
? Math.min(opts.maxKeys, 100_000) : DEFAULTS.maxKeys;
|
|
96
120
|
var keyShape = opts.keyShape instanceof RegExp ? opts.keyShape : DEFAULTS.keyShape;
|
|
97
121
|
|
|
98
122
|
input = safeBuffer.normalizeText(input, {
|
|
@@ -123,7 +147,7 @@ function parse(input, opts) {
|
|
|
123
147
|
if (eqIdx < 0) {
|
|
124
148
|
throw new SafeEnvError("missing '=' separator", "env/bad-line", lineNumber);
|
|
125
149
|
}
|
|
126
|
-
var key = trimmed.substring(0, eqIdx).replace(
|
|
150
|
+
var key = trimmed.substring(0, eqIdx).replace(safeBuffer.TRAILING_HSPACE_RE, "");
|
|
127
151
|
var rest = trimmed.substring(eqIdx + 1);
|
|
128
152
|
|
|
129
153
|
if (key.length === 0) {
|
|
@@ -163,9 +187,9 @@ function parse(input, opts) {
|
|
|
163
187
|
// (so a value like `KEY=color#red` keeps the literal `#`).
|
|
164
188
|
var commentMatch = rest.match(/^([^\s#]*(?:[ \t]+[^#\s]+)*)\s+#.*$/);
|
|
165
189
|
if (commentMatch) {
|
|
166
|
-
value = commentMatch[1].replace(
|
|
190
|
+
value = commentMatch[1].replace(safeBuffer.TRAILING_HSPACE_RE, "");
|
|
167
191
|
} else {
|
|
168
|
-
value = rest.replace(
|
|
192
|
+
value = rest.replace(safeBuffer.TRAILING_HSPACE_RE, "");
|
|
169
193
|
}
|
|
170
194
|
// Reject `$VAR` style references — explicit error so operators
|
|
171
195
|
// see the policy rather than silently getting unexpanded text.
|
|
@@ -209,10 +233,10 @@ function _decodeDoubleQuoted(rest, lineNumber) {
|
|
|
209
233
|
case "$": out += "$"; i += 2; continue; // literal $ escape
|
|
210
234
|
case "u": {
|
|
211
235
|
var hex = rest.substring(i + 2, i + 6);
|
|
212
|
-
if (
|
|
236
|
+
if (!safeBuffer.isHex(hex, 4)) {
|
|
213
237
|
throw new SafeEnvError("bad \\u escape", "env/bad-escape", lineNumber);
|
|
214
238
|
}
|
|
215
|
-
out += String.fromCharCode(parseInt(hex,
|
|
239
|
+
out += String.fromCharCode(parseInt(hex, RADIX_HEX));
|
|
216
240
|
i += 6;
|
|
217
241
|
continue;
|
|
218
242
|
}
|
|
@@ -493,11 +517,9 @@ function _hasNonEmptyDiff(d) {
|
|
|
493
517
|
function _writeAuditRows(filepath, diff) {
|
|
494
518
|
// Both audit.record and cluster.requireLeader sit downstream — if
|
|
495
519
|
// we're a follower, audit.record will throw NotLeaderError. Catch
|
|
496
|
-
//
|
|
520
|
+
// explicitly: a follower's local config-load shouldn't crash because
|
|
497
521
|
// the cluster's audit chain belongs to the leader.
|
|
498
|
-
var audit;
|
|
499
|
-
try { audit = require("./../audit"); }
|
|
500
|
-
catch (_e) { return; /* audit module not loadable in this context */ }
|
|
522
|
+
var audit = auditModule(); // resolve the lazy-required audit module
|
|
501
523
|
|
|
502
524
|
function _safeRecord(action, metadata) {
|
|
503
525
|
try {
|
|
@@ -559,7 +581,14 @@ function readVar(name, schema) {
|
|
|
559
581
|
var type = schema.type || "string";
|
|
560
582
|
var required = schema.required === true;
|
|
561
583
|
var hasDefault = "default" in schema;
|
|
562
|
-
|
|
584
|
+
if (schema.maxBytes !== undefined && !numericBounds.isPositiveFiniteInt(schema.maxBytes)) {
|
|
585
|
+
throw new SafeEnvError(
|
|
586
|
+
"readVar: maxBytes must be a positive finite integer; got " +
|
|
587
|
+
numericBounds.shape(schema.maxBytes),
|
|
588
|
+
"env/bad-opt"
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
var maxBytes = (schema.maxBytes !== undefined)
|
|
563
592
|
? schema.maxBytes : READVAR_DEFAULT_MAX_BYTES;
|
|
564
593
|
var strip = schema.strip === true;
|
|
565
594
|
|