@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
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
|
|
package/lib/parsers/safe-ini.js
CHANGED
|
@@ -31,18 +31,24 @@
|
|
|
31
31
|
* parsers.ini.parse(input, opts?) -> object
|
|
32
32
|
* parsers.ini.IniSafeError (with .code matching ini/...)
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
* surfaces
|
|
34
|
+
* Input validation throws at parse time — every malformed input
|
|
35
|
+
* surfaces immediately, never silently coerces to a usable shape.
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
+
var C = require("../constants");
|
|
39
|
+
var numericBounds = require("../numeric-bounds");
|
|
38
40
|
var { defineClass } = require("../framework-error");
|
|
39
41
|
|
|
40
42
|
var IniSafeError = defineClass("IniSafeError", { alwaysPermanent: true });
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
// parseInt radix — named so the call site doesn't carry a bare 16
|
|
45
|
+
// integer literal that reads as a byte count.
|
|
46
|
+
var RADIX_HEX = 0x10;
|
|
47
|
+
|
|
48
|
+
var DEFAULT_MAX_BYTES = C.BYTES.mib(1);
|
|
43
49
|
var DEFAULT_MAX_SECTIONS = 500;
|
|
44
|
-
var DEFAULT_MAX_KEYS_SECTION =
|
|
45
|
-
var DEFAULT_MAX_VALUE_BYTES = 64
|
|
50
|
+
var DEFAULT_MAX_KEYS_SECTION = 1_000;
|
|
51
|
+
var DEFAULT_MAX_VALUE_BYTES = C.BYTES.kib(64);
|
|
46
52
|
|
|
47
53
|
var FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
48
54
|
|
|
@@ -109,7 +115,7 @@ function _coerceValue(raw) {
|
|
|
109
115
|
if (TRUE_VALUES.has(lower)) return true;
|
|
110
116
|
if (FALSE_VALUES.has(lower)) return false;
|
|
111
117
|
if (/^0x[0-9a-f]+$/i.test(raw)) {
|
|
112
|
-
var hex = parseInt(raw,
|
|
118
|
+
var hex = parseInt(raw, RADIX_HEX);
|
|
113
119
|
if (!Number.isSafeInteger(hex)) {
|
|
114
120
|
throw _err("ini/value-out-of-range", "hex integer exceeds safe-integer range: " + raw);
|
|
115
121
|
}
|
|
@@ -180,10 +186,23 @@ function _parseSectionHeader(line) {
|
|
|
180
186
|
|
|
181
187
|
function parse(input, opts) {
|
|
182
188
|
opts = opts || {};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
// Validate operator-supplied numeric opts via lib/numeric-bounds —
|
|
190
|
+
// Infinity / NaN / negative / non-integer all bypass `|| DEFAULT_*`
|
|
191
|
+
// (Infinity is truthy) and silently lift the DoS cap they were meant
|
|
192
|
+
// to enforce.
|
|
193
|
+
var _numericOpts = ["maxBytes", "maxSections", "maxKeysPerSection", "maxValueBytes"];
|
|
194
|
+
for (var _ni = 0; _ni < _numericOpts.length; _ni++) {
|
|
195
|
+
var _name = _numericOpts[_ni];
|
|
196
|
+
if (opts[_name] !== undefined && !numericBounds.isPositiveFiniteInt(opts[_name])) {
|
|
197
|
+
throw _err("ini/bad-opt",
|
|
198
|
+
"ini.parse: " + _name + " must be a positive finite integer; got " +
|
|
199
|
+
numericBounds.shape(opts[_name]));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
var maxBytes = opts.maxBytes !== undefined ? opts.maxBytes : DEFAULT_MAX_BYTES;
|
|
203
|
+
var maxSections = opts.maxSections !== undefined ? opts.maxSections : DEFAULT_MAX_SECTIONS;
|
|
204
|
+
var maxKeysPerSect = opts.maxKeysPerSection !== undefined ? opts.maxKeysPerSection : DEFAULT_MAX_KEYS_SECTION;
|
|
205
|
+
var maxValueBytes = opts.maxValueBytes !== undefined ? opts.maxValueBytes : DEFAULT_MAX_VALUE_BYTES;
|
|
187
206
|
var onDuplicate = opts.onDuplicate || "throw";
|
|
188
207
|
|
|
189
208
|
if (typeof input !== "string") {
|
package/lib/parsers/safe-toml.js
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
var C = require("../constants");
|
|
48
|
+
var numericBounds = require("../numeric-bounds");
|
|
48
49
|
var safeBuffer = require("../safe-buffer");
|
|
49
50
|
var { FrameworkError } = require("../framework-error");
|
|
50
51
|
|
|
@@ -59,24 +60,49 @@ class SafeTomlError extends FrameworkError {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
// parseInt radices and TOML radix-prefix tokens — naming these constants
|
|
64
|
+
// keeps the literal `8` / `16` byte-shaped numbers off the call sites.
|
|
65
|
+
var RADIX_BIN = 0x2;
|
|
66
|
+
var RADIX_OCTAL = 0x8;
|
|
67
|
+
var RADIX_HEX = 0x10;
|
|
68
|
+
|
|
69
|
+
// Date-time literal character widths (per TOML / RFC 3339).
|
|
70
|
+
var TIME_CHARS = 0x8; // "HH:MM:SS"
|
|
71
|
+
var OFFSET_CHARS = 0x6; // "+HH:MM"
|
|
72
|
+
|
|
62
73
|
var DEFAULTS = {
|
|
63
74
|
maxBytes: C.BYTES.mib(1),
|
|
64
75
|
maxDepth: 100,
|
|
65
|
-
maxKeys:
|
|
76
|
+
maxKeys: 50_000,
|
|
66
77
|
};
|
|
67
78
|
|
|
68
79
|
var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
69
80
|
|
|
70
81
|
function parse(input, opts) {
|
|
71
82
|
opts = opts || {};
|
|
72
|
-
|
|
83
|
+
// Validate operator-supplied numeric opts via lib/numeric-bounds —
|
|
84
|
+
// Infinity / NaN / negative / non-integer all bypass the `> 0` shape
|
|
85
|
+
// and silently lift the DoS cap they were meant to enforce.
|
|
86
|
+
if (opts.maxBytes !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxBytes)) {
|
|
87
|
+
throw new SafeTomlError("toml.parse: maxBytes must be a positive finite integer; got " +
|
|
88
|
+
numericBounds.shape(opts.maxBytes), "toml/bad-opt");
|
|
89
|
+
}
|
|
90
|
+
if (opts.maxDepth !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxDepth)) {
|
|
91
|
+
throw new SafeTomlError("toml.parse: maxDepth must be a positive finite integer; got " +
|
|
92
|
+
numericBounds.shape(opts.maxDepth), "toml/bad-opt");
|
|
93
|
+
}
|
|
94
|
+
if (opts.maxKeys !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxKeys)) {
|
|
95
|
+
throw new SafeTomlError("toml.parse: maxKeys must be a positive finite integer; got " +
|
|
96
|
+
numericBounds.shape(opts.maxKeys), "toml/bad-opt");
|
|
97
|
+
}
|
|
98
|
+
var maxBytes = opts.maxBytes !== undefined
|
|
73
99
|
? Math.min(opts.maxBytes, C.BYTES.mib(64))
|
|
74
100
|
: DEFAULTS.maxBytes;
|
|
75
|
-
var maxDepth =
|
|
76
|
-
? Math.min(opts.maxDepth,
|
|
101
|
+
var maxDepth = opts.maxDepth !== undefined
|
|
102
|
+
? Math.min(opts.maxDepth, 1_000)
|
|
77
103
|
: DEFAULTS.maxDepth;
|
|
78
|
-
var maxKeys =
|
|
79
|
-
? Math.min(opts.maxKeys,
|
|
104
|
+
var maxKeys = opts.maxKeys !== undefined
|
|
105
|
+
? Math.min(opts.maxKeys, 1_000_000)
|
|
80
106
|
: DEFAULTS.maxKeys;
|
|
81
107
|
|
|
82
108
|
input = safeBuffer.normalizeText(input, {
|
|
@@ -237,19 +263,20 @@ function parse(input, opts) {
|
|
|
237
263
|
case "t": return "\t";
|
|
238
264
|
case "u": {
|
|
239
265
|
var hex = input.substring(pos, pos + 4);
|
|
240
|
-
if (hex
|
|
266
|
+
if (!safeBuffer.isHex(hex, 4)) {
|
|
241
267
|
throw _err("bad \\u escape", "toml/bad-escape");
|
|
242
268
|
}
|
|
243
269
|
_advance(4);
|
|
244
|
-
return String.fromCharCode(parseInt(hex,
|
|
270
|
+
return String.fromCharCode(parseInt(hex, RADIX_HEX));
|
|
245
271
|
}
|
|
246
272
|
case "U": {
|
|
247
|
-
var
|
|
248
|
-
|
|
273
|
+
var hexLen = 0x8;
|
|
274
|
+
var hex8 = input.substring(pos, pos + hexLen);
|
|
275
|
+
if (hex8.length < hexLen || !/^[0-9a-fA-F]{8}$/.test(hex8)) {
|
|
249
276
|
throw _err("bad \\U escape", "toml/bad-escape");
|
|
250
277
|
}
|
|
251
|
-
_advance(
|
|
252
|
-
var code = parseInt(hex8,
|
|
278
|
+
_advance(hexLen);
|
|
279
|
+
var code = parseInt(hex8, RADIX_HEX);
|
|
253
280
|
if (code > 0x10FFFF) throw _err("\\U code point > U+10FFFF", "toml/bad-escape");
|
|
254
281
|
return String.fromCodePoint(code);
|
|
255
282
|
}
|
|
@@ -367,11 +394,12 @@ function parse(input, opts) {
|
|
|
367
394
|
if (pos + 10 <= len && /^\d{4}-\d{2}-\d{2}/.test(input.substr(pos, 10))) {
|
|
368
395
|
var sep = _peek(10);
|
|
369
396
|
if (sep === "T" || sep === "t" || sep === " ") {
|
|
370
|
-
// Look ahead for time portion
|
|
397
|
+
// Look ahead for time portion (HH:MM:SS = 8 chars)
|
|
371
398
|
var timeStart = pos + 11;
|
|
372
|
-
|
|
399
|
+
var timeChars = TIME_CHARS;
|
|
400
|
+
if (/^\d{2}:\d{2}:\d{2}/.test(input.substr(timeStart, timeChars))) {
|
|
373
401
|
var datePart = input.substr(pos, 10);
|
|
374
|
-
var timeEnd = timeStart +
|
|
402
|
+
var timeEnd = timeStart + timeChars;
|
|
375
403
|
// Optional fractional
|
|
376
404
|
if (_peek(timeEnd - pos) === ".") {
|
|
377
405
|
timeEnd += 1;
|
|
@@ -383,11 +411,12 @@ function parse(input, opts) {
|
|
|
383
411
|
var nextChar = input.charAt(timeEnd);
|
|
384
412
|
if (nextChar === "Z" || nextChar === "z") { offsetStr = "Z"; timeEnd += 1; }
|
|
385
413
|
else if (nextChar === "+" || nextChar === "-") {
|
|
386
|
-
// Expect HH:MM
|
|
387
|
-
var
|
|
414
|
+
// Expect ±HH:MM (6 chars)
|
|
415
|
+
var offsetChars = OFFSET_CHARS;
|
|
416
|
+
var off = input.substr(timeEnd, offsetChars);
|
|
388
417
|
if (/^[+-]\d{2}:\d{2}$/.test(off)) {
|
|
389
418
|
offsetStr = off;
|
|
390
|
-
timeEnd +=
|
|
419
|
+
timeEnd += offsetChars;
|
|
391
420
|
}
|
|
392
421
|
}
|
|
393
422
|
_advance(timeEnd - pos);
|
|
@@ -409,9 +438,10 @@ function parse(input, opts) {
|
|
|
409
438
|
return { kind: "local-date", value: ds };
|
|
410
439
|
}
|
|
411
440
|
}
|
|
412
|
-
// Time-only: HH:MM:SS
|
|
413
|
-
|
|
414
|
-
|
|
441
|
+
// Time-only: HH:MM:SS (8 chars)
|
|
442
|
+
var timeOnlyChars = TIME_CHARS;
|
|
443
|
+
if (pos + timeOnlyChars <= len && /^\d{2}:\d{2}:\d{2}/.test(input.substr(pos, timeOnlyChars))) {
|
|
444
|
+
var teEnd = pos + timeOnlyChars;
|
|
415
445
|
if (input.charAt(teEnd) === ".") {
|
|
416
446
|
teEnd += 1;
|
|
417
447
|
while (teEnd < len && /\d/.test(input.charAt(teEnd))) teEnd += 1;
|
|
@@ -448,14 +478,14 @@ function parse(input, opts) {
|
|
|
448
478
|
if (prefix === "x" || prefix === "o" || prefix === "b") {
|
|
449
479
|
if (sign !== 1) throw _err("sign not allowed on hex/oct/bin literal", "toml/bad-number");
|
|
450
480
|
_advance(2);
|
|
451
|
-
var radix = prefix === "x" ?
|
|
481
|
+
var radix = prefix === "x" ? RADIX_HEX : prefix === "o" ? RADIX_OCTAL : RADIX_BIN;
|
|
452
482
|
var digitsStart = pos;
|
|
453
483
|
while (!_eof()) {
|
|
454
484
|
var ch = _peek();
|
|
455
485
|
if (ch === "_") { _advance(); continue; }
|
|
456
|
-
if (radix ===
|
|
457
|
-
if (radix ===
|
|
458
|
-
if (radix ===
|
|
486
|
+
if (radix === RADIX_HEX && /[0-9a-fA-F]/.test(ch)) { _advance(); continue; }
|
|
487
|
+
if (radix === RADIX_OCTAL && /[0-7]/.test(ch)) { _advance(); continue; }
|
|
488
|
+
if (radix === RADIX_BIN && /[01]/.test(ch)) { _advance(); continue; }
|
|
459
489
|
break;
|
|
460
490
|
}
|
|
461
491
|
var digits = input.substring(digitsStart, pos).replace(/_/g, "");
|
package/lib/parsers/safe-xml.js
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
42
|
var C = require("../constants");
|
|
43
|
+
var nb = require("../numeric-bounds");
|
|
43
44
|
var safeBuffer = require("../safe-buffer");
|
|
44
45
|
var { FrameworkError } = require("../framework-error");
|
|
45
46
|
|
|
@@ -53,34 +54,44 @@ class SafeXmlError extends FrameworkError {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
// parseInt radix — named so the call site doesn't carry a bare 16
|
|
58
|
+
// integer literal that reads as a byte count.
|
|
59
|
+
var RADIX_HEX = 0x10;
|
|
60
|
+
|
|
56
61
|
var DEFAULTS = {
|
|
57
62
|
maxBytes: C.BYTES.mib(1),
|
|
58
63
|
maxDepth: 100,
|
|
59
|
-
maxElements:
|
|
64
|
+
maxElements: 10_000,
|
|
60
65
|
maxAttributes: 100,
|
|
61
66
|
allowDoctype: false,
|
|
62
67
|
allowProcessing: false, // <?...?> other than the xml decl
|
|
63
68
|
};
|
|
64
69
|
|
|
65
70
|
var ABSOLUTE_MAX_BYTES = C.BYTES.mib(64);
|
|
66
|
-
var ABSOLUTE_MAX_DEPTH =
|
|
67
|
-
var ABSOLUTE_MAX_ELEMENTS =
|
|
71
|
+
var ABSOLUTE_MAX_DEPTH = 1_000;
|
|
72
|
+
var ABSOLUTE_MAX_ELEMENTS = 1_000_000;
|
|
73
|
+
var ABSOLUTE_MAX_ATTRIBUTES = 1_000;
|
|
68
74
|
|
|
69
75
|
// XML built-in entities (the ONLY entities allowed)
|
|
70
76
|
var BUILT_IN_ENTITIES = { lt: "<", gt: ">", amp: "&", quot: "\"", apos: "'" };
|
|
71
77
|
|
|
72
|
-
function
|
|
73
|
-
if (
|
|
74
|
-
|
|
78
|
+
function _validateAndCap(name, value, defaultValue, ceiling) {
|
|
79
|
+
if (value === undefined) return defaultValue;
|
|
80
|
+
if (!nb.isPositiveFiniteInt(value)) {
|
|
81
|
+
throw new SafeXmlError("xml/bad-opt",
|
|
82
|
+
"xml.parse: " + name + " must be a positive finite integer; got " +
|
|
83
|
+
nb.shape(value));
|
|
84
|
+
}
|
|
85
|
+
return Math.min(value, ceiling);
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
function parse(input, opts) {
|
|
78
89
|
opts = opts || {};
|
|
79
90
|
|
|
80
|
-
var maxBytes =
|
|
81
|
-
var maxDepth =
|
|
82
|
-
var maxElements =
|
|
83
|
-
var maxAttrs =
|
|
91
|
+
var maxBytes = _validateAndCap("maxBytes", opts.maxBytes, DEFAULTS.maxBytes, ABSOLUTE_MAX_BYTES);
|
|
92
|
+
var maxDepth = _validateAndCap("maxDepth", opts.maxDepth, DEFAULTS.maxDepth, ABSOLUTE_MAX_DEPTH);
|
|
93
|
+
var maxElements = _validateAndCap("maxElements", opts.maxElements, DEFAULTS.maxElements, ABSOLUTE_MAX_ELEMENTS);
|
|
94
|
+
var maxAttrs = _validateAndCap("maxAttributes", opts.maxAttributes, DEFAULTS.maxAttributes, ABSOLUTE_MAX_ATTRIBUTES);
|
|
84
95
|
var allowDoctype = !!opts.allowDoctype;
|
|
85
96
|
var allowProcessing = !!opts.allowProcessing;
|
|
86
97
|
|
|
@@ -130,7 +141,7 @@ function parse(input, opts) {
|
|
|
130
141
|
// Numeric character reference
|
|
131
142
|
var code;
|
|
132
143
|
if (name.charAt(1) === "x" || name.charAt(1) === "X") {
|
|
133
|
-
code = parseInt(name.substring(2),
|
|
144
|
+
code = parseInt(name.substring(2), RADIX_HEX);
|
|
134
145
|
} else {
|
|
135
146
|
code = parseInt(name.substring(1), 10);
|
|
136
147
|
}
|
package/lib/parsers/safe-yaml.js
CHANGED
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
*/
|
|
54
54
|
|
|
55
55
|
var C = require("../constants");
|
|
56
|
+
var numericBounds = require("../numeric-bounds");
|
|
56
57
|
var safeBuffer = require("../safe-buffer");
|
|
57
58
|
var { FrameworkError } = require("../framework-error");
|
|
58
59
|
|
|
@@ -67,10 +68,21 @@ class SafeYamlError extends FrameworkError {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
// parseInt radices, named so the line itself doesn't carry a bare 8 / 16
|
|
72
|
+
// integer literal that reads as a byte count.
|
|
73
|
+
var RADIX_OCTAL = 0x8;
|
|
74
|
+
var RADIX_HEX = 0x10;
|
|
75
|
+
|
|
76
|
+
// Defensive cap on the size of a single scalar token before regex
|
|
77
|
+
// classification. The parser already bounds the whole input via maxBytes;
|
|
78
|
+
// this cap keeps a pathological single-line scalar from feeding the type-
|
|
79
|
+
// inference regexes an arbitrarily long string.
|
|
80
|
+
var MAX_SCALAR_BYTES = C.BYTES.kib(64);
|
|
81
|
+
|
|
70
82
|
var DEFAULTS = {
|
|
71
83
|
maxBytes: C.BYTES.mib(1),
|
|
72
84
|
maxDepth: 100,
|
|
73
|
-
maxKeys:
|
|
85
|
+
maxKeys: 50_000,
|
|
74
86
|
};
|
|
75
87
|
|
|
76
88
|
var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -88,25 +100,36 @@ var FLOAT_INF = /^[-+]?\.(inf|Inf|INF)$/;
|
|
|
88
100
|
var FLOAT_NAN = /^\.(nan|NaN|NAN)$/;
|
|
89
101
|
|
|
90
102
|
function _resolveScalar(s) {
|
|
103
|
+
// Fall back to string for any token whose length exceeds the scalar cap
|
|
104
|
+
// before running type-inference regexes. The parser caller has already
|
|
105
|
+
// applied the input-wide maxBytes cap; this is a per-token guard so the
|
|
106
|
+
// type-inference regexes never see a pathologically long string.
|
|
107
|
+
if (typeof s !== "string" || s.length > MAX_SCALAR_BYTES) return s;
|
|
108
|
+
// Below: every regex test sees an `s` whose s.length <= MAX_SCALAR_BYTES.
|
|
91
109
|
if (NULL_RE.test(s)) return null;
|
|
92
110
|
if (BOOL_RE.test(s)) return s.toLowerCase() === "true";
|
|
111
|
+
// s.length <= MAX_SCALAR_BYTES asserted at function entry above.
|
|
93
112
|
if (INT_RE.test(s)) {
|
|
94
113
|
var n = parseInt(s, 10);
|
|
95
114
|
if (Number.isSafeInteger(n)) return n;
|
|
96
115
|
return s; // fallback to string for huge ints (don't lose precision silently)
|
|
97
116
|
}
|
|
117
|
+
// s.length <= MAX_SCALAR_BYTES asserted at function entry above.
|
|
98
118
|
if (INT_OCT.test(s)) {
|
|
99
|
-
var oct = parseInt(s.substring(2),
|
|
119
|
+
var oct = parseInt(s.substring(2), RADIX_OCTAL);
|
|
100
120
|
if (Number.isSafeInteger(oct)) return oct;
|
|
101
121
|
return s;
|
|
102
122
|
}
|
|
123
|
+
// s.length <= MAX_SCALAR_BYTES asserted at function entry above.
|
|
103
124
|
if (INT_HEX.test(s)) {
|
|
104
|
-
var hex = parseInt(s.substring(2),
|
|
125
|
+
var hex = parseInt(s.substring(2), RADIX_HEX);
|
|
105
126
|
if (Number.isSafeInteger(hex)) return hex;
|
|
106
127
|
return s;
|
|
107
128
|
}
|
|
129
|
+
// s.length <= MAX_SCALAR_BYTES asserted at function entry above.
|
|
108
130
|
if (FLOAT_INF.test(s)) return s.charAt(0) === "-" ? -Infinity : Infinity;
|
|
109
131
|
if (FLOAT_NAN.test(s)) return NaN;
|
|
132
|
+
// s.length <= MAX_SCALAR_BYTES asserted at function entry above.
|
|
110
133
|
if (FLOAT_RE.test(s)) {
|
|
111
134
|
var f = parseFloat(s);
|
|
112
135
|
if (!isNaN(f)) return f;
|
|
@@ -116,12 +139,27 @@ function _resolveScalar(s) {
|
|
|
116
139
|
|
|
117
140
|
function parse(input, opts) {
|
|
118
141
|
opts = opts || {};
|
|
119
|
-
|
|
142
|
+
// Validate operator-supplied numeric opts via lib/numeric-bounds —
|
|
143
|
+
// Infinity / NaN / negative / non-integer all bypass the
|
|
144
|
+
// `> 0` shape and silently lift the DoS cap they were meant to enforce.
|
|
145
|
+
if (opts.maxBytes !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxBytes)) {
|
|
146
|
+
throw new SafeYamlError("yaml.parse: maxBytes must be a positive finite integer; got " +
|
|
147
|
+
numericBounds.shape(opts.maxBytes), "yaml/bad-opt");
|
|
148
|
+
}
|
|
149
|
+
if (opts.maxDepth !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxDepth)) {
|
|
150
|
+
throw new SafeYamlError("yaml.parse: maxDepth must be a positive finite integer; got " +
|
|
151
|
+
numericBounds.shape(opts.maxDepth), "yaml/bad-opt");
|
|
152
|
+
}
|
|
153
|
+
if (opts.maxKeys !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxKeys)) {
|
|
154
|
+
throw new SafeYamlError("yaml.parse: maxKeys must be a positive finite integer; got " +
|
|
155
|
+
numericBounds.shape(opts.maxKeys), "yaml/bad-opt");
|
|
156
|
+
}
|
|
157
|
+
var maxBytes = opts.maxBytes !== undefined
|
|
120
158
|
? Math.min(opts.maxBytes, C.BYTES.mib(64)) : DEFAULTS.maxBytes;
|
|
121
|
-
var maxDepth =
|
|
122
|
-
? Math.min(opts.maxDepth,
|
|
123
|
-
var maxKeys =
|
|
124
|
-
? Math.min(opts.maxKeys,
|
|
159
|
+
var maxDepth = opts.maxDepth !== undefined
|
|
160
|
+
? Math.min(opts.maxDepth, 1_000) : DEFAULTS.maxDepth;
|
|
161
|
+
var maxKeys = opts.maxKeys !== undefined
|
|
162
|
+
? Math.min(opts.maxKeys, 1_000_000) : DEFAULTS.maxKeys;
|
|
125
163
|
|
|
126
164
|
input = safeBuffer.normalizeText(input, {
|
|
127
165
|
maxBytes: maxBytes,
|
|
@@ -147,7 +185,7 @@ function parse(input, opts) {
|
|
|
147
185
|
var raw = rawLines[i];
|
|
148
186
|
// Strip trailing whitespace from the line for analysis (block-scalar
|
|
149
187
|
// content paths preserve their own internal spacing separately).
|
|
150
|
-
var trimmed = raw.replace(
|
|
188
|
+
var trimmed = raw.replace(safeBuffer.TRAILING_HSPACE_RE, "");
|
|
151
189
|
var indent = 0;
|
|
152
190
|
while (indent < raw.length && raw.charAt(indent) === " ") indent += 1;
|
|
153
191
|
if (indent < raw.length && raw.charAt(indent) === "\t") {
|
|
@@ -294,7 +332,7 @@ function parse(input, opts) {
|
|
|
294
332
|
if (raw.charAt(0) === "'") {
|
|
295
333
|
return _decodeSingleQuoted(raw, lineNumber, col);
|
|
296
334
|
}
|
|
297
|
-
var trimmed = raw.replace(
|
|
335
|
+
var trimmed = raw.replace(safeBuffer.TRAILING_HSPACE_RE, "");
|
|
298
336
|
if (POISONED_KEYS.has(trimmed)) {
|
|
299
337
|
throw new SafeYamlError("forbidden key '" + trimmed + "'",
|
|
300
338
|
"yaml/poisoned-key", lineNumber, col);
|
|
@@ -468,7 +506,7 @@ function parse(input, opts) {
|
|
|
468
506
|
return sq;
|
|
469
507
|
}
|
|
470
508
|
// Plain scalar — strip trailing space, resolve type.
|
|
471
|
-
return _resolveScalar(t.replace(
|
|
509
|
+
return _resolveScalar(t.replace(safeBuffer.TRAILING_HSPACE_RE, ""));
|
|
472
510
|
}
|
|
473
511
|
|
|
474
512
|
// For quoted-string termination check on a single-line value.
|
|
@@ -584,7 +622,7 @@ function parse(input, opts) {
|
|
|
584
622
|
if (c === "," || c === "}" || c === "]") break;
|
|
585
623
|
p += 1;
|
|
586
624
|
}
|
|
587
|
-
var raw = text.substring(start, p).replace(
|
|
625
|
+
var raw = text.substring(start, p).replace(safeBuffer.TRAILING_HSPACE_RE, "");
|
|
588
626
|
return { value: _resolveScalar(raw), nextPos: p };
|
|
589
627
|
}
|
|
590
628
|
|
|
@@ -607,7 +645,7 @@ function parse(input, opts) {
|
|
|
607
645
|
if (c === ":" || c === "," || c === "}" || c === "]") break;
|
|
608
646
|
p += 1;
|
|
609
647
|
}
|
|
610
|
-
return { key: text.substring(start, p).replace(
|
|
648
|
+
return { key: text.substring(start, p).replace(safeBuffer.TRAILING_HSPACE_RE, ""), nextPos: p };
|
|
611
649
|
}
|
|
612
650
|
|
|
613
651
|
function _findMatchingBracket(text, start, open, close, lineNumber, col) {
|
|
@@ -678,10 +716,10 @@ function parse(input, opts) {
|
|
|
678
716
|
case "0": out += "\0"; i += 2; break;
|
|
679
717
|
case "u": {
|
|
680
718
|
var hex = raw.substring(i + 2, i + 6);
|
|
681
|
-
if (
|
|
719
|
+
if (!safeBuffer.isHex(hex, 4)) {
|
|
682
720
|
throw new SafeYamlError("bad \\u escape", "yaml/bad-escape", lineNumber, col + i);
|
|
683
721
|
}
|
|
684
|
-
out += String.fromCharCode(parseInt(hex,
|
|
722
|
+
out += String.fromCharCode(parseInt(hex, RADIX_HEX));
|
|
685
723
|
i += 6;
|
|
686
724
|
break;
|
|
687
725
|
}
|
|
@@ -690,7 +728,7 @@ function parse(input, opts) {
|
|
|
690
728
|
if (!/^[0-9a-fA-F]{8}$/.test(hex8)) {
|
|
691
729
|
throw new SafeYamlError("bad \\U escape", "yaml/bad-escape", lineNumber, col + i);
|
|
692
730
|
}
|
|
693
|
-
var code = parseInt(hex8,
|
|
731
|
+
var code = parseInt(hex8, RADIX_HEX);
|
|
694
732
|
if (code > 0x10FFFF) {
|
|
695
733
|
throw new SafeYamlError("\\U code point > U+10FFFF",
|
|
696
734
|
"yaml/bad-escape", lineNumber, col + i);
|
|
@@ -734,7 +772,7 @@ function parse(input, opts) {
|
|
|
734
772
|
function _stripEolComment(text) {
|
|
735
773
|
// Strip ` #...` comments (must be preceded by whitespace) at end of line.
|
|
736
774
|
var match = text.match(/^(.*?)(\s+#.*)?$/);
|
|
737
|
-
return (match && match[1] != null ? match[1] : text).replace(
|
|
775
|
+
return (match && match[1] != null ? match[1] : text).replace(safeBuffer.TRAILING_HSPACE_RE, "");
|
|
738
776
|
}
|
|
739
777
|
|
|
740
778
|
// ---- Block scalars (| literal, > folded) ----
|