@blamejs/blamejs-shop 0.5.19 → 0.5.21

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.
Files changed (194) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -0
  3. package/SECURITY.md +17 -0
  4. package/lib/addresses.js +2 -2
  5. package/lib/admin.js +49 -45
  6. package/lib/affiliates.js +7 -23
  7. package/lib/announcement-bar.js +5 -22
  8. package/lib/api-keys.js +4 -13
  9. package/lib/asset-manifest.json +1 -1
  10. package/lib/banner-ab-tests.js +5 -9
  11. package/lib/blog-articles.js +8 -26
  12. package/lib/captcha-gate.js +7 -17
  13. package/lib/carrier-accounts.js +3 -10
  14. package/lib/catalog-drafts.js +5 -12
  15. package/lib/category-navigation.js +4 -20
  16. package/lib/cms-blocks.js +4 -7
  17. package/lib/compliance-export.js +4 -11
  18. package/lib/consent-ledger.js +2 -4
  19. package/lib/cost-layers.js +56 -21
  20. package/lib/customer-import.js +2 -2
  21. package/lib/customer-notes.js +7 -23
  22. package/lib/customer-roles.js +3 -5
  23. package/lib/customer-segments.js +17 -14
  24. package/lib/customer-surveys.js +7 -14
  25. package/lib/customers.js +2 -4
  26. package/lib/dispute-resolution.js +5 -13
  27. package/lib/dropship-forwarding.js +3 -5
  28. package/lib/dunning.js +2 -7
  29. package/lib/email-ab-tests.js +4 -8
  30. package/lib/email-campaigns.js +25 -14
  31. package/lib/email-warmup.js +2 -7
  32. package/lib/experiments.js +6 -10
  33. package/lib/externaldb-d1.js +78 -44
  34. package/lib/geolocation.js +18 -28
  35. package/lib/gift-options.js +5 -23
  36. package/lib/knowledge-base.js +7 -22
  37. package/lib/line-gift-wrap.js +3 -17
  38. package/lib/live-chat.js +5 -18
  39. package/lib/metered-usage.js +3 -8
  40. package/lib/notifications.js +3 -7
  41. package/lib/operator-accounts.js +3 -5
  42. package/lib/operator-approvals.js +6 -8
  43. package/lib/operator-audit-log.js +2 -7
  44. package/lib/operator-help-center.js +5 -21
  45. package/lib/operator-inbox.js +4 -11
  46. package/lib/operator-roles.js +5 -7
  47. package/lib/order-escalation.js +2 -2
  48. package/lib/order-export.js +24 -20
  49. package/lib/order-notes.js +4 -14
  50. package/lib/order-ratings.js +4 -10
  51. package/lib/payment-methods.js +5 -13
  52. package/lib/payment-retries.js +2 -7
  53. package/lib/payment.js +103 -58
  54. package/lib/pixel-events.js +8 -23
  55. package/lib/plan-changes.js +2 -7
  56. package/lib/product-qa.js +4 -8
  57. package/lib/promo-banners.js +7 -15
  58. package/lib/purchase-orders.js +3 -6
  59. package/lib/push-notifications.js +7 -32
  60. package/lib/pwa-manifest.js +6 -11
  61. package/lib/quotes.js +5 -9
  62. package/lib/referrals.js +10 -6
  63. package/lib/reviews.js +4 -10
  64. package/lib/robots-config.js +6 -14
  65. package/lib/sales-tax-filings.js +2 -2
  66. package/lib/search-facets.js +5 -5
  67. package/lib/search-ranking.js +4 -7
  68. package/lib/search-synonyms.js +7 -21
  69. package/lib/security-middleware.js +125 -70
  70. package/lib/seller-signup.js +10 -26
  71. package/lib/sidebar-widgets.js +6 -15
  72. package/lib/site-redirects.js +49 -137
  73. package/lib/sitemap-generator.js +3 -7
  74. package/lib/sms-dispatcher.js +7 -23
  75. package/lib/stock-receipts.js +4 -10
  76. package/lib/storefront-dashboards.js +2 -10
  77. package/lib/storefront-forms.js +9 -22
  78. package/lib/storefront-pages.js +6 -20
  79. package/lib/storefront.js +21 -23
  80. package/lib/subscription-billing.js +3 -8
  81. package/lib/subscription-controls.js +2 -7
  82. package/lib/suggestion-box.js +9 -26
  83. package/lib/support-tickets.js +6 -24
  84. package/lib/tax-remittance.js +2 -2
  85. package/lib/tenants.js +2 -7
  86. package/lib/text-guard.js +137 -35
  87. package/lib/theme-assets.js +4 -16
  88. package/lib/translations.js +3 -6
  89. package/lib/trust-badges.js +4 -7
  90. package/lib/vendor/MANIFEST.json +107 -97
  91. package/lib/vendor/blamejs/CHANGELOG.md +180 -0
  92. package/lib/vendor/blamejs/NOTICE +14 -10
  93. package/lib/vendor/blamejs/README.md +17 -5
  94. package/lib/vendor/blamejs/index.js +2 -0
  95. package/lib/vendor/blamejs/lib/acme.js +9 -9
  96. package/lib/vendor/blamejs/lib/api-snapshot.js +35 -16
  97. package/lib/vendor/blamejs/lib/app-shutdown.js +1 -1
  98. package/lib/vendor/blamejs/lib/archive-adapters.js +1 -1
  99. package/lib/vendor/blamejs/lib/archive.js +13 -24
  100. package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -1
  101. package/lib/vendor/blamejs/lib/auth/bot-challenge.js +3 -6
  102. package/lib/vendor/blamejs/lib/auth/ciba.js +32 -3
  103. package/lib/vendor/blamejs/lib/auth/oauth.js +65 -8
  104. package/lib/vendor/blamejs/lib/auth/password.js +1 -1
  105. package/lib/vendor/blamejs/lib/auth/saml.js +21 -3
  106. package/lib/vendor/blamejs/lib/auth-bot-challenge.js +3 -3
  107. package/lib/vendor/blamejs/lib/backup/index.js +5 -5
  108. package/lib/vendor/blamejs/lib/case-fold-classes.js +491 -0
  109. package/lib/vendor/blamejs/lib/codepoint-class.js +131 -0
  110. package/lib/vendor/blamejs/lib/constants.js +79 -0
  111. package/lib/vendor/blamejs/lib/db-declare-view.js +8 -2
  112. package/lib/vendor/blamejs/lib/error-page.js +20 -1
  113. package/lib/vendor/blamejs/lib/external-db.js +5 -1
  114. package/lib/vendor/blamejs/lib/flag-evaluation-context.js +24 -6
  115. package/lib/vendor/blamejs/lib/flag.js +28 -2
  116. package/lib/vendor/blamejs/lib/framework-schema.js +14 -3
  117. package/lib/vendor/blamejs/lib/guard-regex.js +2345 -89
  118. package/lib/vendor/blamejs/lib/http-client.js +217 -40
  119. package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
  120. package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
  121. package/lib/vendor/blamejs/lib/mail-dav.js +26 -26
  122. package/lib/vendor/blamejs/lib/mail-deploy.js +10 -10
  123. package/lib/vendor/blamejs/lib/mail-scan.js +3 -0
  124. package/lib/vendor/blamejs/lib/mail-store.js +7 -1
  125. package/lib/vendor/blamejs/lib/mail.js +5 -1
  126. package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -2
  127. package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +1 -1
  128. package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -1
  129. package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +3 -2
  130. package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
  131. package/lib/vendor/blamejs/lib/middleware/compression.js +1 -1
  132. package/lib/vendor/blamejs/lib/middleware/cors.js +1 -1
  133. package/lib/vendor/blamejs/lib/middleware/csp-report.js +5 -5
  134. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +2 -1
  135. package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +2 -2
  136. package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -1
  137. package/lib/vendor/blamejs/lib/middleware/dpop.js +2 -2
  138. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +2 -1
  139. package/lib/vendor/blamejs/lib/middleware/flag-context.js +17 -0
  140. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +10 -8
  141. package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +3 -2
  142. package/lib/vendor/blamejs/lib/middleware/require-aal.js +3 -2
  143. package/lib/vendor/blamejs/lib/middleware/require-auth.js +4 -3
  144. package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +4 -3
  145. package/lib/vendor/blamejs/lib/middleware/require-content-type.js +3 -2
  146. package/lib/vendor/blamejs/lib/middleware/require-methods.js +3 -2
  147. package/lib/vendor/blamejs/lib/middleware/require-mtls.js +3 -2
  148. package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -1
  149. package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -1
  150. package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
  151. package/lib/vendor/blamejs/lib/network-dns.js +3 -3
  152. package/lib/vendor/blamejs/lib/network-nts.js +35 -14
  153. package/lib/vendor/blamejs/lib/network-proxy.js +42 -4
  154. package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
  155. package/lib/vendor/blamejs/lib/network-tls.js +341 -11
  156. package/lib/vendor/blamejs/lib/nist-crosswalk.js +2 -2
  157. package/lib/vendor/blamejs/lib/ntp-check.js +30 -19
  158. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -1
  159. package/lib/vendor/blamejs/lib/object-store/gcs.js +1 -1
  160. package/lib/vendor/blamejs/lib/object-store/http-put.js +2 -1
  161. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -5
  162. package/lib/vendor/blamejs/lib/object-store/sigv4.js +2 -2
  163. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +4 -2
  164. package/lib/vendor/blamejs/lib/openapi.js +1 -1
  165. package/lib/vendor/blamejs/lib/pqc-agent.js +4 -7
  166. package/lib/vendor/blamejs/lib/problem-details.js +2 -1
  167. package/lib/vendor/blamejs/lib/redis-client.js +12 -1
  168. package/lib/vendor/blamejs/lib/regex-linear.js +1349 -0
  169. package/lib/vendor/blamejs/lib/render.js +531 -7
  170. package/lib/vendor/blamejs/lib/request-helpers.js +181 -5
  171. package/lib/vendor/blamejs/lib/router.js +37 -13
  172. package/lib/vendor/blamejs/lib/safe-async.js +142 -0
  173. package/lib/vendor/blamejs/lib/safe-icap.js +3 -0
  174. package/lib/vendor/blamejs/lib/safe-sql.js +15 -3
  175. package/lib/vendor/blamejs/lib/self-update.js +2 -2
  176. package/lib/vendor/blamejs/lib/testing.js +54 -7
  177. package/lib/vendor/blamejs/lib/validate-opts.js +56 -0
  178. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +62 -25
  179. package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +2004 -142
  180. package/lib/vendor/blamejs/lib/vendor/browser/noble-ciphers.mjs +727 -0
  181. package/lib/vendor/blamejs/lib/vendor/browser/noble-hashes.mjs +1070 -0
  182. package/lib/vendor/blamejs/lib/vendor/browser/noble-post-quantum.mjs +1090 -0
  183. package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +510 -227
  184. package/lib/vendor/blamejs/lib/webhook-hmac.js +19 -2
  185. package/lib/vendor/blamejs/lib/webhook.js +1 -1
  186. package/lib/vendor/blamejs/lib/ws-client.js +24 -2
  187. package/lib/vendor/blamejs/package.json +1 -1
  188. package/lib/vendor/blamejs/sbom.cdx.json +6 -6
  189. package/lib/vendor-invoices.js +3 -6
  190. package/lib/vendors.js +5 -19
  191. package/lib/webhook-receiver.js +46 -83
  192. package/lib/webhook-subscriptions.js +3 -13
  193. package/lib/wishlist-sharing.js +5 -10
  194. package/package.json +1 -1
