@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
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
*/
|
|
70
70
|
|
|
71
71
|
var b = require("./vendor/blamejs");
|
|
72
|
+
var textGuard = require("./text-guard");
|
|
72
73
|
var C = b.constants;
|
|
73
74
|
|
|
74
75
|
// ---- constants ----------------------------------------------------------
|
|
@@ -114,10 +115,6 @@ var RETRY_BACKOFF_MS = [
|
|
|
114
115
|
|
|
115
116
|
var SLUG_RE = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
116
117
|
var LOCALE_RE = /^[A-Za-z]{2,8}(?:[-_][A-Za-z0-9]{2,8}){0,3}$/;
|
|
117
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
118
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
119
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
120
|
-
);
|
|
121
118
|
|
|
122
119
|
// ---- validators ---------------------------------------------------------
|
|
123
120
|
|
|
@@ -162,12 +159,7 @@ function _credentials(s) {
|
|
|
162
159
|
if (s.length > MAX_CREDENTIALS_LEN) {
|
|
163
160
|
throw new TypeError("pushNotifications: credentials must be <= " + MAX_CREDENTIALS_LEN + " characters");
|
|
164
161
|
}
|
|
165
|
-
|
|
166
|
-
throw new TypeError("pushNotifications: credentials contains control bytes");
|
|
167
|
-
}
|
|
168
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
169
|
-
throw new TypeError("pushNotifications: credentials contains zero-width / bidi-override codepoints");
|
|
170
|
-
}
|
|
162
|
+
textGuard.freeText(s, "pushNotifications: credentials", { zeroWidth: "reject" });
|
|
171
163
|
return s;
|
|
172
164
|
}
|
|
173
165
|
|
|
@@ -178,12 +170,7 @@ function _deviceToken(s) {
|
|
|
178
170
|
if (s.length > MAX_TOKEN_LEN) {
|
|
179
171
|
throw new TypeError("pushNotifications: device_token must be <= " + MAX_TOKEN_LEN + " characters");
|
|
180
172
|
}
|
|
181
|
-
|
|
182
|
-
throw new TypeError("pushNotifications: device_token contains control bytes");
|
|
183
|
-
}
|
|
184
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
185
|
-
throw new TypeError("pushNotifications: device_token contains zero-width / bidi-override codepoints");
|
|
186
|
-
}
|
|
173
|
+
textGuard.freeText(s, "pushNotifications: device_token", { zeroWidth: "reject" });
|
|
187
174
|
return s;
|
|
188
175
|
}
|
|
189
176
|
|
|
@@ -208,12 +195,7 @@ function _title(s) {
|
|
|
208
195
|
if (s.length > MAX_TITLE_LEN) {
|
|
209
196
|
throw new TypeError("pushNotifications: title must be <= " + MAX_TITLE_LEN + " characters");
|
|
210
197
|
}
|
|
211
|
-
|
|
212
|
-
throw new TypeError("pushNotifications: title contains control bytes");
|
|
213
|
-
}
|
|
214
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
215
|
-
throw new TypeError("pushNotifications: title contains zero-width / bidi-override codepoints");
|
|
216
|
-
}
|
|
198
|
+
textGuard.freeText(s, "pushNotifications: title", { zeroWidth: "reject", bidiMarks: "allow" });
|
|
217
199
|
return s;
|
|
218
200
|
}
|
|
219
201
|
|
|
@@ -224,12 +206,7 @@ function _body(s) {
|
|
|
224
206
|
if (s.length > MAX_BODY_LEN) {
|
|
225
207
|
throw new TypeError("pushNotifications: body must be <= " + MAX_BODY_LEN + " characters");
|
|
226
208
|
}
|
|
227
|
-
|
|
228
|
-
throw new TypeError("pushNotifications: body contains control bytes");
|
|
229
|
-
}
|
|
230
|
-
if (ZERO_WIDTH_RE.test(s)) {
|
|
231
|
-
throw new TypeError("pushNotifications: body contains zero-width / bidi-override codepoints");
|
|
232
|
-
}
|
|
209
|
+
textGuard.freeText(s, "pushNotifications: body", { zeroWidth: "reject", bidiMarks: "allow" });
|
|
233
210
|
return s;
|
|
234
211
|
}
|
|
235
212
|
|
|
@@ -267,7 +244,7 @@ function _reason(s, label) {
|
|
|
267
244
|
if (s.length > MAX_REASON_LEN) {
|
|
268
245
|
throw new TypeError("pushNotifications: " + label + " must be <= " + MAX_REASON_LEN + " characters");
|
|
269
246
|
}
|
|
270
|
-
if (
|
|
247
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
271
248
|
throw new TypeError("pushNotifications: " + label + " contains control / zero-width bytes");
|
|
272
249
|
}
|
|
273
250
|
return s;
|
|
@@ -280,9 +257,7 @@ function _providerMessageId(s) {
|
|
|
280
257
|
if (s.length > MAX_PROV_MSG_ID_LEN) {
|
|
281
258
|
throw new TypeError("pushNotifications: provider_message_id must be <= " + MAX_PROV_MSG_ID_LEN + " characters");
|
|
282
259
|
}
|
|
283
|
-
|
|
284
|
-
throw new TypeError("pushNotifications: provider_message_id contains control / zero-width bytes");
|
|
285
|
-
}
|
|
260
|
+
textGuard.freeText(s, "pushNotifications: provider_message_id", { zeroWidth: "reject" });
|
|
286
261
|
return s;
|
|
287
262
|
}
|
|
288
263
|
|
package/lib/pwa-manifest.js
CHANGED
|
@@ -168,12 +168,9 @@ var ICON_SIZES_RE = /^(?:any|\d+x\d+)(?:\s+(?:any|\d+x\d+))*$/;
|
|
|
168
168
|
// spec recommends (image/png / image/svg+xml / image/webp).
|
|
169
169
|
var ICON_TYPE_RE = /^[a-z]+\/[a-z0-9.+-]+$/;
|
|
170
170
|
|
|
171
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
172
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
173
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
174
|
-
);
|
|
175
171
|
|
|
176
172
|
var b = require("./vendor/blamejs");
|
|
173
|
+
var textGuard = require("./text-guard");
|
|
177
174
|
|
|
178
175
|
// ---- monotonic clock ---------------------------------------------------
|
|
179
176
|
//
|
|
@@ -202,10 +199,10 @@ function _string(s, label, max) {
|
|
|
202
199
|
if (s.length > max) {
|
|
203
200
|
throw new TypeError("pwaManifest: " + label + " must be <= " + max + " characters");
|
|
204
201
|
}
|
|
205
|
-
if (
|
|
202
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
206
203
|
throw new TypeError("pwaManifest: " + label + " contains control bytes");
|
|
207
204
|
}
|
|
208
|
-
if (
|
|
205
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
209
206
|
throw new TypeError("pwaManifest: " + label + " contains zero-width / direction-override characters");
|
|
210
207
|
}
|
|
211
208
|
return s;
|
|
@@ -270,7 +267,7 @@ function _url(s, label) {
|
|
|
270
267
|
if (s.length > MAX_URL_LEN) {
|
|
271
268
|
throw new TypeError("pwaManifest: " + label + " must be <= " + MAX_URL_LEN + " characters");
|
|
272
269
|
}
|
|
273
|
-
if (
|
|
270
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
274
271
|
throw new TypeError("pwaManifest: " + label + " contains control / zero-width bytes");
|
|
275
272
|
}
|
|
276
273
|
if (s.charCodeAt(0) === 47 /* "/" */) {
|
|
@@ -416,10 +413,8 @@ function _validateRuntimeRules(rules) {
|
|
|
416
413
|
MAX_URL_PATTERN_LEN + " characters"
|
|
417
414
|
);
|
|
418
415
|
}
|
|
419
|
-
if (
|
|
420
|
-
throw new TypeError(
|
|
421
|
-
"pwaManifest: runtime_rules[" + i + "].url_pattern contains control / zero-width bytes"
|
|
422
|
-
);
|
|
416
|
+
if (textGuard.hasCodepointThreat(r.url_pattern, { zeroWidth: "reject" })) {
|
|
417
|
+
throw new TypeError("pwaManifest: runtime_rules[" + i + "].url_pattern contains control / zero-width bytes");
|
|
423
418
|
}
|
|
424
419
|
_enum(r.strategy, ALLOWED_STRATEGIES, "runtime_rules[" + i + "].strategy");
|
|
425
420
|
if (seen[r.url_pattern]) {
|
package/lib/quotes.js
CHANGED
|
@@ -145,11 +145,6 @@ var VIEW_TOKEN_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
|
145
145
|
// are allowed inside multi-line free-text fields (message,
|
|
146
146
|
// operator_notes, line.notes); single-line fields (terms, reject /
|
|
147
147
|
// cancel reason, accepted_by) refuse them too.
|
|
148
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
149
|
-
var CONTROL_BYTE_MULTILINE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
150
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
151
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
152
|
-
);
|
|
153
148
|
|
|
154
149
|
var QUOTE_STATUSES = Object.freeze([
|
|
155
150
|
"requested", "responded", "accepted", "rejected",
|
|
@@ -160,6 +155,7 @@ var TERMINAL_STATUSES = Object.freeze([
|
|
|
160
155
|
]);
|
|
161
156
|
|
|
162
157
|
var b = require("./vendor/blamejs");
|
|
158
|
+
var textGuard = require("./text-guard");
|
|
163
159
|
|
|
164
160
|
// The quote lifecycle, modelled on b.fsm — the same composition the order
|
|
165
161
|
// primitive uses (lib/order.js#_getOrderFsm). The FSM is the single source of
|
|
@@ -316,10 +312,10 @@ function _optShortText(s, label, max) {
|
|
|
316
312
|
if (s.length > max) {
|
|
317
313
|
throw new TypeError("quotes: " + label + " must be <= " + max + " characters");
|
|
318
314
|
}
|
|
319
|
-
if (
|
|
315
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
320
316
|
throw new TypeError("quotes: " + label + " contains control bytes");
|
|
321
317
|
}
|
|
322
|
-
if (
|
|
318
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
323
319
|
throw new TypeError("quotes: " + label + " contains zero-width / direction-override bytes");
|
|
324
320
|
}
|
|
325
321
|
return s;
|
|
@@ -336,10 +332,10 @@ function _optLongText(s, label, max) {
|
|
|
336
332
|
if (s.length > max) {
|
|
337
333
|
throw new TypeError("quotes: " + label + " must be <= " + max + " characters");
|
|
338
334
|
}
|
|
339
|
-
if (
|
|
335
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
340
336
|
throw new TypeError("quotes: " + label + " contains control bytes");
|
|
341
337
|
}
|
|
342
|
-
if (
|
|
338
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
343
339
|
throw new TypeError("quotes: " + label + " contains zero-width / direction-override bytes");
|
|
344
340
|
}
|
|
345
341
|
return s;
|
package/lib/reviews.js
CHANGED
|
@@ -50,9 +50,9 @@ var REVIEW_ORDER_KEY = ["created_at:desc", "id:desc"];
|
|
|
50
50
|
var CUSTOMER_ORDER_KEY = ["created_at:desc", "id:desc"];
|
|
51
51
|
|
|
52
52
|
var ALLOWED_STATUSES = ["pending", "published", "rejected"];
|
|
53
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
54
53
|
|
|
55
54
|
var b = require("./vendor/blamejs");
|
|
55
|
+
var textGuard = require("./text-guard");
|
|
56
56
|
|
|
57
57
|
// ---- validators ---------------------------------------------------------
|
|
58
58
|
|
|
@@ -75,9 +75,7 @@ function _title(s) {
|
|
|
75
75
|
if (s.length > MAX_TITLE_LEN) {
|
|
76
76
|
throw new TypeError("reviews: title must be <= " + MAX_TITLE_LEN + " characters");
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
throw new TypeError("reviews: title contains control bytes");
|
|
80
|
-
}
|
|
78
|
+
textGuard.freeText(s, "reviews: title", { bidiMarks: "allow" });
|
|
81
79
|
return s;
|
|
82
80
|
}
|
|
83
81
|
|
|
@@ -89,9 +87,7 @@ function _body(s) {
|
|
|
89
87
|
if (s.length > MAX_BODY_LEN) {
|
|
90
88
|
throw new TypeError("reviews: body must be <= " + MAX_BODY_LEN + " characters");
|
|
91
89
|
}
|
|
92
|
-
|
|
93
|
-
throw new TypeError("reviews: body contains control bytes");
|
|
94
|
-
}
|
|
90
|
+
textGuard.freeText(s, "reviews: body", { bidiMarks: "allow" });
|
|
95
91
|
return s;
|
|
96
92
|
}
|
|
97
93
|
|
|
@@ -288,9 +284,7 @@ function create(opts) {
|
|
|
288
284
|
if (reason.length > MAX_REJECT_REASON_LEN) {
|
|
289
285
|
throw new TypeError("reviews.reject: reason must be <= " + MAX_REJECT_REASON_LEN + " characters");
|
|
290
286
|
}
|
|
291
|
-
|
|
292
|
-
throw new TypeError("reviews.reject: reason contains control bytes");
|
|
293
|
-
}
|
|
287
|
+
textGuard.freeText(reason, "reviews.reject: reason", { bidiMarks: "allow" });
|
|
294
288
|
}
|
|
295
289
|
var ts = _now();
|
|
296
290
|
var existing = (await query("SELECT id, status FROM reviews WHERE id = ?1", [id])).rows[0];
|
package/lib/robots-config.js
CHANGED
|
@@ -96,9 +96,9 @@ var MAX_CRAWL_DELAY = 86400; // 24 h — anything larger is a typo
|
|
|
96
96
|
var MAX_PRIORITY = 1000000;
|
|
97
97
|
var MAX_RULE_ID_LEN = 80;
|
|
98
98
|
|
|
99
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
100
99
|
|
|
101
100
|
var b = require("./vendor/blamejs");
|
|
101
|
+
var textGuard = require("./text-guard");
|
|
102
102
|
|
|
103
103
|
// ---- predefined templates ---------------------------------------------
|
|
104
104
|
//
|
|
@@ -183,9 +183,7 @@ function _userAgent(s) {
|
|
|
183
183
|
"robotsConfig: user_agent must be a string 1.." + MAX_USER_AGENT_LEN + " chars"
|
|
184
184
|
);
|
|
185
185
|
}
|
|
186
|
-
|
|
187
|
-
throw new TypeError("robotsConfig: user_agent must not contain control bytes");
|
|
188
|
-
}
|
|
186
|
+
textGuard.freeText(s, "robotsConfig: user_agent", { singleLine: "reject" });
|
|
189
187
|
// robots.txt is a colon-delimited key/value format — a colon in the
|
|
190
188
|
// user-agent value would shift the parser onto a different key.
|
|
191
189
|
if (s.indexOf(":") !== -1) {
|
|
@@ -200,7 +198,7 @@ function _pathEntry(s, label) {
|
|
|
200
198
|
"robotsConfig: " + label + " entry must be a string 1.." + MAX_PATH_LEN + " chars"
|
|
201
199
|
);
|
|
202
200
|
}
|
|
203
|
-
if (
|
|
201
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
204
202
|
throw new TypeError("robotsConfig: " + label + " entry must not contain control bytes");
|
|
205
203
|
}
|
|
206
204
|
// robots.txt path entries are /-rooted absolute paths. Wildcards
|
|
@@ -258,9 +256,7 @@ function _ruleId(s) {
|
|
|
258
256
|
"robotsConfig: rule_id must be a string 1.." + MAX_RULE_ID_LEN + " chars"
|
|
259
257
|
);
|
|
260
258
|
}
|
|
261
|
-
|
|
262
|
-
throw new TypeError("robotsConfig: rule_id must not contain control bytes");
|
|
263
|
-
}
|
|
259
|
+
textGuard.freeText(s, "robotsConfig: rule_id", { singleLine: "reject" });
|
|
264
260
|
return s;
|
|
265
261
|
}
|
|
266
262
|
|
|
@@ -270,9 +266,7 @@ function _sitemapUrl(u) {
|
|
|
270
266
|
"robotsConfig: sitemap url must be a string 1.." + MAX_SITEMAP_URL_LEN + " chars"
|
|
271
267
|
);
|
|
272
268
|
}
|
|
273
|
-
|
|
274
|
-
throw new TypeError("robotsConfig: sitemap url must not contain control bytes");
|
|
275
|
-
}
|
|
269
|
+
textGuard.freeText(u, "robotsConfig: sitemap url", { singleLine: "reject" });
|
|
276
270
|
// robots.txt only carries the URL itself, so the path-rooted form
|
|
277
271
|
// promo-banners accepts isn't meaningful here — every crawler treats
|
|
278
272
|
// the Sitemap: value as an absolute URL. https-only protects against
|
|
@@ -293,9 +287,7 @@ function _hostDirective(s) {
|
|
|
293
287
|
"robotsConfig: host must be a string 1.." + MAX_HOST_LEN + " chars"
|
|
294
288
|
);
|
|
295
289
|
}
|
|
296
|
-
|
|
297
|
-
throw new TypeError("robotsConfig: host must not contain control bytes");
|
|
298
|
-
}
|
|
290
|
+
textGuard.freeText(s, "robotsConfig: host", { singleLine: "reject" });
|
|
299
291
|
// The host directive is a bare hostname (optionally with :port) —
|
|
300
292
|
// no scheme, no path. Refuse anything that looks like a URL so an
|
|
301
293
|
// operator can't accidentally paste the full origin URL in.
|
package/lib/sales-tax-filings.js
CHANGED
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
*/
|
|
105
105
|
|
|
106
106
|
var b = require("./vendor/blamejs");
|
|
107
|
+
var textGuard = require("./text-guard");
|
|
107
108
|
var C = b.constants;
|
|
108
109
|
|
|
109
110
|
// ---- constants ----------------------------------------------------------
|
|
@@ -122,7 +123,6 @@ var DEFAULT_LIMIT = 50;
|
|
|
122
123
|
var MAX_LIMIT = 500;
|
|
123
124
|
var MAX_DAYS_AHEAD = 365;
|
|
124
125
|
|
|
125
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
126
126
|
|
|
127
127
|
// FSM transition map. The keys are (from_status, action) → to_status.
|
|
128
128
|
// Defined as a table so a misuse surfaces with the actual current
|
|
@@ -211,7 +211,7 @@ function _shortText(s, label, max) {
|
|
|
211
211
|
throw new TypeError("salesTaxFilings: " + label +
|
|
212
212
|
" must be a non-empty string <= " + max + " chars");
|
|
213
213
|
}
|
|
214
|
-
if (
|
|
214
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
215
215
|
throw new TypeError("salesTaxFilings: " + label + " must not contain control bytes");
|
|
216
216
|
}
|
|
217
217
|
return s;
|
package/lib/search-facets.js
CHANGED
|
@@ -106,7 +106,6 @@ var ALLOWED_KINDS = ["categorical", "numeric_range", "boolean"];
|
|
|
106
106
|
var ALLOWED_COLS = ["field", "kind", "buckets_json", "display_limit", "active"];
|
|
107
107
|
|
|
108
108
|
// Refuse C0 control bytes + DEL on every operator-supplied string.
|
|
109
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
110
109
|
|
|
111
110
|
// Operator-chosen identifier shape — lowercase alnum + hyphen +
|
|
112
111
|
// underscore, must start with a letter. Same shape the rest of the
|
|
@@ -118,6 +117,7 @@ var KEY_RE = /^[a-z][a-z0-9_-]*$/;
|
|
|
118
117
|
var FIELD_RE = /^[a-z][a-z0-9_-]*$/;
|
|
119
118
|
|
|
120
119
|
var b = require("./vendor/blamejs");
|
|
120
|
+
var textGuard = require("./text-guard");
|
|
121
121
|
|
|
122
122
|
// ---- validators ---------------------------------------------------------
|
|
123
123
|
|
|
@@ -175,7 +175,7 @@ function _label(s, fnLabel) {
|
|
|
175
175
|
if (s.length > MAX_LABEL_LEN) {
|
|
176
176
|
throw new TypeError(fnLabel + ": bucket label must be <= " + MAX_LABEL_LEN + " characters");
|
|
177
177
|
}
|
|
178
|
-
if (
|
|
178
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
179
179
|
throw new TypeError(fnLabel + ": bucket label must not contain control bytes");
|
|
180
180
|
}
|
|
181
181
|
return s;
|
|
@@ -291,7 +291,7 @@ function _appliedFilters(input, fnLabel) {
|
|
|
291
291
|
if (s.length > MAX_VALUE_LEN) {
|
|
292
292
|
throw new TypeError(fnLabel + ": applied_filters['" + k + "'][" + j + "] must be <= " + MAX_VALUE_LEN + " characters");
|
|
293
293
|
}
|
|
294
|
-
if (
|
|
294
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
295
295
|
throw new TypeError(fnLabel + ": applied_filters['" + k + "'][" + j + "] must not contain control bytes");
|
|
296
296
|
}
|
|
297
297
|
values.push(s);
|
|
@@ -318,7 +318,7 @@ function _value(raw, fnLabel) {
|
|
|
318
318
|
if (s.length > MAX_VALUE_LEN) {
|
|
319
319
|
throw new TypeError(fnLabel + ": value must be <= " + MAX_VALUE_LEN + " characters");
|
|
320
320
|
}
|
|
321
|
-
if (
|
|
321
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
322
322
|
throw new TypeError(fnLabel + ": value must not contain control bytes");
|
|
323
323
|
}
|
|
324
324
|
return s;
|
|
@@ -331,7 +331,7 @@ function _sessionId(s, fnLabel) {
|
|
|
331
331
|
if (s.length > 256) {
|
|
332
332
|
throw new TypeError(fnLabel + ": session_id must be <= 256 characters");
|
|
333
333
|
}
|
|
334
|
-
if (
|
|
334
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
335
335
|
throw new TypeError(fnLabel + ": session_id must not contain control bytes");
|
|
336
336
|
}
|
|
337
337
|
return s;
|
package/lib/search-ranking.js
CHANGED
|
@@ -121,12 +121,9 @@ var SIGNAL_RE = /^[a-z][a-z0-9_-]*$/;
|
|
|
121
121
|
// which covers SKU notation across every common upstream system
|
|
122
122
|
// without opening the door to control bytes or whitespace.
|
|
123
123
|
var PRODUCT_ID_RE = /^[A-Za-z0-9._:/-]+$/;
|
|
124
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
125
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
126
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
127
|
-
);
|
|
128
124
|
|
|
129
125
|
var b = require("./vendor/blamejs");
|
|
126
|
+
var textGuard = require("./text-guard");
|
|
130
127
|
|
|
131
128
|
// ---- monotonic clock ---------------------------------------------------
|
|
132
129
|
//
|
|
@@ -175,7 +172,7 @@ function _name(s, fnLabel) {
|
|
|
175
172
|
if (s.length > MAX_NAME_LEN) {
|
|
176
173
|
throw new TypeError(fnLabel + ": name must be <= " + MAX_NAME_LEN + " characters");
|
|
177
174
|
}
|
|
178
|
-
if (
|
|
175
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject", zeroWidth: "reject" })) {
|
|
179
176
|
throw new TypeError(fnLabel + ": name contains control / zero-width bytes");
|
|
180
177
|
}
|
|
181
178
|
return s;
|
|
@@ -226,7 +223,7 @@ function _normalizeQuery(s, fnLabel) {
|
|
|
226
223
|
if (s.length > MAX_QUERY_LEN) {
|
|
227
224
|
throw new TypeError(fnLabel + ": query must be <= " + MAX_QUERY_LEN + " characters");
|
|
228
225
|
}
|
|
229
|
-
if (
|
|
226
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject", zeroWidth: "reject" })) {
|
|
230
227
|
throw new TypeError(fnLabel + ": query contains control / zero-width bytes");
|
|
231
228
|
}
|
|
232
229
|
var normalized = s.toLowerCase().replace(/\s+/g, " ").trim();
|
|
@@ -270,7 +267,7 @@ function _sessionIdRaw(s, fnLabel) {
|
|
|
270
267
|
if (s.length > MAX_SESSION_ID_LEN) {
|
|
271
268
|
throw new TypeError(fnLabel + ": session_id must be <= " + MAX_SESSION_ID_LEN + " characters");
|
|
272
269
|
}
|
|
273
|
-
if (
|
|
270
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject", zeroWidth: "reject" })) {
|
|
274
271
|
throw new TypeError(fnLabel + ": session_id contains control / zero-width bytes");
|
|
275
272
|
}
|
|
276
273
|
return s;
|
package/lib/search-synonyms.js
CHANGED
|
@@ -102,18 +102,6 @@ var MAX_QUERY_LEN = 500;
|
|
|
102
102
|
var ALLOWED_KINDS = ["bidirectional", "directional"];
|
|
103
103
|
var ALLOWED_GROUP_COLS = ["kind", "terms_json"];
|
|
104
104
|
|
|
105
|
-
// Refuse C0 control bytes + DEL. Tokens are single-line search terms;
|
|
106
|
-
// any newline / tab in a synonym entry would either be malformed
|
|
107
|
-
// operator input or an attempt to smuggle separator-confusion payload
|
|
108
|
-
// into the search log.
|
|
109
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
110
|
-
// Zero-width + direction-override family — same defense as
|
|
111
|
-
// `supportTickets`. Spelled with \u escapes so the file stays
|
|
112
|
-
// ESLint no-irregular-whitespace clean.
|
|
113
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
114
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
115
|
-
);
|
|
116
|
-
|
|
117
105
|
// Slug shape — operator-chosen identifier on synonym groups. Same
|
|
118
106
|
// shape the rest of the shop uses: lowercase alnum + hyphen, must
|
|
119
107
|
// start with a letter, 1..MAX_SLUG_LEN.
|
|
@@ -126,6 +114,7 @@ var TOKEN_SPLIT_RE = /[^a-z0-9-]+/g;
|
|
|
126
114
|
|
|
127
115
|
// Framework handle (the vendored blamejs); index.js re-exports this as .framework.
|
|
128
116
|
var b = require("./vendor/blamejs");
|
|
117
|
+
var textGuard = require("./text-guard");
|
|
129
118
|
|
|
130
119
|
// ---- validators ---------------------------------------------------------
|
|
131
120
|
|
|
@@ -166,10 +155,10 @@ function _term(s, label, fnLabel) {
|
|
|
166
155
|
if (trimmed.length > MAX_TERM_LEN) {
|
|
167
156
|
throw new TypeError(fnLabel + ": " + label + " must be <= " + MAX_TERM_LEN + " characters");
|
|
168
157
|
}
|
|
169
|
-
if (
|
|
158
|
+
if (textGuard.hasCodepointThreat(trimmed, { singleLine: "reject" })) {
|
|
170
159
|
throw new TypeError(fnLabel + ": " + label + " contains control bytes");
|
|
171
160
|
}
|
|
172
|
-
if (
|
|
161
|
+
if (textGuard.hasCodepointThreat(trimmed, { zeroWidth: "reject" })) {
|
|
173
162
|
throw new TypeError(fnLabel + ": " + label + " contains zero-width / direction-override characters");
|
|
174
163
|
}
|
|
175
164
|
return trimmed;
|
|
@@ -247,13 +236,10 @@ function _queryString(s, fnLabel) {
|
|
|
247
236
|
// give attackers a denial-of-search vector against legitimate
|
|
248
237
|
// shoppers who happened to paste a stray BOM.
|
|
249
238
|
function _sanitiseQuery(s) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
new RegExp("[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]", "g"),
|
|
255
|
-
""
|
|
256
|
-
);
|
|
239
|
+
// Controls become a SPACE, not nothing: deleting them outright would weld
|
|
240
|
+
// the words on either side into one token. The invisible characters do get
|
|
241
|
+
// deleted — they sit inside a word rather than between two.
|
|
242
|
+
return textGuard.scrubInvisible(s.replace(/[\x00-\x1f\x7f]/g, " "));
|
|
257
243
|
}
|
|
258
244
|
|
|
259
245
|
function _tokenise(s) {
|