@blamejs/core 0.18.35 → 0.18.37
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 +67 -0
- package/README.md +1 -1
- package/lib/auth/password.js +14 -5
- package/lib/crypto.js +74 -8
- package/lib/eat.js +7 -3
- package/lib/http-message-signature.js +9 -5
- package/lib/middleware/dpop.js +10 -6
- package/lib/parsers/safe-env.js +126 -17
- package/lib/parsers/safe-ini.js +115 -9
- package/lib/parsers/safe-toml.js +71 -16
- package/lib/parsers/safe-xml.js +7 -1
- package/lib/parsers/safe-yaml.js +213 -40
- package/lib/standard-webhooks.js +7 -5
- package/lib/totp.js +11 -2
- package/lib/webhook-hmac.js +11 -9
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/parsers/safe-yaml.js
CHANGED
|
@@ -59,6 +59,7 @@ var pick = require("../pick");
|
|
|
59
59
|
var boundedMap = require("../bounded-map");
|
|
60
60
|
var numericBounds = require("../numeric-bounds");
|
|
61
61
|
var safeBuffer = require("../safe-buffer");
|
|
62
|
+
var codepointClass = require("../codepoint-class");
|
|
62
63
|
var { FrameworkError } = require("../framework-error");
|
|
63
64
|
|
|
64
65
|
class SafeYamlError extends FrameworkError {
|
|
@@ -93,14 +94,187 @@ var DEFAULTS = {
|
|
|
93
94
|
// YAML 1.2 core-schema scalar resolution. Order matters: null first
|
|
94
95
|
// (covers ~ and empty), then bool, then int (with base prefixes), then
|
|
95
96
|
// float, then string fallback.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
var
|
|
101
|
-
var
|
|
102
|
-
var
|
|
103
|
-
var
|
|
97
|
+
// The resolvers are exact-set membership or a left-to-right walk. YAML's core
|
|
98
|
+
// schema spells each type out, so none of these needs to search: the anchored
|
|
99
|
+
// alternations were only ever a compact way of writing a fixed vocabulary and a
|
|
100
|
+
// digit shape.
|
|
101
|
+
var NULL_TOKENS = { "": 1, "null": 1, "Null": 1, "NULL": 1, "~": 1 };
|
|
102
|
+
var BOOL_TRUE = { "true": 1, "True": 1, "TRUE": 1 };
|
|
103
|
+
var BOOL_FALSE = { "false": 1, "False": 1, "FALSE": 1 };
|
|
104
|
+
var INF_TOKENS = { ".inf": 1, ".Inf": 1, ".INF": 1 };
|
|
105
|
+
var NAN_TOKENS = { ".nan": 1, ".NaN": 1, ".NAN": 1 };
|
|
106
|
+
var OCTAL_DIGITS = "01234567";
|
|
107
|
+
|
|
108
|
+
function _isNull(s) { return Object.prototype.hasOwnProperty.call(NULL_TOKENS, s); }
|
|
109
|
+
function _isBool(s) {
|
|
110
|
+
return Object.prototype.hasOwnProperty.call(BOOL_TRUE, s) ||
|
|
111
|
+
Object.prototype.hasOwnProperty.call(BOOL_FALSE, s);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function _signOffset(s) { var c = s.charAt(0); return (c === "-" || c === "+") ? 1 : 0; }
|
|
115
|
+
|
|
116
|
+
// `^[-+]?(0|[1-9][0-9]*)$` — no leading zeros, because `010` is a string in the
|
|
117
|
+
// core schema rather than an octal.
|
|
118
|
+
function _isDecimalInt(s) {
|
|
119
|
+
var i = _signOffset(s);
|
|
120
|
+
var rest = s.slice(i);
|
|
121
|
+
if (rest.length === 0) return false;
|
|
122
|
+
if (rest === "0") return true;
|
|
123
|
+
if (rest.charAt(0) === "0") return false;
|
|
124
|
+
return codepointClass.isRunOf(rest, codepointClass.ASCII_DIGITS);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function _isPrefixedInt(s, marker, alphabet) {
|
|
128
|
+
if (s.length < 3) return false;
|
|
129
|
+
if (s.charAt(0) !== "0" || s.charAt(1) !== marker) return false;
|
|
130
|
+
return codepointClass.isRunOf(s.slice(2), alphabet);
|
|
131
|
+
}
|
|
132
|
+
function _isOctalInt(s) { return _isPrefixedInt(s, "o", OCTAL_DIGITS); }
|
|
133
|
+
function _isHexInt(s) { return _isPrefixedInt(s, "x", codepointClass.ASCII_HEX); }
|
|
134
|
+
|
|
135
|
+
// `^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$` — either a leading dot
|
|
136
|
+
// with digits after it, or digits with an optional dot and optional digits, then
|
|
137
|
+
// an optional exponent that must carry at least one digit.
|
|
138
|
+
function _isFloat(s) {
|
|
139
|
+
var i = _signOffset(s);
|
|
140
|
+
var n = s.length;
|
|
141
|
+
// Both branches below return early when they find no digit, so reaching the
|
|
142
|
+
// exponent means at least one was consumed — no separate flag needed.
|
|
143
|
+
if (s.charAt(i) === ".") {
|
|
144
|
+
i += 1;
|
|
145
|
+
var fracStart = i;
|
|
146
|
+
while (i < n && codepointClass.ASCII_DIGITS.indexOf(s.charAt(i)) !== -1) i += 1;
|
|
147
|
+
if (i === fracStart) return false; // `.` alone is not a float
|
|
148
|
+
} else {
|
|
149
|
+
var intStart = i;
|
|
150
|
+
while (i < n && codepointClass.ASCII_DIGITS.indexOf(s.charAt(i)) !== -1) i += 1;
|
|
151
|
+
if (i === intStart) return false;
|
|
152
|
+
if (s.charAt(i) === ".") {
|
|
153
|
+
i += 1;
|
|
154
|
+
while (i < n && codepointClass.ASCII_DIGITS.indexOf(s.charAt(i)) !== -1) i += 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (i === n) return true;
|
|
158
|
+
var e = s.charAt(i);
|
|
159
|
+
if (e !== "e" && e !== "E") return false;
|
|
160
|
+
i += 1;
|
|
161
|
+
var expSign = s.charAt(i);
|
|
162
|
+
if (expSign === "+" || expSign === "-") i += 1;
|
|
163
|
+
if (i >= n) return false;
|
|
164
|
+
return codepointClass.isRunOf(s.slice(i), codepointClass.ASCII_DIGITS);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function _isInfinity(s) {
|
|
168
|
+
return Object.prototype.hasOwnProperty.call(INF_TOKENS, s.slice(_signOffset(s)));
|
|
169
|
+
}
|
|
170
|
+
function _isNotANumber(s) {
|
|
171
|
+
return Object.prototype.hasOwnProperty.call(NAN_TOKENS, s);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function _isWhitespaceChar(ch) {
|
|
175
|
+
return ch.length === 1 && codepointClass.inRanges(ch.charCodeAt(0), codepointClass.WHITESPACE_RANGES);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// The four characters `.` does not match without the `s` flag.
|
|
179
|
+
function _isLineTerminator(ch) {
|
|
180
|
+
return ch === "\n" || ch === "\r" || ch === "\u2028" || ch === "\u2029";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// `^---(\s|$)` / `^\.\.\.(\s|$)` — a document marker is the three characters and
|
|
184
|
+
// then either the end of the line or a space, so `---foo` is not one.
|
|
185
|
+
function _isDocumentMarker(line, marker) {
|
|
186
|
+
if (line.slice(0, marker.length) !== marker) return false;
|
|
187
|
+
if (line.length === marker.length) return true;
|
|
188
|
+
return _isWhitespaceChar(line.charAt(marker.length));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// `^[|>][-+]?[0-9]?\s*$` and its variant allowing a trailing `# comment`.
|
|
192
|
+
// A block-scalar header: the indicator, an optional chomping sign, an optional
|
|
193
|
+
// single explicit-indent digit, then nothing that matters.
|
|
194
|
+
function _isBlockScalarHeader(content, allowComment) {
|
|
195
|
+
var i = 0;
|
|
196
|
+
var lead = content.charAt(0);
|
|
197
|
+
if (lead !== "|" && lead !== ">") return false;
|
|
198
|
+
i += 1;
|
|
199
|
+
var sign = content.charAt(i);
|
|
200
|
+
if (sign === "-" || sign === "+") i += 1;
|
|
201
|
+
var digit = content.charAt(i);
|
|
202
|
+
if (digit.length === 1 && codepointClass.ASCII_DIGITS.indexOf(digit) !== -1) i += 1;
|
|
203
|
+
while (i < content.length && _isWhitespaceChar(content.charAt(i))) i += 1;
|
|
204
|
+
if (i === content.length) return true;
|
|
205
|
+
if (!allowComment) return false;
|
|
206
|
+
if (content.charAt(i) !== "#") return false;
|
|
207
|
+
// `.*` stops at a line terminator, and `$` without `m` is end-of-string, so a
|
|
208
|
+
// comment may not contain one. ECMAScript counts four, not just LF: U+2028 and
|
|
209
|
+
// U+2029 are line terminators to the grammar even though nothing else here
|
|
210
|
+
// treats them as breaks, and accepting them would admit a header the pattern
|
|
211
|
+
// refused.
|
|
212
|
+
var tail = content.slice(i);
|
|
213
|
+
for (var t = 0; t < tail.length; t += 1) {
|
|
214
|
+
if (_isLineTerminator(tail.charAt(t))) return false;
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// `(^|\s)([&*])([A-Za-z0-9_][A-Za-z0-9_-]*)` — an anchor or alias sigil that
|
|
220
|
+
// opens a token. Returns the index of the match INCLUDING the leading separator,
|
|
221
|
+
// matching what the pattern captured, so the reported line is unchanged.
|
|
222
|
+
var _NAME_HEAD = codepointClass.ASCII_ALNUM + "_";
|
|
223
|
+
var _NAME_TAIL = codepointClass.ASCII_ALNUM + "_-";
|
|
224
|
+
|
|
225
|
+
function _findAnchorOrAlias(text) {
|
|
226
|
+
for (var i = 0; i < text.length; i += 1) {
|
|
227
|
+
var ch = text.charAt(i);
|
|
228
|
+
if (ch !== "&" && ch !== "*") continue;
|
|
229
|
+
var atStart = i === 0;
|
|
230
|
+
if (!atStart && !_isWhitespaceChar(text.charAt(i - 1))) continue;
|
|
231
|
+
if (_NAME_HEAD.indexOf(text.charAt(i + 1)) === -1 || text.charAt(i + 1) === "") continue;
|
|
232
|
+
return { index: atStart ? i : i - 1, sigil: ch };
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// `(^|[\s-])(!{1,2}[A-Za-z<])` — one or two `!` opening a tag.
|
|
238
|
+
function _findTag(text) {
|
|
239
|
+
for (var i = 0; i < text.length; i += 1) {
|
|
240
|
+
if (text.charAt(i) !== "!") continue;
|
|
241
|
+
var atStart = i === 0;
|
|
242
|
+
var before = text.charAt(i - 1);
|
|
243
|
+
if (!atStart && !(before === "-" || _isWhitespaceChar(before))) continue;
|
|
244
|
+
// The pattern is greedy, so it takes two `!` when both are present.
|
|
245
|
+
var after = text.charAt(i + 1) === "!" ? text.charAt(i + 2) : text.charAt(i + 1);
|
|
246
|
+
if (after.length !== 1) continue;
|
|
247
|
+
// Digits count. The pattern this replaces used `[A-Za-z<]` while the comment
|
|
248
|
+
// above it said "alphanumeric or `<`", and the code was the weaker of the
|
|
249
|
+
// two: `a: !123` is a local tag and parsed as the plain string "!123"
|
|
250
|
+
// instead of being refused, so the documented ban had a hole in it. This is
|
|
251
|
+
// not a regression from the rewrite — `main` behaves the same way — but the
|
|
252
|
+
// promise is that tags are refused, so the stricter reading wins.
|
|
253
|
+
if (after !== "<" && codepointClass.ASCII_ALNUM.indexOf(after) === -1) continue;
|
|
254
|
+
return { index: atStart ? i : i - 1 };
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// `(^|\n)%(YAML|TAG)\b` — a directive at column zero.
|
|
260
|
+
var _DIRECTIVE_NAMES = ["YAML", "TAG"];
|
|
261
|
+
|
|
262
|
+
function _findDirective(text) {
|
|
263
|
+
for (var i = 0; i < text.length; i += 1) {
|
|
264
|
+
if (text.charAt(i) !== "%") continue;
|
|
265
|
+
var atStart = i === 0;
|
|
266
|
+
if (!atStart && text.charAt(i - 1) !== "\n") continue;
|
|
267
|
+
for (var d = 0; d < _DIRECTIVE_NAMES.length; d += 1) {
|
|
268
|
+
var name = _DIRECTIVE_NAMES[d];
|
|
269
|
+
if (text.slice(i + 1, i + 1 + name.length) !== name) continue;
|
|
270
|
+
// `\b` — the character after the name must not continue a word.
|
|
271
|
+
var next = text.charAt(i + 1 + name.length);
|
|
272
|
+
if (next.length === 1 && (_NAME_HEAD.indexOf(next) !== -1)) continue;
|
|
273
|
+
return { index: atStart ? i : i - 1, precededByNewline: !atStart };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
104
278
|
|
|
105
279
|
function _resolveScalar(s) {
|
|
106
280
|
// Fall back to string for any token whose length exceeds the scalar cap
|
|
@@ -109,31 +283,31 @@ function _resolveScalar(s) {
|
|
|
109
283
|
// type-inference regexes never see a pathologically long string.
|
|
110
284
|
if (typeof s !== "string" || s.length > MAX_SCALAR_BYTES) return s;
|
|
111
285
|
// Below: every regex test sees an `s` whose s.length <= MAX_SCALAR_BYTES.
|
|
112
|
-
if (
|
|
113
|
-
if (
|
|
286
|
+
if (_isNull(s)) return null;
|
|
287
|
+
if (_isBool(s)) return s.toLowerCase() === "true";
|
|
114
288
|
// s.length <= MAX_SCALAR_BYTES asserted at function entry above.
|
|
115
|
-
if (
|
|
289
|
+
if (_isDecimalInt(s)) {
|
|
116
290
|
var n = parseInt(s, 10);
|
|
117
291
|
if (Number.isSafeInteger(n)) return n;
|
|
118
292
|
return s; // fallback to string for huge ints (don't lose precision silently)
|
|
119
293
|
}
|
|
120
294
|
// s.length <= MAX_SCALAR_BYTES asserted at function entry above.
|
|
121
|
-
if (
|
|
295
|
+
if (_isOctalInt(s)) {
|
|
122
296
|
var oct = parseInt(s.substring(2), RADIX_OCTAL);
|
|
123
297
|
if (Number.isSafeInteger(oct)) return oct;
|
|
124
298
|
return s;
|
|
125
299
|
}
|
|
126
300
|
// s.length <= MAX_SCALAR_BYTES asserted at function entry above.
|
|
127
|
-
if (
|
|
301
|
+
if (_isHexInt(s)) {
|
|
128
302
|
var hex = parseInt(s.substring(2), RADIX_HEX);
|
|
129
303
|
if (Number.isSafeInteger(hex)) return hex;
|
|
130
304
|
return s;
|
|
131
305
|
}
|
|
132
306
|
// s.length <= MAX_SCALAR_BYTES asserted at function entry above.
|
|
133
|
-
if (
|
|
134
|
-
if (
|
|
307
|
+
if (_isInfinity(s)) return s.charAt(0) === "-" ? -Infinity : Infinity;
|
|
308
|
+
if (_isNotANumber(s)) return NaN;
|
|
135
309
|
// s.length <= MAX_SCALAR_BYTES asserted at function entry above.
|
|
136
|
-
if (
|
|
310
|
+
if (_isFloat(s)) {
|
|
137
311
|
var f = parseFloat(s);
|
|
138
312
|
if (!isNaN(f)) return f;
|
|
139
313
|
}
|
|
@@ -179,7 +353,9 @@ function parse(input, opts) {
|
|
|
179
353
|
_preValidate(input);
|
|
180
354
|
|
|
181
355
|
// Normalize line endings: CRLF / CR → LF for consistent line-based work.
|
|
182
|
-
|
|
356
|
+
// Normalise CRLF and lone CR to LF. splitLinesAny treats all three as one
|
|
357
|
+
// break, so rejoining with "\n" is the same substitution in one pass.
|
|
358
|
+
input = codepointClass.splitLinesAny(input).join("\n");
|
|
183
359
|
|
|
184
360
|
// Split into raw lines preserving line numbers.
|
|
185
361
|
var rawLines = input.split("\n");
|
|
@@ -212,11 +388,11 @@ function parse(input, opts) {
|
|
|
212
388
|
// Reject any later `---` or `...` (multi-document streams not supported).
|
|
213
389
|
var idx = 0;
|
|
214
390
|
while (idx < lines.length && (lines[idx].isBlank || lines[idx].isComment)) idx += 1;
|
|
215
|
-
if (idx < lines.length &&
|
|
391
|
+
if (idx < lines.length && _isDocumentMarker(lines[idx].content, "---")) idx += 1;
|
|
216
392
|
// Subsequent doc markers anywhere = reject.
|
|
217
393
|
for (var j = idx; j < lines.length; j++) {
|
|
218
394
|
var c = lines[j].content;
|
|
219
|
-
if (
|
|
395
|
+
if (_isDocumentMarker(c, "---") || _isDocumentMarker(c, "...")) {
|
|
220
396
|
throw new SafeYamlError(
|
|
221
397
|
"multi-document YAML streams are not supported",
|
|
222
398
|
"yaml/multi-document", lines[j].lineNumber, 1
|
|
@@ -277,7 +453,7 @@ function parse(input, opts) {
|
|
|
277
453
|
}
|
|
278
454
|
|
|
279
455
|
// Block scalar header: `|` or `>` (with optional chomp/indent indicator)
|
|
280
|
-
if (
|
|
456
|
+
if (_isBlockScalarHeader(content, true)) {
|
|
281
457
|
return _parseBlockScalar(k, indent, content);
|
|
282
458
|
}
|
|
283
459
|
|
|
@@ -398,7 +574,8 @@ function parse(input, opts) {
|
|
|
398
574
|
// 1. if there's content after the colon, that's a flow value or
|
|
399
575
|
// a plain scalar continuation on the same line.
|
|
400
576
|
// 2. otherwise the value lives on subsequent more-indented lines.
|
|
401
|
-
var afterColon =
|
|
577
|
+
var afterColon = codepointClass.trimChars(
|
|
578
|
+
ln.content.substring(keyRange.valueStart), " \t", { trailing: false });
|
|
402
579
|
// Strip end-of-line comment
|
|
403
580
|
afterColon = _stripEolComment(afterColon);
|
|
404
581
|
var value;
|
|
@@ -406,7 +583,7 @@ function parse(input, opts) {
|
|
|
406
583
|
// Inline value on this line.
|
|
407
584
|
if (afterColon.charAt(0) === "|" || afterColon.charAt(0) === ">") {
|
|
408
585
|
// Block scalar header inline with key
|
|
409
|
-
if (
|
|
586
|
+
if (!_isBlockScalarHeader(afterColon, false)) {
|
|
410
587
|
throw new SafeYamlError("malformed block scalar header",
|
|
411
588
|
"yaml/bad-block-scalar", ln.lineNumber, ln.indent + 1);
|
|
412
589
|
}
|
|
@@ -506,7 +683,8 @@ function parse(input, opts) {
|
|
|
506
683
|
// branch below) using _parseFlowValue's end position, then return the value
|
|
507
684
|
// via the direct parser so the shape is identical to the pre-existing path.
|
|
508
685
|
var fend = _parseFlowValue(t, 0, lineNumber, col, 0).nextPos;
|
|
509
|
-
var afterFlow =
|
|
686
|
+
var afterFlow = codepointClass.trimRanges(
|
|
687
|
+
t.slice(fend), codepointClass.WHITESPACE_RANGES, { trailing: false });
|
|
510
688
|
if (afterFlow.length > 0 && afterFlow.charAt(0) !== "#") {
|
|
511
689
|
throw new SafeYamlError("unexpected content after flow collection",
|
|
512
690
|
"yaml/trailing-content", lineNumber, col);
|
|
@@ -517,7 +695,8 @@ function parse(input, opts) {
|
|
|
517
695
|
if (t.charAt(0) === '"') {
|
|
518
696
|
var dq = _decodeDoubleQuoted(t, lineNumber, col);
|
|
519
697
|
var afterDq = _trailingAfterQuoted(t, '"');
|
|
520
|
-
if (afterDq.length > 0 &&
|
|
698
|
+
if (afterDq.length > 0 &&
|
|
699
|
+
codepointClass.trimRanges(afterDq, codepointClass.WHITESPACE_RANGES, { trailing: false }) !== "") {
|
|
521
700
|
throw new SafeYamlError("unexpected content after quoted string",
|
|
522
701
|
"yaml/trailing-content", lineNumber, col);
|
|
523
702
|
}
|
|
@@ -759,7 +938,7 @@ function parse(input, opts) {
|
|
|
759
938
|
}
|
|
760
939
|
case "U": {
|
|
761
940
|
var hex8 = raw.substring(i + 2, i + 10);
|
|
762
|
-
if (
|
|
941
|
+
if (hex8.length !== 8 || !codepointClass.isRunOf(hex8, codepointClass.ASCII_HEX)) {
|
|
763
942
|
throw new SafeYamlError("bad \\U escape", "yaml/bad-escape", lineNumber, col + i);
|
|
764
943
|
}
|
|
765
944
|
var code = parseInt(hex8, RADIX_HEX);
|
|
@@ -904,7 +1083,7 @@ function parse(input, opts) {
|
|
|
904
1083
|
|
|
905
1084
|
if (chomp === "-") {
|
|
906
1085
|
// strip — remove trailing newline(s)
|
|
907
|
-
body =
|
|
1086
|
+
body = codepointClass.trimRanges(body, [0x0A], { leading: false });
|
|
908
1087
|
} else if (chomp === "+") {
|
|
909
1088
|
// keep — restore trailing blanks we popped
|
|
910
1089
|
body += "\n".repeat(trailingBlanks);
|
|
@@ -1026,14 +1205,12 @@ function _preValidate(input) {
|
|
|
1026
1205
|
// (or start of value position). A simple heuristic: any unescaped `&`
|
|
1027
1206
|
// that's followed by an identifier char and is preceded by space or
|
|
1028
1207
|
// line start is an anchor. Same for `*`.
|
|
1029
|
-
var
|
|
1030
|
-
var m = safe.match(anchorOrAliasRe);
|
|
1208
|
+
var m = _findAnchorOrAlias(safe);
|
|
1031
1209
|
if (m) {
|
|
1032
|
-
var
|
|
1033
|
-
var lineCount = safe.substring(0, posIdx).split("\n").length;
|
|
1210
|
+
var lineCount = safe.substring(0, m.index).split("\n").length;
|
|
1034
1211
|
throw new SafeYamlError(
|
|
1035
|
-
m
|
|
1036
|
-
m
|
|
1212
|
+
m.sigil === "&" ? "anchors are not supported" : "aliases are not supported",
|
|
1213
|
+
m.sigil === "&" ? "yaml/anchors-banned" : "yaml/aliases-banned",
|
|
1037
1214
|
lineCount, 1
|
|
1038
1215
|
);
|
|
1039
1216
|
}
|
|
@@ -1041,21 +1218,17 @@ function _preValidate(input) {
|
|
|
1041
1218
|
// Tags: `!` at start of value or after `: ` / `- `. False-positive risk:
|
|
1042
1219
|
// "key: !something" vs "key: ![bracket". We match `!` followed by
|
|
1043
1220
|
// alphanumeric or `<`.
|
|
1044
|
-
var
|
|
1045
|
-
var mt = safe.match(tagRe);
|
|
1221
|
+
var mt = _findTag(safe);
|
|
1046
1222
|
if (mt) {
|
|
1047
|
-
var
|
|
1048
|
-
var tagLine = safe.substring(0, tagIdx).split("\n").length;
|
|
1223
|
+
var tagLine = safe.substring(0, mt.index).split("\n").length;
|
|
1049
1224
|
throw new SafeYamlError("tags are not supported",
|
|
1050
1225
|
"yaml/tags-banned", tagLine, 1);
|
|
1051
1226
|
}
|
|
1052
1227
|
|
|
1053
1228
|
// Directives: `%YAML` or `%TAG` at column 0
|
|
1054
|
-
var
|
|
1055
|
-
var md = safe.match(dirRe);
|
|
1229
|
+
var md = _findDirective(safe);
|
|
1056
1230
|
if (md) {
|
|
1057
|
-
var
|
|
1058
|
-
var dirLine = safe.substring(0, dirIdx).split("\n").length + (md[1] === "\n" ? 1 : 0);
|
|
1231
|
+
var dirLine = safe.substring(0, md.index).split("\n").length + (md.precededByNewline ? 1 : 0);
|
|
1059
1232
|
throw new SafeYamlError("directives are not supported",
|
|
1060
1233
|
"yaml/directives-banned", dirLine, 1);
|
|
1061
1234
|
}
|
package/lib/standard-webhooks.js
CHANGED
|
@@ -158,17 +158,19 @@ function verify(opts) {
|
|
|
158
158
|
var toSign = id + "." + ts + "." + bodyBuf.toString("utf8");
|
|
159
159
|
var expected = nodeCrypto.createHmac("sha256", opts.secret).update(toSign).digest("base64");
|
|
160
160
|
// Multi-version: signature header is `v1,<sig> v2,<sig>` etc.
|
|
161
|
+
// Every v1 signature in the header is compared, with no break: stopping at
|
|
162
|
+
// the first match let the response time report the POSITION of the matching
|
|
163
|
+
// signature within a multi-version header, which is a property of the
|
|
164
|
+
// sender's key rotation and not something a verifier should hand back.
|
|
161
165
|
var parts = sigHeader.split(" ");
|
|
162
|
-
var
|
|
166
|
+
var offered = [];
|
|
163
167
|
for (var p = 0; p < parts.length; p += 1) {
|
|
164
168
|
var pair = parts[p].split(",");
|
|
165
169
|
if (pair.length !== 2) continue;
|
|
166
170
|
if (pair[0] !== "v1") continue;
|
|
167
|
-
|
|
168
|
-
any = true;
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
+
offered.push(Buffer.from(pair[1], "utf8"));
|
|
171
172
|
}
|
|
173
|
+
var any = bCrypto.timingSafeEqualAny(Buffer.from(expected, "utf8"), offered);
|
|
172
174
|
if (!any) {
|
|
173
175
|
throw new StandardWebhooksError("standard-webhooks/bad-signature",
|
|
174
176
|
"verify: no v1 signature matched");
|
package/lib/totp.js
CHANGED
|
@@ -282,6 +282,12 @@ function verify(secret, code, opts) {
|
|
|
282
282
|
var userCode = String(code).replace(/[\s.\-_]/g, "").padStart(resolved.digits, "0");
|
|
283
283
|
var userBuf = Buffer.from(userCode);
|
|
284
284
|
|
|
285
|
+
// Every step in the drift window is compared, even after one matches. Returning
|
|
286
|
+
// from inside the loop would make the response time report WHICH step matched,
|
|
287
|
+
// and that is the clock offset between the authenticator and the server — a
|
|
288
|
+
// value derived from the shared secret's timeline that a caller submitting
|
|
289
|
+
// codes can otherwise only guess at.
|
|
290
|
+
var matchedStep = null;
|
|
285
291
|
for (var d = -resolved.driftSteps; d <= resolved.driftSteps; d++) {
|
|
286
292
|
var step = currentStep + d;
|
|
287
293
|
if (lastUsedStep !== null && step <= lastUsedStep) continue; // reject replays at-or-below the last accepted step
|
|
@@ -293,11 +299,14 @@ function verify(secret, code, opts) {
|
|
|
293
299
|
try { expected = _hotp(secret, step, resolved); }
|
|
294
300
|
catch (_e) { return false; }
|
|
295
301
|
var expectedBuf = Buffer.from(expected);
|
|
302
|
+
// The comparison is not guarded by `matchedStep === null` — that would
|
|
303
|
+
// short-circuit the call away once something matched, which is the early
|
|
304
|
+
// exit wearing a different syntax. The earliest match still wins.
|
|
296
305
|
if (timingSafeEqual(expectedBuf, userBuf)) {
|
|
297
|
-
|
|
306
|
+
if (matchedStep === null) matchedStep = step;
|
|
298
307
|
}
|
|
299
308
|
}
|
|
300
|
-
return false;
|
|
309
|
+
return matchedStep === null ? false : matchedStep;
|
|
301
310
|
}
|
|
302
311
|
|
|
303
312
|
function uri(secret, account, opts) {
|
package/lib/webhook-hmac.js
CHANGED
|
@@ -197,17 +197,19 @@ function verify(opts) {
|
|
|
197
197
|
var signed = Buffer.concat([Buffer.from(tsRaw + ".", "utf8"), bodyBuf]);
|
|
198
198
|
var expected = bCrypto.hmac(secretBuf, signed, nodeAlg);
|
|
199
199
|
var expectedBuf = Buffer.from(expected, "utf8");
|
|
200
|
-
|
|
200
|
+
// Every offered signature is compared. The `break` this replaced ended the
|
|
201
|
+
// loop at the first match, so a header whose first signature matched
|
|
202
|
+
// answered sooner than one whose last did — the position of the sender's
|
|
203
|
+
// current key inside its rotation, reported by timing.
|
|
204
|
+
//
|
|
205
|
+
// timingSafeEqualAny applies the same length pre-check per candidate; a
|
|
206
|
+
// wrong-length candidate cannot be the digest (the hex length is fixed by
|
|
207
|
+
// the algorithm, and is not secret), so it leaks nothing.
|
|
208
|
+
var offeredBufs = [];
|
|
201
209
|
for (var s = 0; s < sigs.length; s += 1) {
|
|
202
|
-
|
|
203
|
-
// cannot be the digest (the hex length is fixed by the algorithm, and is
|
|
204
|
-
// not secret), so the length pre-check leaks nothing.
|
|
205
|
-
if (sigs[s].length === expected.length &&
|
|
206
|
-
bCrypto.timingSafeEqual(expectedBuf, Buffer.from(sigs[s], "utf8"))) {
|
|
207
|
-
matched = true;
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
+
offeredBufs.push(Buffer.from(sigs[s], "utf8"));
|
|
210
211
|
}
|
|
212
|
+
var matched = bCrypto.timingSafeEqualAny(expectedBuf, offeredBufs);
|
|
211
213
|
if (!matched) {
|
|
212
214
|
throw new WebhookHmacError("webhook-hmac/bad-signature",
|
|
213
215
|
"verify: no '" + sigField + "' signature matched");
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:446f4ad2-1001-404f-a62c-c116788ed0b8",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-19T02:55:12.713Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.18.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.18.37",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.18.
|
|
25
|
+
"version": "0.18.37",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.18.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.18.37",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.18.
|
|
57
|
+
"ref": "@blamejs/core@0.18.37",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|