@@ -164,13 +164,13 @@ var MAX_HOURS = 24 * 365; // one-year cap
164
164
 
165
165
  var SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
166
166
  var ACTION_KIND_RE = /^[a-z][a-z0-9_.\-]{0,79}$/;
167
- var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
168
167
 
169
168
  var DECISIONS = Object.freeze(["approve", "reject", "abstain"]);
170
169
  var STATUSES = Object.freeze(["pending", "approved", "rejected",
171
170
  "executed", "cancelled", "escalated"]);
172
171
 
173
172
  var b = require("./vendor/blamejs");
173
+ var textGuard = require("./text-guard");
174
174
 
175
175
  // ---- validators ---------------------------------------------------------
176
176
 
@@ -195,7 +195,7 @@ function _operatorId(s, label) {
195
195
  throw new TypeError("operatorApprovals: " + label +
196
196
  " must be a non-empty string (<= " + MAX_OPERATOR_ID_LEN + " chars)");
197
197
  }
198
- if (CONTROL_BYTE_RE.test(s)) {
198
+ if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
199
199
  throw new TypeError("operatorApprovals: " + label + " must not contain control bytes");
200
200
  }
201
201
  return s;
@@ -206,7 +206,7 @@ function _reason(s) {
206
206
  throw new TypeError("operatorApprovals: reason must be a non-empty string <= " +
207
207
  MAX_REASON_LEN + " chars");
208
208
  }
