@blamejs/blamejs-shop 0.5.20 → 0.5.22
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 +1 -0
- package/SECURITY.md +23 -0
- package/lib/addresses.js +17 -3
- package/lib/admin.js +14 -5
- package/lib/affiliates.js +7 -23
- package/lib/announcement-bar.js +5 -22
- package/lib/api-keys.js +4 -13
- package/lib/asset-manifest.json +1 -1
- package/lib/banner-ab-tests.js +5 -9
- package/lib/blog-articles.js +8 -26
- package/lib/captcha-gate.js +7 -17
- package/lib/carrier-accounts.js +3 -10
- package/lib/catalog-drafts.js +5 -12
- package/lib/category-navigation.js +7 -20
- package/lib/cms-blocks.js +4 -7
- package/lib/compliance-export.js +11 -11
- package/lib/consent-ledger.js +2 -4
- package/lib/cost-layers.js +56 -21
- package/lib/customer-import.js +2 -2
- package/lib/customer-notes.js +7 -23
- package/lib/customer-roles.js +3 -5
- package/lib/customer-segments.js +17 -14
- package/lib/customer-surveys.js +7 -14
- package/lib/customers.js +2 -4
- package/lib/dispute-resolution.js +5 -13
- package/lib/dropship-forwarding.js +3 -5
- package/lib/dunning.js +2 -7
- package/lib/email-ab-tests.js +4 -8
- package/lib/email-campaigns.js +39 -15
- package/lib/email-warmup.js +2 -7
- package/lib/experiments.js +6 -10
- package/lib/externaldb-d1.js +47 -31
- package/lib/geolocation.js +18 -28
- package/lib/gift-options.js +5 -23
- package/lib/knowledge-base.js +7 -22
- package/lib/line-gift-wrap.js +7 -17
- package/lib/live-chat.js +5 -18
- package/lib/metered-usage.js +3 -8
- package/lib/notifications.js +3 -7
- package/lib/operator-accounts.js +3 -5
- package/lib/operator-approvals.js +6 -8
- package/lib/operator-audit-log.js +2 -7
- package/lib/operator-help-center.js +5 -21
- package/lib/operator-inbox.js +4 -11
- package/lib/operator-roles.js +5 -7
- package/lib/order-escalation.js +2 -2
- package/lib/order-export.js +24 -20
- package/lib/order-notes.js +4 -14
- package/lib/order-ratings.js +4 -10
- package/lib/payment-methods.js +5 -13
- package/lib/payment-retries.js +2 -7
- package/lib/pixel-events.js +21 -28
- package/lib/plan-changes.js +2 -7
- package/lib/product-qa.js +4 -8
- package/lib/promo-banners.js +7 -15
- package/lib/purchase-orders.js +3 -6
- package/lib/push-notifications.js +7 -32
- package/lib/pwa-manifest.js +6 -11
- package/lib/quotes.js +5 -9
- package/lib/reviews.js +4 -10
- package/lib/robots-config.js +6 -14
- package/lib/sales-tax-filings.js +2 -2
- package/lib/search-facets.js +5 -5
- package/lib/search-ranking.js +4 -7
- package/lib/search-synonyms.js +7 -21
- package/lib/security-middleware.js +86 -58
- package/lib/seller-signup.js +10 -26
- package/lib/sidebar-widgets.js +6 -15
- package/lib/site-redirects.js +5 -16
- package/lib/sitemap-generator.js +3 -7
- package/lib/sms-dispatcher.js +7 -23
- package/lib/stock-receipts.js +4 -10
- package/lib/storefront-dashboards.js +2 -10
- package/lib/storefront-forms.js +9 -22
- package/lib/storefront-pages.js +6 -20
- package/lib/storefront.js +2 -2
- package/lib/subscription-billing.js +3 -8
- package/lib/subscription-controls.js +2 -7
- package/lib/suggestion-box.js +9 -26
- package/lib/support-tickets.js +6 -24
- package/lib/tax-remittance.js +2 -2
- package/lib/tenants.js +2 -7
- package/lib/text-guard.js +179 -35
- package/lib/theme-assets.js +4 -16
- package/lib/translations.js +3 -6
- package/lib/trust-badges.js +4 -7
- package/lib/vendor/MANIFEST.json +12 -12
- package/lib/vendor/blamejs/CHANGELOG.md +32 -0
- package/lib/vendor/blamejs/NOTICE +1 -1
- package/lib/vendor/blamejs/README.md +1 -1
- package/lib/vendor/blamejs/lib/network-tls.js +98 -13
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +12 -12
- package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +568 -304
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/sbom.cdx.json +6 -6
- package/lib/vendor-invoices.js +3 -6
- package/lib/vendors.js +5 -19
- package/lib/webhook-receiver.js +3 -10
- package/lib/webhook-subscriptions.js +3 -13
- package/lib/wishlist-sharing.js +5 -10
- package/package.json +1 -1
package/lib/text-guard.js
CHANGED
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
* - slugLabel(s, label, opts?) — lowercase URL-slug label
|
|
37
37
|
* - hostLabel(host, label?) — outbound host SSRF + by-name denylist
|
|
38
38
|
* - freeText(s, label, policy?) — dangerous-codepoint screen for
|
|
39
|
-
* unconstrained UTF-8 text fields
|
|
39
|
+
* unconstrained UTF-8 text fields, throwing on a refusal
|
|
40
|
+
* - hasCodepointThreat(s, policy?) — the same screen for a defensive
|
|
41
|
+
* reader that DROPS a bad value instead of refusing the request
|
|
42
|
+
* - firstCodepointThreat(s, policy?) — the one decision both run, so
|
|
43
|
+
* the throwing and dropping paths cannot disagree
|
|
40
44
|
*
|
|
41
45
|
* @primitive textGuard
|
|
42
46
|
* @related b.money, b.ssrfGuard, b.safeUrl, shop.currencyRounding,
|
|
@@ -52,6 +56,67 @@ var b = require("./vendor/blamejs");
|
|
|
52
56
|
// second copy of the tables.
|
|
53
57
|
var codepointClass = b.codepointClass;
|
|
54
58
|
|
|
59
|
+
// Two invisible-codepoint groups this shop refuses that the framework catalog
|
|
60
|
+
// does not carry yet. Built with codepointClass.charClass off a range table so
|
|
61
|
+
// the escaping is the catalog's, not a second hand-spelled class:
|
|
62
|
+
//
|
|
63
|
+
// U+007F DELETE — grouped with the C0 controls everywhere it matters, but
|
|
64
|
+
// outside the 0x00-0x1F block the framework's C0_CTRL_RANGES covers.
|
|
65
|
+
// U+2061-2064 FUNCTION APPLICATION / INVISIBLE TIMES / INVISIBLE SEPARATOR /
|
|
66
|
+
// INVISIBLE PLUS — zero-width invisible operators, the same spoofing class
|
|
67
|
+
// as U+2060 WORD JOINER, which the framework's ZERO_WIDTH_RANGES does have.
|
|
68
|
+
//
|
|
69
|
+
// Requested upstream (blamejs#580). When a vendor refresh lands them in the
|
|
70
|
+
// catalog this supplement becomes dead weight and comes out — the drift test
|
|
71
|
+
// in test/layer-1-state/text-guard.test.js fails the moment that happens, so
|
|
72
|
+
// it cannot be silently carried forever.
|
|
73
|
+
//
|
|
74
|
+
// Kept as two classes rather than one so each joins the check whose refusal
|
|
75
|
+
// message is already true of it.
|
|
76
|
+
// allow:dynamic-regex — codepoints from a literal range table
|
|
77
|
+
var DEL_RE = new RegExp("[" + codepointClass.charClass([0x007F]) + "]");
|
|
78
|
+
// allow:dynamic-regex — codepoints from a literal range table
|
|
79
|
+
var INVISIBLE_OP_RE = new RegExp("[" + codepointClass.charClass([[0x2061, 0x2064]]) + "]");
|
|
80
|
+
// Global twin for the strip pass; the unglobbed one above is the presence test.
|
|
81
|
+
// allow:dynamic-regex — codepoints from a literal range table
|
|
82
|
+
var INVISIBLE_OP_RE_G = new RegExp("[" + codepointClass.charClass([[0x2061, 0x2064]]) + "]", "g");
|
|
83
|
+
|
|
84
|
+
// The framework's BIDI_RE is the union of two groups that do NOT carry the
|
|
85
|
+
// same risk, and refusing both is what broke right-to-left addresses:
|
|
86
|
+
//
|
|
87
|
+
// OVERRIDES + ISOLATES — U+202A-202E and U+2066-2069. These reorder a RUN
|
|
88
|
+
// of surrounding text, which is the Trojan Source primitive: a value that
|
|
89
|
+
// displays as something other than what it says. Nothing legitimate in a
|
|
90
|
+
// shop's stored text needs them.
|
|
91
|
+
// MARKS — U+200E LEFT-TO-RIGHT MARK, U+200F RIGHT-TO-LEFT MARK, U+061C
|
|
92
|
+
// ARABIC LETTER MARK. These only resolve the direction of ADJACENT
|
|
93
|
+
// NEUTRAL characters — a house number inside an Arabic street name, a
|
|
94
|
+
// Latin brand inside a Hebrew sentence. They are how mixed-direction text
|
|
95
|
+
// is correctly authored, and an Arabic or Hebrew postal address routinely
|
|
96
|
+
// contains one. They cannot reverse a run.
|
|
97
|
+
//
|
|
98
|
+
// So the marks are ALLOWED by default and the overrides are refused. A field
|
|
99
|
+
// that genuinely wants neither (a machine-readable identifier, a slug) asks
|
|
100
|
+
// for `bidiMarks: "reject"`.
|
|
101
|
+
// allow:dynamic-regex — codepoints from a literal range table
|
|
102
|
+
var BIDI_OVERRIDE_RE = new RegExp(
|
|
103
|
+
"[" + codepointClass.charClass([[0x202A, 0x202E], [0x2066, 0x2069]]) + "]"
|
|
104
|
+
);
|
|
105
|
+
// allow:dynamic-regex — codepoints from a literal range table
|
|
106
|
+
var BIDI_MARK_RE = new RegExp(
|
|
107
|
+
"[" + codepointClass.charClass([0x200E, 0x200F, 0x061C]) + "]"
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Everything that ends a line or pads it out, for a value that must stay on
|
|
111
|
+
// one. Tab / LF / CR sit outside the framework's C0 table on purpose — a
|
|
112
|
+
// multi-line body needs them — so a single-line field asks for them here.
|
|
113
|
+
// U+2028 / U+2029 are not control characters at all, but they terminate a
|
|
114
|
+
// line for both a renderer and a JS string literal.
|
|
115
|
+
// allow:dynamic-regex — codepoints from a literal range table
|
|
116
|
+
var LINE_BREAK_RE = new RegExp(
|
|
117
|
+
"[" + codepointClass.charClass([0x0009, 0x000A, 0x000D, [0x2028, 0x2029]]) + "]"
|
|
118
|
+
);
|
|
119
|
+
|
|
55
120
|
// ---- shop validators ----------------------------------------------------
|
|
56
121
|
|
|
57
122
|
// asciiUpperLetters(s, label, n?) — exactly `n` (default 3) ASCII
|
|
@@ -157,45 +222,121 @@ function hostLabel(host, label) {
|
|
|
157
222
|
return h;
|
|
158
223
|
}
|
|
159
224
|
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
225
|
+
// firstCodepointThreat(s, policy?) — the single decision both the throwing
|
|
226
|
+
// screen (freeText) and the drop-silent one (hasCodepointThreat) run, so a
|
|
227
|
+
// field guarded one way and a field guarded the other can never disagree
|
|
228
|
+
// about what "dangerous" means. Returns a short reason string naming the
|
|
229
|
+
// class, or null when the text is clean. The reason is the clause that
|
|
230
|
+
// follows the field name in a refusal ("contains a null byte"), so a caller
|
|
231
|
+
// building a message just joins the two.
|
|
232
|
+
//
|
|
233
|
+
// Policies, each "reject" to enable and any other value to allow:
|
|
234
|
+
// bidi — default "reject". Bidi OVERRIDES and ISOLATES only
|
|
235
|
+
// (U+202A-202E, U+2066-2069) — the codepoints that reorder a
|
|
236
|
+
// run of text (CVE-2021-42574).
|
|
237
|
+
// bidiMarks — default "reject". U+200E / U+200F / U+061C, the direction
|
|
238
|
+
// MARKS. These are legitimate in NATURAL-LANGUAGE text — an
|
|
239
|
+
// Arabic postal address or a Hebrew gift message carries one
|
|
240
|
+
// wherever a Latin word or number sits inside the line — and
|
|
241
|
+
// illegitimate in a machine-readable value, where the only
|
|
242
|
+
// reason for an invisible character is to make the value read
|
|
243
|
+
// as something it is not. Neither case is the majority, so
|
|
244
|
+
// the default is chosen by how each mistake fails: refusing a
|
|
245
|
+
// mark that belonged is reported by the customer it turned
|
|
246
|
+
// away, while accepting one that did not is silent. A prose
|
|
247
|
+
// field therefore opts OUT with `bidiMarks: "allow"`.
|
|
248
|
+
// nullByte — default "reject". U+0000.
|
|
249
|
+
// control — default "reject". C0 controls + U+007F DELETE. Tab, LF and
|
|
250
|
+
// CR are ALLOWED here: a multi-line body legitimately has
|
|
251
|
+
// them. Use singleLine for a field that must not.
|
|
252
|
+
// singleLine — off by default. Tab / LF / CR, plus U+2028 LINE SEPARATOR
|
|
253
|
+
// and U+2029 PARAGRAPH SEPARATOR, which break a line without
|
|
254
|
+
// being C0 at all. For a value rendered on one line — a URL,
|
|
255
|
+
// a subject, a title, a header value.
|
|
256
|
+
// zeroWidth — off by default. Zero-width / invisible formatting. Includes
|
|
257
|
+
// U+00AD SOFT HYPHEN, which text pasted out of a hyphenating
|
|
258
|
+
// word processor or PDF carries invisibly.
|
|
259
|
+
// mixedScript — off by default. Confusable / homograph across writing
|
|
260
|
+
// systems, narrowed by policy.allowedScripts.
|
|
261
|
+
function firstCodepointThreat(s, policy) {
|
|
175
262
|
policy = policy || {};
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
var controlP = policy.control || "reject";
|
|
179
|
-
if (bidiP === "reject" && codepointClass.BIDI_RE.test(s)) {
|
|
180
|
-
throw new TypeError(name + " contains a Unicode bidi override (CVE-2021-42574 Trojan Source)");
|
|
263
|
+
if ((policy.bidi || "reject") === "reject" && BIDI_OVERRIDE_RE.test(s)) {
|
|
264
|
+
return "contains a Unicode bidi override (CVE-2021-42574 Trojan Source)";
|
|
181
265
|
}
|
|
182
|
-
if (
|
|
183
|
-
|
|
266
|
+
if ((policy.bidiMarks || "reject") === "reject" && BIDI_MARK_RE.test(s)) {
|
|
267
|
+
return "contains a Unicode bidi direction mark";
|
|
184
268
|
}
|
|
185
|
-
if (
|
|
186
|
-
|
|
269
|
+
if ((policy.nullByte || "reject") === "reject" &&
|
|
270
|
+
s.indexOf(codepointClass.NULL_BYTE) !== -1) {
|
|
271
|
+
return "contains a null byte";
|
|
187
272
|
}
|
|
188
|
-
if (policy.
|
|
189
|
-
|
|
273
|
+
if ((policy.control || "reject") === "reject" &&
|
|
274
|
+
(codepointClass.C0_CTRL_RE.test(s) || DEL_RE.test(s))) {
|
|
275
|
+
return "contains a C0 control character";
|
|
276
|
+
}
|
|
277
|
+
if (policy.singleLine === "reject" && LINE_BREAK_RE.test(s)) {
|
|
278
|
+
return "contains a line break or tab but must be a single line";
|
|
279
|
+
}
|
|
280
|
+
if (policy.zeroWidth === "reject" &&
|
|
281
|
+
(codepointClass.ZERO_WIDTH_RE.test(s) || INVISIBLE_OP_RE.test(s))) {
|
|
282
|
+
return "contains a zero-width / invisible formatting character";
|
|
190
283
|
}
|
|
191
284
|
if (policy.mixedScript === "reject") {
|
|
192
285
|
var scripts = codepointClass.detectMixedScripts(s, policy.allowedScripts);
|
|
193
286
|
if (scripts) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
287
|
+
return "mixes writing systems (" + scripts.join(", ") +
|
|
288
|
+
") — possible confusable / homograph";
|
|
197
289
|
}
|
|
198
290
|
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// hasCodepointThreat(s, policy?) — the non-throwing sibling of freeText, for
|
|
295
|
+
// the defensive readers that DROP a bad value rather than refusing the
|
|
296
|
+
// request (a link URL inside a rendered body, a header line being assembled).
|
|
297
|
+
// A non-string is dangerous by definition here: the caller is about to render
|
|
298
|
+
// or transmit it. Returns true when the text must not be used.
|
|
299
|
+
function hasCodepointThreat(s, policy) {
|
|
300
|
+
if (typeof s !== "string") return true;
|
|
301
|
+
return firstCodepointThreat(s, policy) !== null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// scrubInvisible(s) — remove every bidi override and invisible-formatting
|
|
305
|
+
// codepoint and return the cleaned string. The third disposition, alongside
|
|
306
|
+
// throwing and dropping: for a value that must be ACCEPTED whatever it
|
|
307
|
+
// contains, because refusing it would hand an attacker a denial-of-service
|
|
308
|
+
// against a legitimate user (a search query is the standing example — a
|
|
309
|
+
// pasted BOM must not cost the shopper their search).
|
|
310
|
+
//
|
|
311
|
+
// Composes the framework's strip pass and then removes the codepoints the
|
|
312
|
+
// catalog does not carry yet, so what this deletes is exactly what
|
|
313
|
+
// hasCodepointThreat would have objected to under zeroWidth: "reject".
|
|
314
|
+
// Control characters are deliberately NOT touched: a caller stripping those
|
|
315
|
+
// usually wants to substitute a space to avoid welding two words together,
|
|
316
|
+
// which is its own decision.
|
|
317
|
+
function scrubInvisible(s) {
|
|
318
|
+
if (typeof s !== "string") return s;
|
|
319
|
+
var out = codepointClass.applyCharStripPolicies(s, {
|
|
320
|
+
bidiPolicy: "strip",
|
|
321
|
+
zeroWidthPolicy: "strip",
|
|
322
|
+
tagsPolicy: "strip",
|
|
323
|
+
});
|
|
324
|
+
return out.replace(INVISIBLE_OP_RE_G, "");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// freeText(s, label, policy?) — the dangerous-codepoint screen for an
|
|
328
|
+
// UNCONSTRAINED UTF-8 text field (customer note, review body, gift
|
|
329
|
+
// message, product Q&A). Unlike the ASCII-allowlist validators above,
|
|
330
|
+
// these fields legitimately carry arbitrary letters, so the codepoint
|
|
331
|
+
// catalog is the right backing. Throws TypeError on a refused codepoint;
|
|
332
|
+
// returns the input on success. See firstCodepointThreat for the policies.
|
|
333
|
+
function freeText(s, label, policy) {
|
|
334
|
+
var name = label || "value";
|
|
335
|
+
if (typeof s !== "string") {
|
|
336
|
+
throw new TypeError(name + " must be a string");
|
|
337
|
+
}
|
|
338
|
+
var reason = firstCodepointThreat(s, policy);
|
|
339
|
+
if (reason) throw new TypeError(name + " " + reason);
|
|
199
340
|
return s;
|
|
200
341
|
}
|
|
201
342
|
|
|
@@ -213,9 +354,12 @@ module.exports = {
|
|
|
213
354
|
applyCharStripPolicies: codepointClass.applyCharStripPolicies,
|
|
214
355
|
|
|
215
356
|
// Shop validators.
|
|
216
|
-
asciiUpperLetters:
|
|
217
|
-
currencyCode:
|
|
218
|
-
slugLabel:
|
|
219
|
-
hostLabel:
|
|
220
|
-
freeText:
|
|
357
|
+
asciiUpperLetters: asciiUpperLetters,
|
|
358
|
+
currencyCode: currencyCode,
|
|
359
|
+
slugLabel: slugLabel,
|
|
360
|
+
hostLabel: hostLabel,
|
|
361
|
+
freeText: freeText,
|
|
362
|
+
hasCodepointThreat: hasCodepointThreat,
|
|
363
|
+
firstCodepointThreat: firstCodepointThreat,
|
|
364
|
+
scrubInvisible: scrubInvisible,
|
|
221
365
|
};
|
package/lib/theme-assets.js
CHANGED
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
*/
|
|
85
85
|
|
|
86
86
|
var b = require("./vendor/blamejs");
|
|
87
|
+
var textGuard = require("./text-guard");
|
|
87
88
|
|
|
88
89
|
var MAX_SLUG_LEN = 80;
|
|
89
90
|
var MAX_THEME_SLUG_LEN = 80;
|
|
@@ -130,14 +131,10 @@ var SHA3_512_HEX_RE = /^[0-9a-f]{128}$/;
|
|
|
130
131
|
var CONTENT_TYPE_RE = /^[A-Za-z0-9!#$&^_.+-]+\/[A-Za-z0-9!#$&^_.+-]+(?:\s*;\s*[A-Za-z0-9!#$&^_.+-]+\s*=\s*(?:"[^"\x00-\x1f]*"|[A-Za-z0-9!#$&^_.+-]+))*$/;
|
|
131
132
|
|
|
132
133
|
// Refuse C0 control bytes + DEL in operator-authored text fields.
|
|
133
|
-
var CONTROL_BYTE_LINE_RE = /[\x00-\x1f\x7f]/;
|
|
134
134
|
|
|
135
135
|
// Zero-width / direction-override family — mirrors the rest of the
|
|
136
136
|
// shop primitives. Spelled with \u-escapes so ESLint's
|
|
137
137
|
// no-irregular-whitespace stays happy.
|
|
138
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
139
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
140
|
-
);
|
|
141
138
|
|
|
142
139
|
var MS_PER_DAY = b.constants.TIME.days(1);
|
|
143
140
|
|
|
@@ -181,9 +178,7 @@ function _contentType(s) {
|
|
|
181
178
|
MAX_CONTENT_TYPE_LEN + " chars"
|
|
182
179
|
);
|
|
183
180
|
}
|
|
184
|
-
|
|
185
|
-
throw new TypeError("themeAssets: content_type contains control bytes");
|
|
186
|
-
}
|
|
181
|
+
textGuard.freeText(s, "themeAssets: content_type", { singleLine: "reject" });
|
|
187
182
|
if (!CONTENT_TYPE_RE.test(s)) {
|
|
188
183
|
throw new TypeError(
|
|
189
184
|
"themeAssets: content_type " + JSON.stringify(s) +
|
|
@@ -230,9 +225,7 @@ function _sourceUrl(s) {
|
|
|
230
225
|
MAX_SOURCE_URL_LEN + " chars"
|
|
231
226
|
);
|
|
232
227
|
}
|
|
233
|
-
|
|
234
|
-
throw new TypeError("themeAssets: source_url contains control / zero-width bytes");
|
|
235
|
-
}
|
|
228
|
+
textGuard.freeText(s, "themeAssets: source_url", { singleLine: "reject", zeroWidth: "reject" });
|
|
236
229
|
if (s.charCodeAt(0) === 47 /* "/" */) {
|
|
237
230
|
if (s.length > 1 && s.charCodeAt(1) === 47) {
|
|
238
231
|
throw new TypeError(
|
|
@@ -264,12 +257,7 @@ function _altText(s) {
|
|
|
264
257
|
MAX_ALT_TEXT_LEN + " chars (or null)"
|
|
265
258
|
);
|
|
266
259
|
}
|
|
267
|
-
|
|
268
|
-
throw new TypeError("themeAssets: alt_text contains control bytes (incl. CR/LF)");
|
|
269
|
-
}
|
|
270
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
271
|
-
throw new TypeError("themeAssets: alt_text contains zero-width / direction-override characters");
|
|
272
|
-
}
|
|
260
|
+
textGuard.freeText(s, "themeAssets: alt_text", { singleLine: "reject", zeroWidth: "reject", bidiMarks: "allow" });
|
|
273
261
|
return s;
|
|
274
262
|
}
|
|
275
263
|
|
package/lib/translations.js
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
*/
|
|
59
59
|
|
|
60
60
|
var b = require("./vendor/blamejs");
|
|
61
|
+
var textGuard = require("./text-guard");
|
|
61
62
|
|
|
62
63
|
var MAX_KIND_LEN = 64;
|
|
63
64
|
var MAX_RESOURCE_ID_LEN = 128;
|
|
@@ -90,14 +91,10 @@ var LOCALE_RE = /^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;
|
|
|
90
91
|
// allowed (operators author multi-line page bodies) — the rest of
|
|
91
92
|
// the C0 range would only reach the storefront as a smuggling
|
|
92
93
|
// payload or a copy-paste accident from a terminal.
|
|
93
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
94
94
|
|
|
95
95
|
// Zero-width / direction-override family. Spelled with \u-escapes
|
|
96
96
|
// so ESLint's no-irregular-whitespace stays happy. Same defence the
|
|
97
97
|
// rest of the shop uses on operator-authored strings.
|
|
98
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
99
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
100
|
-
);
|
|
101
98
|
|
|
102
99
|
// ---- validators ---------------------------------------------------------
|
|
103
100
|
|
|
@@ -193,10 +190,10 @@ function _value(s, fnLabel) {
|
|
|
193
190
|
if (s.length > MAX_VALUE_LEN) {
|
|
194
191
|
throw new TypeError(fnLabel + ": value must be <= " + MAX_VALUE_LEN + " characters");
|
|
195
192
|
}
|
|
196
|
-
if (
|
|
193
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
197
194
|
throw new TypeError(fnLabel + ": value contains forbidden control bytes");
|
|
198
195
|
}
|
|
199
|
-
if (
|
|
196
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
200
197
|
throw new TypeError(fnLabel + ": value contains zero-width / direction-override characters");
|
|
201
198
|
}
|
|
202
199
|
return s;
|
package/lib/trust-badges.js
CHANGED
|
@@ -116,13 +116,9 @@ var EVENT_TYPES = Object.freeze(["impression", "click"]);
|
|
|
116
116
|
var SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
|
|
117
117
|
|
|
118
118
|
// Refuse C0 control bytes + DEL in single-line operator strings.
|
|
119
|
-
var CONTROL_BYTE_LINE_RE = /[\x00-\x1f\x7f]/;
|
|
120
119
|
|
|
121
120
|
// Zero-width / direction-override family — same catalogue as the
|
|
122
121
|
// other operator-authored primitives.
|
|
123
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
124
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
125
|
-
);
|
|
126
122
|
|
|
127
123
|
// Hash namespace — opaque per-primitive constant so a session_id_hash
|
|
128
124
|
// emitted by trustBadges can't collide with one emitted by analytics
|
|
@@ -142,6 +138,7 @@ var ALLOWED_PATCH_COLUMNS = Object.freeze([
|
|
|
142
138
|
]);
|
|
143
139
|
|
|
144
140
|
var b = require("./vendor/blamejs");
|
|
141
|
+
var textGuard = require("./text-guard");
|
|
145
142
|
|
|
146
143
|
// ---- validators ---------------------------------------------------------
|
|
147
144
|
|
|
@@ -184,10 +181,10 @@ function _line(s, label, maxLen) {
|
|
|
184
181
|
if (typeof s !== "string" || !s.length || s.length > maxLen) {
|
|
185
182
|
throw new TypeError("trustBadges: " + label + " must be a non-empty string ≤ " + maxLen + " chars");
|
|
186
183
|
}
|
|
187
|
-
if (
|
|
184
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
188
185
|
throw new TypeError("trustBadges: " + label + " contains control bytes (incl. CR/LF)");
|
|
189
186
|
}
|
|
190
|
-
if (
|
|
187
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
191
188
|
throw new TypeError("trustBadges: " + label + " contains zero-width / direction-override characters");
|
|
192
189
|
}
|
|
193
190
|
return s;
|
|
@@ -201,7 +198,7 @@ function _httpsOrRootUrl(s, label, maxLen) {
|
|
|
201
198
|
if (typeof s !== "string" || !s.length || s.length > maxLen) {
|
|
202
199
|
throw new TypeError("trustBadges: " + label + " must be a non-empty string ≤ " + maxLen + " chars");
|
|
203
200
|
}
|
|
204
|
-
if (
|
|
201
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject", zeroWidth: "reject" })) {
|
|
205
202
|
throw new TypeError("trustBadges: " + label + " contains control / zero-width bytes");
|
|
206
203
|
}
|
|
207
204
|
if (s.charCodeAt(0) === 47 /* "/" */) {
|
package/lib/vendor/MANIFEST.json
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"_about": "blamejs.shop vendors a single framework — blamejs — which itself bundles every server-side crypto/identity dependency. The transitive packages blamejs ships are surfaced in its own MANIFEST.json at lib/vendor/blamejs/lib/vendor/MANIFEST.json — Trivy / Grype rely on that nested data for CVE attribution.",
|
|
4
4
|
"packages": {
|
|
5
5
|
"blamejs": {
|
|
6
|
-
"version": "0.18.
|
|
7
|
-
"tag": "v0.18.
|
|
6
|
+
"version": "0.18.24",
|
|
7
|
+
"tag": "v0.18.24",
|
|
8
8
|
"license": "Apache-2.0",
|
|
9
9
|
"author": "blamejs contributors",
|
|
10
10
|
"source": "https://github.com/blamejs/blamejs",
|
|
@@ -599,15 +599,15 @@
|
|
|
599
599
|
"package.json": "lib/vendor/blamejs/package.json",
|
|
600
600
|
"sbom.cdx.json": "lib/vendor/blamejs/sbom.cdx.json"
|
|
601
601
|
},
|
|
602
|
-
"bundler": "signed release tarball blamejs-core-0.18.
|
|
603
|
-
"bundledAt": "2026-08-
|
|
602
|
+
"bundler": "signed release tarball blamejs-core-0.18.24.tgz from github.com/blamejs/blamejs — SHA3-512 digest and ML-DSA-65 signature verified against keys/vendor-blamejs-pqc-pub.json before unpacking",
|
|
603
|
+
"bundledAt": "2026-08-11",
|
|
604
604
|
"hashes": {
|
|
605
|
-
"CHANGELOG.md": "sha256:
|
|
605
|
+
"CHANGELOG.md": "sha256:2ac9860b3cc5e84c9103557a9610d3e60af5f9a3f648d22aa127c55f9c72cf0e",
|
|
606
606
|
"LICENSE": "sha256:812075be5abe785284e823a97d09b5d1515b0221e3128059e1f5065de3c907d5",
|
|
607
607
|
"LTS-CALENDAR.md": "sha256:fbdf074ca0d9544bf10fff87c8947ba38aa5ac0500267796dc8f0c92822aacbe",
|
|
608
608
|
"MIGRATING.md": "sha256:7bee6f55c28f017d4083132dbe45e0e63bcdb7ad3086b95b02551eaa4cc052bc",
|
|
609
|
-
"NOTICE": "sha256:
|
|
610
|
-
"README.md": "sha256:
|
|
609
|
+
"NOTICE": "sha256:979a65f58486faf2defacd821fa97476149a3a4efe1ddea8f6b53b3283175fe5",
|
|
610
|
+
"README.md": "sha256:4ea822a1e856e860ea6d24b01a6dbb34252d519f78707eccdcb0a6de3d3775e8",
|
|
611
611
|
"bin/blamejs.js": "sha256:59798e63ad43c0afdf03ab88aaae105fa1f525c2703d80903eee48717db567c4",
|
|
612
612
|
"index.js": "sha256:2d2c81c19e4177450b701dba05f582a8b6ef9581d202f01ec04d912556266a2f",
|
|
613
613
|
"lib/_test/crypto-fixtures.js": "sha256:16055e38d3c1a3065735e50fe43e81de1ed5d90cf72d7fb90ac263d07deb7ced",
|
|
@@ -1019,7 +1019,7 @@
|
|
|
1019
1019
|
"lib/network-nts.js": "sha256:947e1887f339ef1bb20891d5c9531e2969653b162886b576c9b8a723d6b62002",
|
|
1020
1020
|
"lib/network-proxy.js": "sha256:392e79ddc58ae7125cd7a8abec872df50ffee76170ea40ff6847d581b2a405a3",
|
|
1021
1021
|
"lib/network-smtp-policy.js": "sha256:b8b88766be9dd1a0e4dfe60b21f97fe8b166d76328ecbbbb4e1349bfb2335e95",
|
|
1022
|
-
"lib/network-tls.js": "sha256:
|
|
1022
|
+
"lib/network-tls.js": "sha256:0a2b52cf7d9e35d988ba0a6d3d0afd22536c7f68ff047f24981e0732964a4fa3",
|
|
1023
1023
|
"lib/network-tsig.js": "sha256:233b008815bbbd60e6914e4a74b41de0d9136c27331bf00827ca4a983ddd29e2",
|
|
1024
1024
|
"lib/network.js": "sha256:a4a4bd54396c0e162f1d5469d4d3e264a9f4537179446843589674e551ac1f29",
|
|
1025
1025
|
"lib/nis2-report.js": "sha256:d54937f1fe996c5316756cfcf9095c67c8f955621c199079abb87862a82afd75",
|
|
@@ -1158,10 +1158,10 @@
|
|
|
1158
1158
|
"lib/vc.js": "sha256:2f0e2a32bb52f53e4c38df64a2a658665cf5e442b56882d3b3bbdf31c7124132",
|
|
1159
1159
|
"lib/vendor-data.js": "sha256:365b0dcca1198e4fad647b3873b82702fcf90a5ee2d8c7f25c78864cadb7d717",
|
|
1160
1160
|
"lib/vendor/.vendor-data-pubkey": "sha256:73a935b8c72f55d821c22ddb8188453bab324f80d229d033a0a1549819a1f096",
|
|
1161
|
-
"lib/vendor/MANIFEST.json": "sha256:
|
|
1161
|
+
"lib/vendor/MANIFEST.json": "sha256:75576b6fbcb07383d382c94fbf3fb317703d5cb4bf838b55801b93d7ead3de08",
|
|
1162
1162
|
"lib/vendor/bimi-trust-anchors.data.js": "sha256:aa7a4d33b65a68422a2a2c1670177689f66fdcaa08bd2514d78798b827bd1608",
|
|
1163
1163
|
"lib/vendor/bimi-trust-anchors.pem": "sha256:81ff9f5ab3c9774132c845684e783be95cf73146f8b670d964105f0a3765b4b4",
|
|
1164
|
-
"lib/vendor/blamejs-pki.cjs": "sha256:
|
|
1164
|
+
"lib/vendor/blamejs-pki.cjs": "sha256:96a873b733c95b53b8e2ced065a82192b6acabcede312cf68a45a128857cc3bc",
|
|
1165
1165
|
"lib/vendor/browser/noble-ciphers.mjs": "sha256:0ffd91540bcb586a29b56e52ee1c29df69097b50776beb4036a07558f7a4e12e",
|
|
1166
1166
|
"lib/vendor/browser/noble-hashes.mjs": "sha256:dfe4b7ae3c9880e388c8da4b68f44742b229b53afacd1e674179527e33da62b0",
|
|
1167
1167
|
"lib/vendor/browser/noble-post-quantum.mjs": "sha256:cae1d5bbdc7184b202b6ca68df6e1db7b0d0f668c77809ded189ca7f271accc9",
|
|
@@ -1188,8 +1188,8 @@
|
|
|
1188
1188
|
"lib/ws-client.js": "sha256:348d01cd552fed583f05944d1853f71b0119105cb067c2e607b19ec9e6f47aee",
|
|
1189
1189
|
"lib/x509-chain.js": "sha256:c2b5544202601a50230c7c652ff028e73e9d29a34d58db349beb5b5164b7b4d5",
|
|
1190
1190
|
"lib/xml-c14n.js": "sha256:6239db09b021f032a78aa101fe295238db4ad91acc93ae13eef7d14efd797b94",
|
|
1191
|
-
"package.json": "sha256:
|
|
1192
|
-
"sbom.cdx.json": "sha256:
|
|
1191
|
+
"package.json": "sha256:ab52ca260fa6408d4910a3d9a726d442e61127e883e920c965e7ef31c9319a0d",
|
|
1192
|
+
"sbom.cdx.json": "sha256:b47182cff49c56cc0534ba835f62bd8ddebcb659ae3d693bbfc1f4b059a427c7"
|
|
1193
1193
|
}
|
|
1194
1194
|
}
|
|
1195
1195
|
}
|
|
@@ -8,6 +8,38 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.18.x
|
|
10
10
|
|
|
11
|
+
- v0.18.24 (2026-08-11) — **A scheduler test waited for the wrong task, so a loaded machine could fail the release pipeline on a race in the test rather than a defect in the framework.** The suite covering scheduled-task success and failure registered two tasks — one that returns and one that throws — then waited for the aggregate counters to show a fire and an error before asserting that the succeeding task had actually run. Both counters are totals across tasks, so the throwing task alone satisfied the wait. On an unloaded machine the two first-fires land together and it never mattered; on a loaded CI runner they separate far enough that the wait returned while the succeeding task had not run yet, and the assertion failed.
|
|
12
|
+
|
|
13
|
+
The wait now uses the same counter the assertion does. Nothing in the shipped framework changes; this is the release pipeline being made to fail only for real reasons. **Fixed:** *The scheduler suite waits for the task it asserts on* — `aggregate.totalFires` and `aggregate.withErrors` both count across every registered task, so a wait for "at least one fire and at least one error" is satisfied by a single failing task. The assertion that followed checked a counter incremented inside the succeeding task's body, which that wait says nothing about.
|
|
14
|
+
|
|
15
|
+
This is the general shape worth naming: a poll-until-ready is only as good as the condition it polls, and a condition that is merely correlated with the assertion is a race waiting for a slow machine. The wait now polls the success counter itself, alongside the aggregate error count, so it cannot return before the thing being asserted is true.
|
|
16
|
+
|
|
17
|
+
- v0.18.23 (2026-08-10) — **A Certificate Transparency consistency proof was checked against the new tree but not against the tree you pinned — unless your pinned size happened to be a power of two.** `b.network.tls.ct.verifyConsistency` answers one question: is the tree I pinned earlier still a prefix of the tree this log is showing me now? RFC 9162 §2.1.4.2 answers it by rebuilding BOTH roots from the proof — the first tree's and the second's — and requiring both to match what the operator holds. The implementation rebuilt only the second. The pinned root reached the computation solely as a seed in the branch taken when the first tree is a complete subtree, so for a pinned size of 4, 8 or 16 it was checked by accident, and for 3, 5, 6, 7, 9 and every other size it was not looked at at all. A proof describing an entirely different first tree of the same size verified.
|
|
18
|
+
|
|
19
|
+
That is the whole premise of pinning, and it was being taken on trust. Both roots are now rebuilt and both are checked; a proof that does not connect to the pinned tree is refused with `first-root-mismatch`.
|
|
20
|
+
|
|
21
|
+
Two smaller defects came out of the same work. A tree is consistent with itself and RFC 9162 says so with an empty proof, which the walk refused for any size that was not a power of two — so an operator re-checking an unchanged STH was told their log was inconsistent. And a proof carrying entries beyond the root was accepted with the remainder ignored, where the sibling inclusion-proof walk refuses exactly that. **Changed:** *Vendored `@blamejs/pki` 0.4.13 → 0.4.14* — The PKI toolkit behind `b.mtlsCa` now wipes every key-establishment secret it allocates once that secret stops being needed, classical as well as post-quantum, and reaches raw key material through a single path that clears the copy it hands out. A password supplied as a string or `Uint8Array` is encoded into a buffer the toolkit allocated — a credential copy — and that copy is cleared once the key derivation has consumed it; a caller-supplied `Buffer` is borrowed and left untouched. Public API behaviour is unchanged. **Fixed:** *A tree is consistent with itself* — RFC 9162 §2.1.4 gives the proof for two equal tree sizes as the empty list. The verification walk in §2.1.4.2 is written for a first tree strictly smaller than the second, and running it on equal sizes made it look for a sibling the definition says is not there — so it refused. Not always: for a power-of-two size the index bits shift down to zero on their own and the walk short-circuits, so the failure appeared at 3, 5, 6, 7, 9 and not at 4, 8, 16.
|
|
22
|
+
|
|
23
|
+
An operator polling a log that has not grown since their last check got told the log was inconsistent, intermittently by tree size. A false alarm on a security check is worse than a quiet one, because it is the alarm that teaches you to ignore the alarm. Equal sizes now verify on an empty proof and refuse a non-empty one. · *A consistency proof must be fully consumed* — Entries past the point the walk stopped were ignored rather than refused, while the inclusion-proof walk beside it refuses an audit path with entries beyond the root. Nothing was forgeable through it — the computed root still had to match — but a proof carrying material the verifier never looked at is not the proof the operator believes was checked, and the two walkers disagreeing about what well-formed means is its own defect. Both now refuse a tail. **Security:** *The consistency proof must connect to the pinned first root* — A consistency check has two halves: the proof rebuilds the new tree's root, which must match the STH the log just served, and it rebuilds the old tree's root, which must match the STH the operator pinned. Only the first half was enforced.
|
|
24
|
+
|
|
25
|
+
The pinned root was used as the walk's seed when the first tree happened to be a complete subtree — true exactly when the pinned size is a power of two — and in that case the final root comparison carried it along. At every other size the walk seeded from the proof itself and the supplied root was never read. A log could serve a proof for some other first tree of the same size and it verified; so did a pinned root of arbitrary bytes.
|
|
26
|
+
|
|
27
|
+
The exposure is not that a log can forge a tree, but that pinning stopped detecting the thing it exists to detect: a log that quietly rewrote history before the point you pinned would still have passed, at 11 of the 16 first-sizes below 17. Both roots are now rebuilt per RFC 9162 §2.1.4.2 and both are checked, with a distinct `first-root-mismatch` reason so a proof that is well-formed but describes the wrong tree does not read as a malformed one. **Detectors:** *Merkle proofs are tested against real trees* — All three defects were reachable because the tests built their fixtures by hand: four-leaf trees with hand-picked sibling values. A four-leaf tree is a perfect binary tree, so it never takes the uneven split the algorithm turns on, and hand-picked values can be chosen to make the answer come out right without describing any tree at all — one fixture asserted the incomplete-subtree branch with a first root of thirty-two identical bytes, which is exactly the input the missing check would have caught.
|
|
28
|
+
|
|
29
|
+
The suite now transcribes RFC 9162's recursive definitions separately from the framework's iterative walk, builds real trees, derives real proofs, and feeds every first-size/second-size pair up to seventeen leaves through the verifier before breaking them one way at a time. The two implementations share no structure, so agreement means something. A detector fires on a re-hand-rolled tree hash.
|
|
30
|
+
|
|
31
|
+
- v0.18.22 (2026-08-10) — **An OCSP response that declared one signature algorithm and carried another was accepted — the field steering the digest was never checked against the key doing the verifying.** `b.network.tls.ocsp.evaluate` read the responder's `signatureAlgorithm` OID to pick a digest, then handed the bytes to `node:crypto`, which selects RSA or ECDSA from the KEY rather than from that field. A response declaring `sha256WithRSAEncryption` while carrying an ECDSA signature therefore verified, and so did the reverse.
|
|
32
|
+
|
|
33
|
+
This is not a forgery path and was never exploitable: the issuer's private key is required either way, and a response signed by any other key is still refused. What it meant is that the declared algorithm and the algorithm actually verified were allowed to differ, with nothing in the result saying so. RFC 6960 §4.2.1 gives that field one job; the verifier now holds it to it, and a mismatch is refused with `tls/ocsp-sig-alg-key-mismatch`.
|
|
34
|
+
|
|
35
|
+
It surfaced while covering the refusal paths of the revocation checker — the half of a security primitive that only matters when something is wrong, and the half that had the least coverage. **Fixed:** *The revocation checker's refusal paths are covered* — A revocation check earns its cost only by saying NO for every reason it should, and the accept path is the part that gets exercised. Each refusal now has a test driving the real `evaluate` call with a response that is well-formed in every respect except the one under test: `certStatus` revoked (with and without a CRLReason) and unknown; a `responseStatus` that is not successful; a ResponseData carrying no responses at all; a nonce that does not echo and one absent when required; a signature that does not verify and one made by the wrong key; a CertID under an unrecognised hash algorithm and one naming a different issuer than the certificate under validation; a SingleResponse with no thisUpdate.
|
|
36
|
+
|
|
37
|
+
Both encodings of the nonce extension are covered — the RFC 8954 raw form and the RFC 6960 double-wrapped one — because a verifier that reads only one scores a live responder's echo as a mismatch and refuses a good response. **Security:** *The OCSP signatureAlgorithm must agree with the issuer key* — The verifier mapped the response's `signatureAlgorithm` OID to a digest — `sha256WithRSAEncryption` to SHA-256, `ecdsa-with-SHA384` to SHA-384, and so on — and then called `node:crypto.verify(digest, tbs, issuerKey, signature)`. Node decides RSA versus ECDSA from the key object, so the OID's algorithm half was read and discarded. An EC-signed response declaring RSA verified; an RSA-signed response declaring ECDSA verified.
|
|
38
|
+
|
|
39
|
+
Nothing was forgeable through it. Producing a signature that verifies under the issuer key still requires that private key, and the release ships a test asserting that a response signed by a different key is refused. The defect is that a field the verifier depends on to choose the digest was never validated against the key it verified with, so what a response claimed and what was actually checked could differ silently.
|
|
40
|
+
|
|
41
|
+
`evaluate` now requires the OID family and the issuer key type to agree, and refuses with `tls/ocsp-sig-alg-key-mismatch` when they do not. Responders that declare their own algorithm — every conformant one — are unaffected. **Detectors:** *one OCSP response builder for the test suite* — An OCSP response is layers of nested DER, so each suite that needed one grew its own assembler — three of them, each reaching only the shapes that suite had thought of. Between them they covered the accept path, staleness and the CertID issuer binding, and could not express the signature-algorithm disagreement above at all: no builder had a knob for it. A shared builder makes a new refusal shape an argument rather than a fourth copy, and a detector fires on a re-hand-rolled one.
|
|
42
|
+
|
|
11
43
|
- v0.18.21 (2026-08-10) — **An NTS or NTP query that cannot build its request left the datagram socket open — one leaked handle per failed query, for the life of the process.** `b.network.ntp.nts.querySingle` and `b.ntpCheck.querySingle` opened their UDP socket before building the request packet. Building it can fail — an AEAD suite the encoder does not recognise is the reachable case, and refusing it is the point — and the socket opened first had nobody left to close it once the exception left the Promise executor. The caller saw the right error either way, which is why this stayed invisible: nothing was wrong with the rejection, only with what it left behind. A scheduler polling a misconfigured time source accumulated one dead handle per attempt and never released any of them.
|
|
12
44
|
|
|
13
45
|
Both now build the request first and open the socket only once every step that can throw has succeeded, so there is nothing to leak rather than something each new failure path has to remember to undo. A synchronous refusal from `send()` routes through the same close-then-settle path.
|
|
@@ -77,7 +77,7 @@ Used for: WebAuthn / passkey registration + authentication response
|
|
|
77
77
|
server.cjs).
|
|
78
78
|
--------------------------------------------------------------------------------
|
|
79
79
|
Component: @blamejs/pki
|
|
80
|
-
Version: 0.4.
|
|
80
|
+
Version: 0.4.14
|
|
81
81
|
Source: https://github.com/blamejs/pki
|
|
82
82
|
License: Apache-2.0
|
|
83
83
|
Copyright: Copyright (c) blamejs contributors
|
|
@@ -321,7 +321,7 @@ All runtime dependencies are committed to the repo — no transitive npm install
|
|
|
321
321
|
| [`@noble/curves`](https://github.com/paulmillr/noble-curves) | 2.3.0 (bundles @noble/hashes 2.3.0) | [Paul Miller](https://github.com/paulmillr) | RFC 9497 Oblivious Pseudo-Random Function (OPRF / VOPRF / POPRF) over ristretto255 / P-256 / P-384 / P-521, behind `b.crypto.oprf` |
|
|
322
322
|
| [`@noble/post-quantum`](https://github.com/paulmillr/noble-post-quantum) | 0.7.0 (bundles @noble/hashes, @noble/curves, @noble/ciphers 2.3.0) | [Paul Miller](https://github.com/paulmillr) | Pure-JS FIPS 203 ML-KEM (`ml_kem_512` / `ml_kem_768` / `ml_kem_1024`), FIPS 204 ML-DSA (`ml_dsa_44/65/87`), FIPS 205 SLH-DSA (`slh_dsa_*`). First-class on both server-side and client-side via `b.pqcSoftware` — security-first defaults pin to the highest cat-5 levels (ML-KEM-1024, ML-DSA-87, SLH-DSA-SHAKE-256f); interoperable with Node's built-in WebCrypto ML-KEM that `b.crypto.encrypt` / `b.middleware.apiEncrypt` use. A browser (ESM) build ships beside it carrying the KEM suites only — a client half encapsulates and does not sign |
|
|
323
323
|
| [`@simplewebauthn/server`](https://github.com/MasterKale/SimpleWebAuthn) | 13.3.2 | [Matthew Miller](https://github.com/MasterKale) | WebAuthn / passkey verification |
|
|
324
|
-
| [`@blamejs/pki`](https://github.com/blamejs/pki) | 0.4.
|
|
324
|
+
| [`@blamejs/pki`](https://github.com/blamejs/pki) | 0.4.14 | [blamejs](https://github.com/blamejs) | Zero-dependency pure-JS X.509 / CRL / PKCS#12 / CSR / CMS toolkit backing `b.mtlsCa` — ML-DSA-87 (FIPS 204) post-quantum + ECDSA-P384 cert signing, PBMAC1 PKCS#12 packaging, chain validation (no openssl CLI) |
|
|
325
325
|
| [`SecLists` 10k-most-common.txt](https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10k-most-common.txt) | master snapshot | [Daniel Miessler / SecLists contributors](https://github.com/danielmiessler/SecLists) (CC-BY-3.0) | Top-10000 common-password dictionary read by `b.auth.password.policy()` for the NIST 800-63B §5.1.1.2 "previously breached" check |
|
|
326
326
|
| [`prismjs`](https://prismjs.com/) | 1.30.0 | [Lea Verou + contributors](https://github.com/PrismJS/prism) | Syntax highlighting in the example wiki's code blocks (browser-side) |
|
|
327
327
|
|