209
- if (CONTROL_BYTE_RE.test(s.replace(/[\t\r\n]/g, ""))) {
209
+ if (textGuard.hasCodepointThreat(s)) {
210
210
  throw new TypeError("operatorApprovals: reason must not contain control bytes (except whitespace)");
211
211
  }
212
212
  return s;
@@ -217,7 +217,7 @@ function _justification(s) {
217
217
  throw new TypeError("operatorApprovals: justification must be a non-empty string <= " +
218
218
  MAX_JUSTIFICATION_LEN + " chars");
219
219
  }
220
- if (CONTROL_BYTE_RE.test(s.replace(/[\t\r\n]/g, ""))) {
220
+ if (textGuard.hasCodepointThreat(s)) {
221
221
  throw new TypeError("operatorApprovals: justification must not contain control bytes (except whitespace)");
222
222
  }
223
223
  return s;
@@ -229,7 +229,7 @@ function _comment(s) {
229
229
  throw new TypeError("operatorApprovals: comment must be a string <= " +
230
230
  MAX_COMMENT_LEN + " chars");
231
231
  }
232
- if (CONTROL_BYTE_RE.test(s.replace(/[\t\r\n]/g, ""))) {
232
+ if (textGuard.hasCodepointThreat(s)) {
233
233
  throw new TypeError("operatorApprovals: comment must not contain control bytes (except whitespace)");
234
234
  }
235
235
  return s;
@@ -317,9 +317,7 @@ function _capability(s) {
317
317
  if (typeof s !== "string" || !s.length || s.length > MAX_ACTION_KIND_LEN) {
318
318
  throw new TypeError("operatorApprovals: required_capability must be a non-empty string or null");
319
319
  }
320
- if (CONTROL_BYTE_RE.test(s)) {
321
- throw new TypeError("operatorApprovals: required_capability must not contain control bytes");
322
- }
320
+ textGuard.freeText(s, "operatorApprovals: required_capability", { singleLine: "reject" });
323
321
  return s;
324
322
  }
325
323
 
@@ -104,16 +104,13 @@ var ALLOWED_UA_CLASSES = Object.freeze([
104
104
  // discipline matches every other operator-facing primitive (catalog,
105
105
  // experiments, promo-banners) — strings reach the dashboard as inert
106
106
  // text, never as live markup.
107
- var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
108
- var ZERO_WIDTH_RE = new RegExp(
109
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
110
- );
111
107
 
112
108
  var IDENT_RE = /^[A-Za-z0-9][A-Za-z0-9._:\-]{0,127}$/;
113
109
  var ACTION_RE = /^[A-Za-z0-9][A-Za-z0-9._:\-]{0,127}$/;
114
110
  var KIND_RE = /^[A-Za-z0-9][A-Za-z0-9._\-]{0,63}$/;
115
111
 
116
112
  var b = require("./vendor/blamejs");
113
+ var textGuard = require("./text-guard");
117
114
 
118
115
  // ---- validators ---------------------------------------------------------
119
116
 
@@ -148,9 +145,7 @@ function _ipHash(s) {
148
145
  throw new TypeError("operatorAuditLog: ip_hash must be a non-empty string ≤ " +
149
146
  MAX_IP_HASH_LEN + " chars");
150
147
  }
151
- if (CONTROL_BYTE_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
152
- throw new TypeError("operatorAuditLog: ip_hash contains control / zero-width characters");
153
- }
148
+ textGuard.freeText(s, "operatorAuditLog: ip_hash", { singleLine: "reject", zeroWidth: "reject" });
154
149
  return s;
155
150
  }
156
151
 
@@ -109,11 +109,6 @@ var SLUG_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
109
109
  var SECTION_RE = /^[a-z][a-z0-9_-]*$/;
110
110
  var ACTION_RE = /^[a-z0-9](?:[a-z0-9._/:-]*[a-z0-9])?$/;
111
111
 
112
- var CONTROL_BYTE_BODY_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
113
- var CONTROL_BYTE_STRICT_RE = /[\x00-\x1f\x7f]/;
114
- var ZERO_WIDTH_RE = new RegExp(
115
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
116
- );
117
112
 
118
113
  var SEARCH_WEIGHTS = Object.freeze({
119
114
  title: 3,
@@ -126,6 +121,7 @@ var ALLOWED_PATCH_COLUMNS = Object.freeze([
126
121
  ]);
127
122
 
128
123
  var b = require("./vendor/blamejs");
124
+ var textGuard = require("./text-guard");
129
125
 
130
126
  // ---- monotonic clock ---------------------------------------------------
131
127
  //
@@ -168,12 +164,7 @@ function _title(s) {
168
164
  if (s.length > MAX_TITLE_LEN) {
169
165
  throw new TypeError("operatorHelpCenter: title must be <= " + MAX_TITLE_LEN + " characters");
170
166
  }
171
- if (CONTROL_BYTE_STRICT_RE.test(s)) {
172
- throw new TypeError("operatorHelpCenter: title contains control bytes");
173
- }
174
- if (ZERO_WIDTH_RE.test(s)) {
175
- throw new TypeError("operatorHelpCenter: title contains zero-width / direction-override characters");
176
- }
167
+ textGuard.freeText(s, "operatorHelpCenter: title", { singleLine: "reject", zeroWidth: "reject" });
177
168
  return s;
178
169
  }
179
170
 
@@ -188,12 +179,7 @@ function _body(s) {
188
179
  if (s.length > MAX_BODY_LEN) {
189
180
  throw new TypeError("operatorHelpCenter: body must be <= " + MAX_BODY_LEN + " characters");
190
181
  }
191
- if (CONTROL_BYTE_BODY_RE.test(s)) {
192
- throw new TypeError("operatorHelpCenter: body contains control bytes");
193
- }
194
- if (ZERO_WIDTH_RE.test(s)) {
195
- throw new TypeError("operatorHelpCenter: body contains zero-width / direction-override characters");
196
- }
182
+ textGuard.freeText(s, "operatorHelpCenter: body", { zeroWidth: "reject" });
197
183
  return s;
198
184
  }
199
185
 
@@ -304,9 +290,7 @@ function _queryStr(s) {
304
290
  if (s.length > MAX_QUERY_LEN) {
305
291
  throw new TypeError("operatorHelpCenter: query must be <= " + MAX_QUERY_LEN + " characters");
306
292
  }
307
- if (CONTROL_BYTE_BODY_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
308
- throw new TypeError("operatorHelpCenter: query contains control / zero-width bytes");
309
- }
293
+ textGuard.freeText(s, "operatorHelpCenter: query", { zeroWidth: "reject" });
310
294
  return s;
311
295
  }
312
296
 
@@ -346,7 +330,7 @@ function _esc(s) {
346
330
 
347
331
  function _safeLinkUrl(url) {
348
332
  if (typeof url !== "string" || !url.length || url.length > 2048) return null;
349
- if (CONTROL_BYTE_BODY_RE.test(url) || ZERO_WIDTH_RE.test(url)) return null;
333
+ if (textGuard.hasCodepointThreat(url, { zeroWidth: "reject" })) return null;
350
334
  if (url.charCodeAt(0) === 47 /* "/" */) {
351
335
  if (url.length > 1 && url.charCodeAt(1) === 47) return null;
352
336
  if (url.indexOf("..") !== -1) return null;
@@ -165,6 +165,7 @@
165
165
  */
166
166
 
167
167
  var b = require("./vendor/blamejs");
168
+ var textGuard = require("./text-guard");
168
169
 
169
170
  // ---- constants ----------------------------------------------------------
170
171
 
@@ -183,8 +184,6 @@ var MAX_BULK_IDS = 200;
183
184
  var DEFAULT_LIMIT = 50;
184
185
  var MAX_LIMIT = 500;
185
186
 
186
- var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
187
- var CONTROL_BYTE_STRICT_RE = /[\x00-\x1f\x7f]/;
188
187
 
189
188
  var CURSOR_ORDER_KEY = Object.freeze(["created_at", "id"]);
190
189
 
@@ -249,9 +248,7 @@ function _subject(s) {
249
248
  if (typeof s !== "string" || !s.length || s.length > MAX_SUBJECT_LEN) {
250
249
  throw new TypeError("operatorInbox: subject must be a non-empty string <= " + MAX_SUBJECT_LEN + " chars");
251
250
  }
252
- if (CONTROL_BYTE_RE.test(s)) {
253
- throw new TypeError("operatorInbox: subject must not contain control bytes");
254
- }
251
+ textGuard.freeText(s, "operatorInbox: subject");
255
252
  return s;
256
253
  }
257
254
 
@@ -259,9 +256,7 @@ function _body(s) {
259
256
  if (typeof s !== "string" || !s.length || s.length > MAX_BODY_LEN) {
260
257
  throw new TypeError("operatorInbox: body must be a non-empty string <= " + MAX_BODY_LEN + " chars");
261
258
  }
262
- if (CONTROL_BYTE_RE.test(s)) {
263
- throw new TypeError("operatorInbox: body must not contain control bytes");
264
- }
259
+ textGuard.freeText(s, "operatorInbox: body");
265
260
  return s;
266
261
  }
267
262
 
@@ -284,9 +279,7 @@ function _sourceEventId(s) {
284
279
  if (typeof s !== "string" || s.length > MAX_SOURCE_LEN) {
285
280
  throw new TypeError("operatorInbox: source_event_id must be a string <= " + MAX_SOURCE_LEN + " chars");
286
281
  }
287
- if (CONTROL_BYTE_STRICT_RE.test(s)) {
288
- throw new TypeError("operatorInbox: source_event_id must not contain control bytes");
289
- }
282
+ textGuard.freeText(s, "operatorInbox: source_event_id", { singleLine: "reject" });
290
283
  return s;
291
284
  }
292
285
 
@@ -144,7 +144,6 @@ var MAX_LIST_LIMIT = 500;
144
144
  // Slug shape matches the rest of the codebase — alnum-leading, alnum
145
145
  // + hyphen + underscore + dot, capped length.
146
146
  var SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
147
- var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
148
147
 
149
148
  // Closed permission allow-list. Adding a new permission is a code
150
149
  // change here AND the operator re-defining the affected roles.
@@ -170,6 +169,7 @@ var PERMISSIONS = Object.freeze([
170
169
  var ALLOWED_PATCH_COLUMNS = Object.freeze(["title", "permissions", "description"]);
171
170
 
172
171
  var b = require("./vendor/blamejs");
172
+ var textGuard = require("./text-guard");
173
173
 
174
174
  // ---- validators ---------------------------------------------------------
175
175
 
@@ -185,9 +185,7 @@ function _title(s) {
185
185
  if (typeof s !== "string" || !s.length || s.length > MAX_TITLE_LEN) {
186
186
  throw new TypeError("operatorRoles: title must be a non-empty string <= " + MAX_TITLE_LEN + " chars");
187
187
  }
188
- if (CONTROL_BYTE_RE.test(s)) {
189
- throw new TypeError("operatorRoles: title must not contain control bytes");
190
- }
188
+ textGuard.freeText(s, "operatorRoles: title", { singleLine: "reject" });
191
189
  return s;
192
190
  }
193
191
 
@@ -196,7 +194,7 @@ function _description(s) {
196
194
  if (typeof s !== "string" || s.length > MAX_DESC_LEN) {
197
195
  throw new TypeError("operatorRoles: description must be a string <= " + MAX_DESC_LEN + " chars");
198
196
  }
199
- if (CONTROL_BYTE_RE.test(s.replace(/[\t\r\n]/g, ""))) {
197
+ if (textGuard.hasCodepointThreat(s)) {
200
198
  throw new TypeError("operatorRoles: description must not contain control bytes (except whitespace)");
201
199
  }
202
200
  return s;
@@ -231,7 +229,7 @@ function _operatorId(s, label) {
231
229
  if (typeof s !== "string" || !s.length || s.length > MAX_OPERATOR_ID_LEN) {
232
230
  throw new TypeError("operatorRoles: " + label + " must be a non-empty string (<= " + MAX_OPERATOR_ID_LEN + " chars)");
233
231
  }
234
- if (CONTROL_BYTE_RE.test(s)) {
232
+ if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
235
233
  throw new TypeError("operatorRoles: " + label + " must not contain control bytes");
236
234
  }
237
235
  return s;
@@ -241,7 +239,7 @@ function _reason(s) {
241
239
  if (typeof s !== "string" || !s.length || s.length > MAX_REASON_LEN) {
242
240
  throw new TypeError("operatorRoles: reason must be a non-empty string <= " + MAX_REASON_LEN + " chars");
243
241
  }
244
- if (CONTROL_BYTE_RE.test(s.replace(/[\t\r\n]/g, ""))) {
242
+ if (textGuard.hasCodepointThreat(s)) {
245
243
  throw new TypeError("operatorRoles: reason must not contain control bytes (except whitespace)");
246
244
  }
247
245
  return s;
@@ -61,7 +61,6 @@ var COUNTRY_RE = /^[A-Z]{2}$/;
61
61
  var PAYMENT_KIND_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
62
62
  var TAG_RE = /^[A-Za-z0-9][A-Za-z0-9._:\-]{0,63}$/;
63
63
  var TEMPLATE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
64
- var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
65
64
 
66
65
  var SEVERITIES = Object.freeze(["info", "warning", "critical"]);
67
66
  var SEVERITY_RANK = Object.freeze({ info: 0, warning: 1, critical: 2 });
@@ -85,6 +84,7 @@ var ALLOWED_PATCH_COLUMNS = Object.freeze([
85
84
  ]);
86
85
 
87
86
  var b = require("./vendor/blamejs");
87
+ var textGuard = require("./text-guard");
88
88
 
89
89
  // ---- monotonic clock ----------------------------------------------------
90
90
  //
@@ -322,7 +322,7 @@ function _freeText(v, label, max) {
322
322
  if (v.length > max) {
323
323
  throw new TypeError("orderEscalation: " + label + " must be <= " + max + " characters");
324
324
  }
325
- if (CONTROL_BYTE_RE.test(v)) {
325
+ if (textGuard.hasCodepointThreat(v)) {
326
326
  throw new TypeError("orderEscalation: " + label + " must not contain control bytes");
327
327
  }
328
328
  return v;
@@ -117,6 +117,7 @@ var DEFAULT_BATCH_SIZE = 500;
117
117
  var MAX_BATCH_SIZE = 5000;
118
118
  var MAX_LIST_LIMIT = 200;
119
119
  var EMAIL_HASH_PREFIX = "order-export-email";
120
+ var CSV_LINE_ENDING = "\r\n"; // RFC 4180
120
121
  var FORMATS = Object.freeze(["csv", "ndjson"]);
121
122
  var EXPORT_STATUSES = Object.freeze(["queued", "running", "complete", "failed", "cancelled"]);
122
123
 
@@ -209,17 +210,6 @@ function _limit(n) {
209
210
 
210
211
  // ---- CSV helpers --------------------------------------------------------
211
212
 
212
- // RFC-4180 quoting: every cell wrapped in `"`, embedded `"` doubled.
213
- // We quote unconditionally — the cost is a few extra bytes per cell;
214
- // the win is that a downstream parser never has to track quote-vs-
215
- // bare-cell state for a column with mixed shapes. The injection
216
- // neutralization runs first via the shared vendored primitive.
217
- function _csvCell(value) {
218
- var s = _coerceCell(value);
219
- s = _neutralizeInjection(s);
220
- return '"' + s.replace(/"/g, '""') + '"';
221
- }
222
-
223
213
  function _coerceCell(value) {
224
214
  if (value == null) return "";
225
215
  if (typeof value === "string") return value;
@@ -239,14 +229,29 @@ function _coerceCell(value) {
239
229
  // numerics (`+15.00`); the shared primitive prefixes those too (the safe
240
230
  // OWASP posture — `-2+3+cmd|…` is a real injection that begins like an
241
231
  // amount), which is the more complete behavior this consolidation buys.
242
- function _neutralizeInjection(s) {
243
- return b.guardCsv.escapeCell(s);
244
- }
245
-
232
+ // One export row, RFC 4180.
233
+ //
234
+ // b.guardCsv.serialize does the whole line: the formula-injection prefix on
235
+ // every cell, the unconditional quoting, doubling an embedded quote, the
236
+ // delimiter and the CRLF. Only the injection step was composed before; the
237
+ // quoting rules were written here and again in customer-segments.js.
238
+ //
239
+ // Cells are quoted unconditionally. It costs a few bytes and means a
240
+ // downstream parser never has to track quote-versus-bare state on a column
241
+ // with mixed shapes.
242
+ //
243
+ // serialize emits the rows it is given WITHOUT a trailing terminator, which is
244
+ // right for a whole document and wrong for a generator yielding one row at a
245
+ // time — so the line ending is appended here. Verified byte-identical to the
246
+ // previous builder across formula-injection, embedded-quote, embedded-comma,
247
+ // newline, tab and empty cells.
246
248
  function _csvRow(cells) {
247
- var parts = [];
248
- for (var i = 0; i < cells.length; i += 1) parts.push(_csvCell(cells[i]));
249
- return parts.join(",") + "\r\n";
249
+ return b.guardCsv.serialize([cells.map(_coerceCell)], {
250
+ profile: "strict",
251
+ alwaysQuote: true,
252
+ headers: false,
253
+ lineEnding: CSV_LINE_ENDING,
254
+ }) + CSV_LINE_ENDING;
250
255
  }
251
256
 
252
257
  // ---- row projection -----------------------------------------------------
@@ -758,8 +763,7 @@ function create(opts) {
758
763
  // Exposed for the test suite so it can assert column semantics
759
764
  // without having to thread orders through a full DB round-trip.
760
765
  _projectOrder: _projectOrder,
761
- _csvCell: _csvCell,
762
- _neutralizeInjection: _neutralizeInjection,
766
+ _csvRow: _csvRow,
763
767
  // Exposed only to keep the linter quiet about the unused `order`
764
768
  // binding when an integrator wires it later — the export reads
765
769
  // directly today, but the indirection is preserved.
@@ -46,7 +46,6 @@ var ALLOWED_VISIBILITY = ["internal", "customer_visible"];
46
46
  // Refuse C0 control bytes + DEL + the LRM/RLM/ZWJ/ZWNJ family of
47
47
  // zero-width / direction-override characters. Newlines (LF, CR) +
48
48
  // tab are tolerated because operator notes legitimately wrap.
49
- var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
50
49
  // Zero-width + direction-override family: ZWSP/ZWNJ/ZWJ (U+200B-200D),
51
50
  // LRM/RLM (U+200E/U+200F), the bidi-formatting block
52
51
  // (U+202A-U+202E), the invisible-math block (U+2060-U+2064), the
@@ -54,9 +53,6 @@ var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
54
53
  // Arabic letter mark (U+061C). Spelled with \u-escapes so the
55
54
  // detector pattern stays grep-able and ESLint's
56
55
  // no-irregular-whitespace stays happy.
57
- var ZERO_WIDTH_RE = new RegExp(
58
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
59
- );
60
56
 
61
57
  // (pinned DESC, created_at DESC, id DESC) — pinned notes float to
62
58
  // the top; the id tie-break keeps cursor pagination deterministic
@@ -65,6 +61,7 @@ var LIST_ORDER_KEY = ["pinned:desc", "created_at:desc", "id:desc"];
65
61
 
66
62
  // Framework handle (the vendored blamejs); index.js re-exports this as .framework.
67
63
  var b = require("./vendor/blamejs");
64
+ var textGuard = require("./text-guard");
68
65
 
69
66
  // ---- validators ---------------------------------------------------------
70
67
 
@@ -98,12 +95,7 @@ function _body(s) {
98
95
  if (s.length > MAX_BODY_LEN) {
99
96
  throw new TypeError("orderNotes: body must be <= " + MAX_BODY_LEN + " characters");
100
97
  }
101
- if (CONTROL_BYTE_RE.test(s)) {
102
- throw new TypeError("orderNotes: body contains control bytes");
103
- }
104
- if (ZERO_WIDTH_RE.test(s)) {
105
- throw new TypeError("orderNotes: body contains zero-width / direction-override characters");
106
- }
98
+ textGuard.freeText(s, "orderNotes: body", { zeroWidth: "reject" });
107
99
  return s;
108
100
  }
109
101
 
@@ -124,7 +116,7 @@ function _tags(input) {
124
116
  if (t.length > MAX_TAG_LEN) {
125
117
  throw new TypeError("orderNotes: tags[" + i + "] must be <= " + MAX_TAG_LEN + " characters");
126
118
  }
127
- if (CONTROL_BYTE_RE.test(t) || ZERO_WIDTH_RE.test(t)) {
119
+ if (textGuard.hasCodepointThreat(t, { zeroWidth: "reject" })) {
128
120
  throw new TypeError("orderNotes: tags[" + i + "] contains control / zero-width bytes");
129
121
  }
130
122
  out.push(t);
@@ -139,9 +131,7 @@ function _resolution(s) {
139
131
  if (s.length > MAX_RESOLUTION_LEN) {
140
132
  throw new TypeError("orderNotes: resolution must be <= " + MAX_RESOLUTION_LEN + " characters");
141
133
  }
142
- if (CONTROL_BYTE_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
143
- throw new TypeError("orderNotes: resolution contains control / zero-width bytes");
144
- }
134
+ textGuard.freeText(s, "orderNotes: resolution", { zeroWidth: "reject" });
145
135
  return s;
146
136
  }
147
137
 
@@ -74,6 +74,7 @@
74
74
  */
75
75
 
76
76
  var b = require("./vendor/blamejs");
77
+ var textGuard = require("./text-guard");
77
78
 
78
79
  // ---- constants ----------------------------------------------------------
79
80
 
@@ -88,7 +89,6 @@ var MAX_LIST_LIMIT = 500;
88
89
  var DEFAULT_TOP_LIMIT = 10;
89
90
  var MAX_TOP_LIMIT = 100;
90
91
 
91
- var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
92
92
 
93
93
  // ---- monotonic clock ----------------------------------------------------
94
94
  //
@@ -130,9 +130,7 @@ function _comment(s) {
130
130
  if (s.length > MAX_COMMENT_LEN) {
131
131
  throw new TypeError("orderRatings: comment must be <= " + MAX_COMMENT_LEN + " chars");
132
132
  }
133
- if (CONTROL_BYTE_RE.test(s)) {
134
- throw new TypeError("orderRatings: comment must not contain control bytes");
135
- }
133
+ textGuard.freeText(s, "orderRatings: comment");
136
134
  return s;
137
135
  }
138
136
 
@@ -140,9 +138,7 @@ function _flagReason(s) {
140
138
  if (typeof s !== "string" || !s.length || s.length > MAX_FLAG_REASON_LEN) {
141
139
  throw new TypeError("orderRatings: reason must be a non-empty string <= " + MAX_FLAG_REASON_LEN + " chars");
142
140
  }
143
- if (CONTROL_BYTE_RE.test(s)) {
144
- throw new TypeError("orderRatings: reason must not contain control bytes");
145
- }
141
+ textGuard.freeText(s, "orderRatings: reason");
146
142
  return s;
147
143
  }
148
144
 
@@ -150,9 +146,7 @@ function _responseText(s) {
150
146
  if (typeof s !== "string" || !s.length || s.length > MAX_RESPONSE_LEN) {
151
147
  throw new TypeError("orderRatings: response must be a non-empty string <= " + MAX_RESPONSE_LEN + " chars");
152
148
  }
153
- if (CONTROL_BYTE_RE.test(s)) {
154
- throw new TypeError("orderRatings: response must not contain control bytes");
155
- }
149
+ textGuard.freeText(s, "orderRatings: response");
156
150
  return s;
157
151
  }
158
152
 
@@ -49,6 +49,7 @@
49
49
  */
50
50
 
51
51
  var b = require("./vendor/blamejs");
52
+ var textGuard = require("./text-guard");
52
53
 
53
54
  var PROCESSORS = ["stripe", "paypal", "square", "braintree", "authorize_net"];
54
55
  var ARCHIVE_REASONS = ["customer_request", "expired", "replaced", "fraud", "operator"];
@@ -65,7 +66,6 @@ var MAX_ACTOR_LEN = 128;
65
66
  // branch-on-this dispatch key.
66
67
  var BRAND_RE = /^[A-Za-z0-9 _.\-]{1,32}$/;
67
68
  var LAST4_RE = /^[0-9]{4}$/;
68
- var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
69
69
 
70
70
  // PAN screen: any field whose value is a string carrying 13-19
71
71
  // consecutive ASCII digits (with or without space/dash separators
@@ -125,9 +125,7 @@ function _brand(s) {
125
125
  if (s.length > MAX_BRAND_LEN) {
126
126
  throw new TypeError("paymentMethods: brand must be <= " + MAX_BRAND_LEN + " characters");
127
127
  }
128
- if (CONTROL_BYTE_RE.test(s)) {
129
- throw new TypeError("paymentMethods: brand contains control bytes");
130
- }
128
+ textGuard.freeText(s, "paymentMethods: brand", { singleLine: "reject" });
131
129
  if (!BRAND_RE.test(s)) {
132
130
  throw new TypeError("paymentMethods: brand contains characters outside [A-Za-z0-9 _.-]");
133
131
  }
@@ -142,9 +140,7 @@ function _label(s) {
142
140
  if (s.length > MAX_LABEL_LEN) {
143
141
  throw new TypeError("paymentMethods: label must be <= " + MAX_LABEL_LEN + " characters");
144
142
  }
145
- if (CONTROL_BYTE_RE.test(s)) {
146
- throw new TypeError("paymentMethods: label contains control bytes");
147
- }
143
+ textGuard.freeText(s, "paymentMethods: label", { singleLine: "reject" });
148
144
  return s;
149
145
  }
150
146
 
@@ -155,9 +151,7 @@ function _token(s) {
155
151
  if (s.length > MAX_TOKEN_LEN) {
156
152
  throw new TypeError("paymentMethods: processor_token must be <= " + MAX_TOKEN_LEN + " characters");
157
153
  }
158
- if (CONTROL_BYTE_RE.test(s)) {
159
- throw new TypeError("paymentMethods: processor_token contains control bytes");
160
- }
154
+ textGuard.freeText(s, "paymentMethods: processor_token", { singleLine: "reject" });
161
155
  return s;
162
156
  }
163
157
 
@@ -187,9 +181,7 @@ function _actor(s) {
187
181
  if (s.length > MAX_ACTOR_LEN) {
188
182
  throw new TypeError("paymentMethods: actor must be <= " + MAX_ACTOR_LEN + " characters");
189
183
  }
190
- if (CONTROL_BYTE_RE.test(s)) {
191
- throw new TypeError("paymentMethods: actor contains control bytes");
192
- }
184
+ textGuard.freeText(s, "paymentMethods: actor", { singleLine: "reject" });
193
185
  return s;
194
186
  }
195
187
 
@@ -129,6 +129,7 @@
129
129
  */
130
130
 
131
131
  var b = require("./vendor/blamejs");
132
+ var textGuard = require("./text-guard");
132
133
  var C = b.constants;
133
134
 
134
135
  // ---- constants ----------------------------------------------------------
@@ -147,10 +148,6 @@ var MS_PER_HOUR = C.TIME.hours(1);
147
148
 
148
149
  var SLUG_RE = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/;
149
150
  var FAILURE_CODE_RE = /^[a-z](?:[a-z0-9_]*[a-z0-9])?$/;
150
- var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
151
- var ZERO_WIDTH_RE = new RegExp(
152
- "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
153
- );
154
151
 
155
152
  // ---- validators ---------------------------------------------------------
156
153
 
@@ -232,9 +229,7 @@ function _reason(s) {
232
229
  if (s.length > MAX_REASON_LEN) {
233
230
  throw new TypeError("paymentRetries: reason must be <= " + MAX_REASON_LEN + " characters");
234
231
  }
235
- if (CONTROL_BYTE_RE.test(s) || ZERO_WIDTH_RE.test(s)) {
236
- throw new TypeError("paymentRetries: reason contains control / zero-width bytes");
237
- }
232
+ textGuard.freeText(s, "paymentRetries: reason", { zeroWidth: "reject" });
238
233
  return s;
239
234
  }
240
235