@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.
- package/CHANGELOG.md +4 -0
- package/README.md +1 -0
- package/SECURITY.md +17 -0
- package/lib/addresses.js +2 -2
- package/lib/admin.js +49 -45
- 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 +4 -20
- package/lib/cms-blocks.js +4 -7
- package/lib/compliance-export.js +4 -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 +25 -14
- package/lib/email-warmup.js +2 -7
- package/lib/experiments.js +6 -10
- package/lib/externaldb-d1.js +78 -44
- 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 +3 -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/payment.js +103 -58
- package/lib/pixel-events.js +8 -23
- 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/referrals.js +10 -6
- 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 +125 -70
- package/lib/seller-signup.js +10 -26
- package/lib/sidebar-widgets.js +6 -15
- package/lib/site-redirects.js +49 -137
- 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 +21 -23
- 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 +137 -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 +107 -97
- package/lib/vendor/blamejs/CHANGELOG.md +180 -0
- package/lib/vendor/blamejs/NOTICE +14 -10
- package/lib/vendor/blamejs/README.md +17 -5
- package/lib/vendor/blamejs/index.js +2 -0
- package/lib/vendor/blamejs/lib/acme.js +9 -9
- package/lib/vendor/blamejs/lib/api-snapshot.js +35 -16
- package/lib/vendor/blamejs/lib/app-shutdown.js +1 -1
- package/lib/vendor/blamejs/lib/archive-adapters.js +1 -1
- package/lib/vendor/blamejs/lib/archive.js +13 -24
- package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -1
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +3 -6
- package/lib/vendor/blamejs/lib/auth/ciba.js +32 -3
- package/lib/vendor/blamejs/lib/auth/oauth.js +65 -8
- package/lib/vendor/blamejs/lib/auth/password.js +1 -1
- package/lib/vendor/blamejs/lib/auth/saml.js +21 -3
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +3 -3
- package/lib/vendor/blamejs/lib/backup/index.js +5 -5
- package/lib/vendor/blamejs/lib/case-fold-classes.js +491 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +131 -0
- package/lib/vendor/blamejs/lib/constants.js +79 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +8 -2
- package/lib/vendor/blamejs/lib/error-page.js +20 -1
- package/lib/vendor/blamejs/lib/external-db.js +5 -1
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +24 -6
- package/lib/vendor/blamejs/lib/flag.js +28 -2
- package/lib/vendor/blamejs/lib/framework-schema.js +14 -3
- package/lib/vendor/blamejs/lib/guard-regex.js +2345 -89
- package/lib/vendor/blamejs/lib/http-client.js +217 -40
- package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
- package/lib/vendor/blamejs/lib/mail-dav.js +26 -26
- package/lib/vendor/blamejs/lib/mail-deploy.js +10 -10
- package/lib/vendor/blamejs/lib/mail-scan.js +3 -0
- package/lib/vendor/blamejs/lib/mail-store.js +7 -1
- package/lib/vendor/blamejs/lib/mail.js +5 -1
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/compression.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/cors.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +5 -5
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/dpop.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +17 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +10 -8
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -1
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
- package/lib/vendor/blamejs/lib/network-dns.js +3 -3
- package/lib/vendor/blamejs/lib/network-nts.js +35 -14
- package/lib/vendor/blamejs/lib/network-proxy.js +42 -4
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
- package/lib/vendor/blamejs/lib/network-tls.js +341 -11
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +2 -2
- package/lib/vendor/blamejs/lib/ntp-check.js +30 -19
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/gcs.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/http-put.js +2 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -5
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +2 -2
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +4 -2
- package/lib/vendor/blamejs/lib/openapi.js +1 -1
- package/lib/vendor/blamejs/lib/pqc-agent.js +4 -7
- package/lib/vendor/blamejs/lib/problem-details.js +2 -1
- package/lib/vendor/blamejs/lib/redis-client.js +12 -1
- package/lib/vendor/blamejs/lib/regex-linear.js +1349 -0
- package/lib/vendor/blamejs/lib/render.js +531 -7
- package/lib/vendor/blamejs/lib/request-helpers.js +181 -5
- package/lib/vendor/blamejs/lib/router.js +37 -13
- package/lib/vendor/blamejs/lib/safe-async.js +142 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +3 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +15 -3
- package/lib/vendor/blamejs/lib/self-update.js +2 -2
- package/lib/vendor/blamejs/lib/testing.js +54 -7
- package/lib/vendor/blamejs/lib/validate-opts.js +56 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +62 -25
- package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +2004 -142
- package/lib/vendor/blamejs/lib/vendor/browser/noble-ciphers.mjs +727 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-hashes.mjs +1070 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-post-quantum.mjs +1090 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +510 -227
- package/lib/vendor/blamejs/lib/webhook-hmac.js +19 -2
- package/lib/vendor/blamejs/lib/webhook.js +1 -1
- package/lib/vendor/blamejs/lib/ws-client.js +24 -2
- 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 +46 -83
- package/lib/webhook-subscriptions.js +3 -13
- package/lib/wishlist-sharing.js +5 -10
- package/package.json +1 -1
package/lib/payment.js
CHANGED
|
@@ -29,26 +29,21 @@
|
|
|
29
29
|
* token can't smuggle unsigned events into the order pipeline.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
var nodeHttps = require("node:https"); // allow:non-shop-require — the PSP TLS agent must be a real node https.Agent for the vendored httpClient's caller-agent (h1) path; no b.* primitive constructs one, and the framework's downgrade prescription is an explicitly-built agent in the consumer's diff
|
|
33
|
-
|
|
34
32
|
var b = require("./vendor/blamejs");
|
|
35
33
|
var C = b.constants;
|
|
36
34
|
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// (
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
// the processor edges negotiate ML-KEM hybrid groups.
|
|
50
|
-
var _PSP_TLS_AGENT = new nodeHttps.Agent({ keepAlive: true, minVersion: "TLSv1.3" });
|
|
51
|
-
|
|
35
|
+
// The processor dials carry no caller-supplied TLS agent. They used to: the
|
|
36
|
+
// framework's outbound group list was once hybrid-only, and api.stripe.com
|
|
37
|
+
// answered that ClientHello with handshake_failure (TLS alert 40), so these
|
|
38
|
+
// three dials pinned an explicitly-built agent on Node's default groups to
|
|
39
|
+
// keep checkout working. That list now ends in classical X25519 as a
|
|
40
|
+
// last-resort floor, so the framework's own posture already negotiates with
|
|
41
|
+
// both processors, and the pinned agent had become a downgrade rather than a
|
|
42
|
+
// workaround — it forced classical key exchange on a peer that offers an
|
|
43
|
+
// ML-KEM hybrid, and it bypassed the client's post-handshake downgrade
|
|
44
|
+
// auditing. Without an agent the vendored client builds its own through
|
|
45
|
+
// b.pqcAgent, which locks the key exchange to b.network.tls.outboundPosture()
|
|
46
|
+
// and therefore follows b.network.tls.preferredGroups.set(...) at runtime.
|
|
52
47
|
var STRIPE_API_BASE_DEFAULT = "https://api.stripe.com/v1";
|
|
53
48
|
var STRIPE_WEBHOOK_TOLERANCE = 300; // ± 5 minutes (Stripe default)
|
|
54
49
|
var STRIPE_HTTP_TIMEOUT_MS = 15000;
|
|
@@ -330,7 +325,6 @@ async function _stripeCall(opts, method, path, params, idempotencyKey) {
|
|
|
330
325
|
headers: headers,
|
|
331
326
|
body: body || undefined,
|
|
332
327
|
timeoutMs: opts.timeoutMs || STRIPE_HTTP_TIMEOUT_MS,
|
|
333
|
-
agent: _PSP_TLS_AGENT,
|
|
334
328
|
allowedHosts: [opts._allowedHost],
|
|
335
329
|
});
|
|
336
330
|
var text = res.body && res.body.toString ? res.body.toString("utf8") : "";
|
|
@@ -832,7 +826,12 @@ function stripe(opts) {
|
|
|
832
826
|
var PAYPAL_API_BASE_LIVE = "https://api-m.paypal.com";
|
|
833
827
|
var PAYPAL_API_BASE_SANDBOX = "https://api-m.sandbox.paypal.com";
|
|
834
828
|
var PAYPAL_HTTP_TIMEOUT_MS = 15000;
|
|
835
|
-
|
|
829
|
+
// How far before expiry the token manager re-fetches. Two minutes rather than
|
|
830
|
+
// the primitive's default minute, so a token cannot expire mid-checkout on a
|
|
831
|
+
// slow Orders-v2 round trip.
|
|
832
|
+
// allow:raw-time-literal — clientCredentialsManager takes refreshSkewSec in
|
|
833
|
+
// SECONDS; C.TIME returns milliseconds and cannot express this argument.
|
|
834
|
+
var PAYPAL_TOKEN_SKEW_SEC = 120;
|
|
836
835
|
|
|
837
836
|
// PayPal rejects decimal places for these currencies; everything else is
|
|
838
837
|
// 2-decimal. Amounts cross the wire as decimal strings in MAJOR units.
|
|
@@ -896,43 +895,88 @@ function _headerCI(headers, name) {
|
|
|
896
895
|
return undefined;
|
|
897
896
|
}
|
|
898
897
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
898
|
+
// The PayPal client-credentials bearer token, through b.auth.oauth.
|
|
899
|
+
//
|
|
900
|
+
// The manager owns the whole grant: a cached token, ONE shared in-flight fetch
|
|
901
|
+
// so a burst of checkouts on a cold cache mints one token rather than one
|
|
902
|
+
// each, a re-fetch before expiry, and a backoff window on a 429 during which
|
|
903
|
+
// the still-valid cached token is served instead of hammering the endpoint.
|
|
904
|
+
// PayPal rate-limits the token endpoint well below the rate a checkout burst
|
|
905
|
+
// reaches, so each of those matters.
|
|
906
|
+
//
|
|
907
|
+
// It dials through THIS adapter's client rather than reaching for its own, so
|
|
908
|
+
// the exchange keeps everything the Orders-v2 calls have: the per-upstream
|
|
909
|
+
// circuit breaker, the bounded retry, the SSRF host pin, and the injected
|
|
910
|
+
// opts.httpClient the tests drive. That seam is the reason this composes at
|
|
911
|
+
// all — without it the token endpoint would be the one dial the circuit
|
|
912
|
+
// protecting live checkouts could not see, which is exactly backwards, since
|
|
913
|
+
// its failure is the clearest signal the upstream is unhealthy.
|
|
914
|
+
//
|
|
915
|
+
// PayPal requires HTTP Basic client authentication. client_secret_basic also
|
|
916
|
+
// removes the secret from the request body rather than sending it twice.
|
|
917
|
+
function _paypalTokenManager(opts, state) {
|
|
918
|
+
if (state.tokenManager) return state.tokenManager;
|
|
919
|
+
var httpClient = opts.httpClient || b.httpClient;
|
|
920
|
+
var apiBase = _paypalApiBase(opts);
|
|
921
|
+
var allowedHost = _paypalAllowedHost(opts);
|
|
922
|
+
|
|
923
|
+
// Re-asking for a token is always safe, so the exchange takes the retry arm
|
|
924
|
+
// of the dial alongside the breaker. Request options the caller sets win
|
|
925
|
+
// over these defaults — the framework's host pin wraps this client and
|
|
926
|
+
// disables redirect following per request, and that must not be overwritten.
|
|
927
|
+
//
|
|
928
|
+
// The two layers want opposite things from a failed exchange, so the dial
|
|
929
|
+
// gives each what it needs. A breaker only counts what THROWS, and
|
|
930
|
+
// b.httpClient resolves a 500 as an ordinary response, so a non-2xx has to
|
|
931
|
+
// be raised inside the dial or the token endpoint becomes invisible to the
|
|
932
|
+
// circuit. The OAuth client, meanwhile, wants the response itself so it can
|
|
933
|
+
// build its own typed error and read the reason PayPal gave. So the throw
|
|
934
|
+
// drives the retry and the breaker, and the response is handed back at the
|
|
935
|
+
// boundary once they are done with it. `statusCode` on the raised error is
|
|
936
|
+
// what makes the retry skip a 4xx and ride out a 5xx, while the breaker
|
|
937
|
+
// counts both — a sustained stream of 4xx is its own kind of unhealthy.
|
|
938
|
+
var dialer = {
|
|
939
|
+
request: function (reqOpts) {
|
|
940
|
+
var received = null;
|
|
941
|
+
return _dial(opts._breaker, true, async function () {
|
|
942
|
+
var res = await httpClient.request(Object.assign({
|
|
943
|
+
timeoutMs: opts.timeoutMs || PAYPAL_HTTP_TIMEOUT_MS,
|
|
944
|
+
allowedHosts: [allowedHost],
|
|
945
|
+
}, reqOpts));
|
|
946
|
+
received = res;
|
|
947
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
948
|
+
var err = new Error("paypal: token endpoint returned HTTP " + res.statusCode);
|
|
949
|
+
err.statusCode = res.statusCode;
|
|
950
|
+
throw err;
|
|
951
|
+
}
|
|
952
|
+
return res;
|
|
953
|
+
}).catch(function (e) {
|
|
954
|
+
// A response we actually received goes back to the OAuth client, which
|
|
955
|
+
// turns it into auth-oauth/token-error-<status> — the code its own
|
|
956
|
+
// backoff classifier reads. Anything else (a transport failure, or an
|
|
957
|
+
// open circuit that never dialled) is a real throw and propagates.
|
|
958
|
+
if (received && e && e.statusCode === received.statusCode) return received;
|
|
959
|
+
throw e;
|
|
960
|
+
});
|
|
961
|
+
},
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
var oauth = b.auth.oauth.create({
|
|
965
|
+
issuer: apiBase,
|
|
966
|
+
clientId: opts.clientId,
|
|
967
|
+
clientSecret: opts.secret,
|
|
968
|
+
tokenEndpoint: apiBase + "/v1/oauth2/token",
|
|
969
|
+
tokenEndpointAuthMethod: "client_secret_basic",
|
|
970
|
+
http: { client: dialer, allowedHosts: [allowedHost] },
|
|
931
971
|
});
|
|
932
|
-
state.
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
return state.
|
|
972
|
+
state.tokenManager = oauth.clientCredentialsManager({
|
|
973
|
+
refreshSkewSec: PAYPAL_TOKEN_SKEW_SEC,
|
|
974
|
+
});
|
|
975
|
+
return state.tokenManager;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function _paypalToken(opts, state) {
|
|
979
|
+
return _paypalTokenManager(opts, state).getToken();
|
|
936
980
|
}
|
|
937
981
|
|
|
938
982
|
// `breaker` selects which circuit the dial rides — every payment call rides
|
|
@@ -970,7 +1014,6 @@ async function _paypalCall(opts, state, method, path, bodyObj, requestId, breake
|
|
|
970
1014
|
headers: headers,
|
|
971
1015
|
body: body,
|
|
972
1016
|
timeoutMs: opts.timeoutMs || PAYPAL_HTTP_TIMEOUT_MS,
|
|
973
|
-
agent: _PSP_TLS_AGENT,
|
|
974
1017
|
allowedHosts: [allowedHost],
|
|
975
1018
|
});
|
|
976
1019
|
var text = res.body && res.body.toString ? res.body.toString("utf8") : "";
|
|
@@ -1021,8 +1064,10 @@ function paypal(opts) {
|
|
|
1021
1064
|
var state = {
|
|
1022
1065
|
query: opts.query || null,
|
|
1023
1066
|
now: typeof opts.now === "function" ? opts.now : function () { return Date.now(); },
|
|
1024
|
-
token
|
|
1025
|
-
|
|
1067
|
+
// Built on first use and kept for the adapter's lifetime — the token
|
|
1068
|
+
// cache, the shared in-flight fetch and the 429 backoff all live on it,
|
|
1069
|
+
// so a per-call manager would throw every one of them away.
|
|
1070
|
+
tokenManager: null,
|
|
1026
1071
|
};
|
|
1027
1072
|
|
|
1028
1073
|
function _maybeIdempotent(operation, idempotencyKey, requestObj, doCall) {
|
package/lib/pixel-events.js
CHANGED
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
var nodeCrypto = require("node:crypto"); // allow:non-shop-require allow:weak-hash-sha2 — Provider spec (Meta CAPI, Google EC, TikTok Events, Pinterest CAPI, Snap CAPI) mandates SHA-256 of the normalised identifier; blamejs.crypto exposes SHA3-512 / SHAKE256 / namespaceHash, none of which match the wire format ad platforms accept. Node's stdlib SHA-256 hash is the minimum-surface route.
|
|
100
100
|
|
|
101
101
|
var b = require("./vendor/blamejs");
|
|
102
|
+
var textGuard = require("./text-guard");
|
|
102
103
|
var C = b.constants;
|
|
103
104
|
|
|
104
105
|
// ---- constants ----------------------------------------------------------
|
|
@@ -132,10 +133,6 @@ var CURRENCY_RE = /^[A-Z]{3}$/;
|
|
|
132
133
|
var UA_CLASS_RE = /^[a-z0-9._-]+$/;
|
|
133
134
|
var IP_HASH_RE = /^[0-9a-f]+$/;
|
|
134
135
|
var SHA256_HEX_RE = /^[0-9a-f]{64}$/;
|
|
135
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
136
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
137
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
138
|
-
);
|
|
139
136
|
// Conservative email shape — operator-facing storefronts already
|
|
140
137
|
// validated this via the framework's address-input primitive; the
|
|
141
138
|
// gate here is a defensive last-line check before we normalise +
|
|
@@ -218,9 +215,7 @@ function _pixelId(s) {
|
|
|
218
215
|
if (s.length > MAX_PIXEL_ID_LEN) {
|
|
219
216
|
throw new TypeError("pixelEvents: pixel_id must be <= " + MAX_PIXEL_ID_LEN + " characters");
|
|
220
217
|
}
|
|
221
|
-
|
|
222
|
-
throw new TypeError("pixelEvents: pixel_id contains control / zero-width bytes");
|
|
223
|
-
}
|
|
218
|
+
textGuard.freeText(s, "pixelEvents: pixel_id", { zeroWidth: "reject" });
|
|
224
219
|
return s;
|
|
225
220
|
}
|
|
226
221
|
|
|
@@ -231,9 +226,7 @@ function _accessToken(s) {
|
|
|
231
226
|
if (s.length > MAX_ACCESS_TOKEN_LEN) {
|
|
232
227
|
throw new TypeError("pixelEvents: access_token must be <= " + MAX_ACCESS_TOKEN_LEN + " characters");
|
|
233
228
|
}
|
|
234
|
-
|
|
235
|
-
throw new TypeError("pixelEvents: access_token contains control / zero-width bytes");
|
|
236
|
-
}
|
|
229
|
+
textGuard.freeText(s, "pixelEvents: access_token", { zeroWidth: "reject" });
|
|
237
230
|
return s;
|
|
238
231
|
}
|
|
239
232
|
|
|
@@ -244,9 +237,7 @@ function _conversionUrl(s) {
|
|
|
244
237
|
if (s.length > MAX_URL_LEN) {
|
|
245
238
|
throw new TypeError("pixelEvents: conversion_url must be <= " + MAX_URL_LEN + " characters");
|
|
246
239
|
}
|
|
247
|
-
|
|
248
|
-
throw new TypeError("pixelEvents: conversion_url contains control / zero-width bytes");
|
|
249
|
-
}
|
|
240
|
+
textGuard.freeText(s, "pixelEvents: conversion_url", { zeroWidth: "reject" });
|
|
250
241
|
// Every supported provider's conversion API is public HTTPS — refuse
|
|
251
242
|
// anything else so an operator typo can't egress conversion data in
|
|
252
243
|
// plaintext.
|
|
@@ -276,9 +267,7 @@ function _eventSourceUrl(s) {
|
|
|
276
267
|
if (s.length > MAX_URL_LEN) {
|
|
277
268
|
throw new TypeError("pixelEvents: event_source_url must be <= " + MAX_URL_LEN + " characters");
|
|
278
269
|
}
|
|
279
|
-
|
|
280
|
-
throw new TypeError("pixelEvents: event_source_url contains control / zero-width bytes");
|
|
281
|
-
}
|
|
270
|
+
textGuard.freeText(s, "pixelEvents: event_source_url", { zeroWidth: "reject" });
|
|
282
271
|
if (!/^https?:\/\//i.test(s)) {
|
|
283
272
|
throw new TypeError("pixelEvents: event_source_url must be an absolute http(s):// URL");
|
|
284
273
|
}
|
|
@@ -293,9 +282,7 @@ function _email(s) {
|
|
|
293
282
|
if (s.length > MAX_EMAIL_LEN) {
|
|
294
283
|
throw new TypeError("pixelEvents: customer_email must be <= " + MAX_EMAIL_LEN + " characters");
|
|
295
284
|
}
|
|
296
|
-
|
|
297
|
-
throw new TypeError("pixelEvents: customer_email contains control / zero-width bytes");
|
|
298
|
-
}
|
|
285
|
+
textGuard.freeText(s, "pixelEvents: customer_email", { zeroWidth: "reject" });
|
|
299
286
|
// Validate the trimmed form — normalisation strips leading /
|
|
300
287
|
// trailing whitespace before hashing, so a " alice@example.com "
|
|
301
288
|
// input still hashes to the canonical address. Reject only when
|
|
@@ -402,7 +389,7 @@ function _reason(s, label) {
|
|
|
402
389
|
if (s.length > MAX_REASON_LEN) {
|
|
403
390
|
throw new TypeError("pixelEvents: " + label + " must be <= " + MAX_REASON_LEN + " characters");
|
|
404
391
|
}
|
|
405
|
-
if (
|
|
392
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
406
393
|
throw new TypeError("pixelEvents: " + label + " contains control / zero-width bytes");
|
|
407
394
|
}
|
|
408
395
|
return s;
|
|
@@ -420,9 +407,7 @@ function _responseBody(s) {
|
|
|
420
407
|
// already (the dispatch hook captured them); allow the full ASCII
|
|
421
408
|
// range minus the zero-width / bidi block so a malicious provider
|
|
422
409
|
// can't smuggle invisible bytes through the audit trail.
|
|
423
|
-
|
|
424
|
-
throw new TypeError("pixelEvents: response_body contains zero-width / bidi-override bytes");
|
|
425
|
-
}
|
|
410
|
+
textGuard.freeText(s, "pixelEvents: response_body", { zeroWidth: "reject" });
|
|
426
411
|
return s;
|
|
427
412
|
}
|
|
428
413
|
|
package/lib/plan-changes.js
CHANGED
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
*/
|
|
62
62
|
|
|
63
63
|
var b = require("./vendor/blamejs");
|
|
64
|
+
var textGuard = require("./text-guard");
|
|
64
65
|
|
|
65
66
|
// Observability sink for batch-path settlement failures: the scheduler
|
|
66
67
|
// reverts a due row to 'pending' and carries on when a Stripe push or a
|
|
@@ -79,10 +80,6 @@ var MAX_REASON_LEN = 280;
|
|
|
79
80
|
// can land in cancel_reason and replay into the customer profile
|
|
80
81
|
// screen, so the same "no direction-override / no invisible glyph"
|
|
81
82
|
// floor applies.
|
|
82
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
83
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
84
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
85
|
-
);
|
|
86
83
|
|
|
87
84
|
// ---- validators ---------------------------------------------------------
|
|
88
85
|
|
|
@@ -118,9 +115,7 @@ function _optReason(s) {
|
|
|
118
115
|
if (s.length > MAX_REASON_LEN) {
|
|
119
116
|
throw new TypeError("planChanges: reason must be <= " + MAX_REASON_LEN + " characters");
|
|
120
117
|
}
|
|
121
|
-
|
|
122
|
-
throw new TypeError("planChanges: reason contains control / zero-width bytes");
|
|
123
|
-
}
|
|
118
|
+
textGuard.freeText(s, "planChanges: reason", { zeroWidth: "reject" });
|
|
124
119
|
return s;
|
|
125
120
|
}
|
|
126
121
|
|
package/lib/product-qa.js
CHANGED
|
@@ -81,9 +81,9 @@ var MIN_CLEANUP_DAYS = 1;
|
|
|
81
81
|
var STATUSES = Object.freeze(["pending", "approved", "rejected"]);
|
|
82
82
|
var AUTHORS = Object.freeze(["operator", "customer", "system"]);
|
|
83
83
|
|
|
84
|
-
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
85
84
|
|
|
86
85
|
var b = require("./vendor/blamejs");
|
|
86
|
+
var textGuard = require("./text-guard");
|
|
87
87
|
var C = b.constants;
|
|
88
88
|
|
|
89
89
|
// ---- monotonic clock ---------------------------------------------------
|
|
@@ -116,7 +116,7 @@ function _body(s, label) {
|
|
|
116
116
|
if (s.length > MAX_BODY_LEN) {
|
|
117
117
|
throw new TypeError("productQA: " + label + " must be <= " + MAX_BODY_LEN + " characters");
|
|
118
118
|
}
|
|
119
|
-
if (
|
|
119
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
120
120
|
throw new TypeError("productQA: " + label + " must not contain control bytes");
|
|
121
121
|
}
|
|
122
122
|
return s;
|
|
@@ -131,9 +131,7 @@ function _reason(s) {
|
|
|
131
131
|
if (s.length > MAX_REASON_LEN) {
|
|
132
132
|
throw new TypeError("productQA: reason must be <= " + MAX_REASON_LEN + " characters");
|
|
133
133
|
}
|
|
134
|
-
|
|
135
|
-
throw new TypeError("productQA: reason must not contain control bytes");
|
|
136
|
-
}
|
|
134
|
+
textGuard.freeText(s, "productQA: reason");
|
|
137
135
|
return s;
|
|
138
136
|
}
|
|
139
137
|
|
|
@@ -167,9 +165,7 @@ function _sessionId(s) {
|
|
|
167
165
|
if (s.length > MAX_SESSION_ID_LEN) {
|
|
168
166
|
throw new TypeError("productQA: session_id must be <= " + MAX_SESSION_ID_LEN + " characters");
|
|
169
167
|
}
|
|
170
|
-
|
|
171
|
-
throw new TypeError("productQA: session_id must not contain control bytes");
|
|
172
|
-
}
|
|
168
|
+
textGuard.freeText(s, "productQA: session_id");
|
|
173
169
|
return s;
|
|
174
170
|
}
|
|
175
171
|
|
package/lib/promo-banners.js
CHANGED
|
@@ -102,15 +102,10 @@ var SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
|
|
|
102
102
|
// operator dashboards. Newlines are allowed in `body` (multi-line
|
|
103
103
|
// marketing copy is common); `headline` / `cta_label` refuse LF / CR
|
|
104
104
|
// so a single-line presentation slot can't be smuggled into.
|
|
105
|
-
var CONTROL_BYTE_LINE_RE = /[\x00-\x1f\x7f]/;
|
|
106
|
-
var CONTROL_BYTE_BLOCK_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
107
105
|
|
|
108
106
|
// Zero-width / direction-override family — mirrors the gift-options
|
|
109
107
|
// + order-notes catalogues. Spelled with \u-escapes so ESLint's
|
|
110
108
|
// no-irregular-whitespace stays happy.
|
|
111
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
112
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
113
|
-
);
|
|
114
109
|
|
|
115
110
|
var ALLOWED_PATCH_COLUMNS = Object.freeze([
|
|
116
111
|
"placement",
|
|
@@ -128,6 +123,7 @@ var ALLOWED_PATCH_COLUMNS = Object.freeze([
|
|
|
128
123
|
]);
|
|
129
124
|
|
|
130
125
|
var b = require("./vendor/blamejs");
|
|
126
|
+
var textGuard = require("./text-guard");
|
|
131
127
|
|
|
132
128
|
// ---- validators ---------------------------------------------------------
|
|
133
129
|
|
|
@@ -164,10 +160,10 @@ function _line(s, label, maxLen) {
|
|
|
164
160
|
if (typeof s !== "string" || !s.length || s.length > maxLen) {
|
|
165
161
|
throw new TypeError("promoBanners: " + label + " must be a non-empty string ≤ " + maxLen + " chars");
|
|
166
162
|
}
|
|
167
|
-
if (
|
|
163
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
168
164
|
throw new TypeError("promoBanners: " + label + " contains control bytes (incl. CR/LF)");
|
|
169
165
|
}
|
|
170
|
-
if (
|
|
166
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
171
167
|
throw new TypeError("promoBanners: " + label + " contains zero-width / direction-override characters");
|
|
172
168
|
}
|
|
173
169
|
return s;
|
|
@@ -181,10 +177,10 @@ function _block(s, label, maxLen) {
|
|
|
181
177
|
if (s.length > maxLen) {
|
|
182
178
|
throw new TypeError("promoBanners: " + label + " must be ≤ " + maxLen + " chars");
|
|
183
179
|
}
|
|
184
|
-
if (
|
|
180
|
+
if (textGuard.hasCodepointThreat(s)) {
|
|
185
181
|
throw new TypeError("promoBanners: " + label + " contains control bytes");
|
|
186
182
|
}
|
|
187
|
-
if (
|
|
183
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
188
184
|
throw new TypeError("promoBanners: " + label + " contains zero-width / direction-override characters");
|
|
189
185
|
}
|
|
190
186
|
return s;
|
|
@@ -200,9 +196,7 @@ function _ctaUrl(s) {
|
|
|
200
196
|
if (typeof s !== "string" || !s.length || s.length > MAX_CTA_URL_LEN) {
|
|
201
197
|
throw new TypeError("promoBanners: cta_url must be a non-empty string ≤ " + MAX_CTA_URL_LEN + " chars");
|
|
202
198
|
}
|
|
203
|
-
|
|
204
|
-
throw new TypeError("promoBanners: cta_url contains control / zero-width bytes");
|
|
205
|
-
}
|
|
199
|
+
textGuard.freeText(s, "promoBanners: cta_url", { singleLine: "reject", zeroWidth: "reject" });
|
|
206
200
|
if (s.charCodeAt(0) === 47 /* "/" */) {
|
|
207
201
|
// /-rooted absolute path — refuse protocol-relative `//host`
|
|
208
202
|
// (the second char being "/" turns the link into an off-site
|
|
@@ -230,9 +224,7 @@ function _imageUrl(s) {
|
|
|
230
224
|
if (typeof s !== "string" || !s.length || s.length > MAX_IMAGE_URL_LEN) {
|
|
231
225
|
throw new TypeError("promoBanners: image_url must be a non-empty string ≤ " + MAX_IMAGE_URL_LEN + " chars");
|
|
232
226
|
}
|
|
233
|
-
|
|
234
|
-
throw new TypeError("promoBanners: image_url contains control / zero-width bytes");
|
|
235
|
-
}
|
|
227
|
+
textGuard.freeText(s, "promoBanners: image_url", { singleLine: "reject", zeroWidth: "reject" });
|
|
236
228
|
if (s.charCodeAt(0) === 47 /* "/" */) {
|
|
237
229
|
if (s.length > 1 && s.charCodeAt(1) === 47) {
|
|
238
230
|
throw new TypeError("promoBanners: image_url protocol-relative `//host/...` refused — use absolute https://");
|
package/lib/purchase-orders.js
CHANGED
|
@@ -96,10 +96,6 @@ var REFERENCE_RE = /^[A-Za-z0-9][A-Za-z0-9._\/ -]{0,127}$/;
|
|
|
96
96
|
// as vendors. Operator-rendered text fields refuse these to keep the
|
|
97
97
|
// downstream dashboard / printout safe from header-injection +
|
|
98
98
|
// visual-spoofing attacks.
|
|
99
|
-
var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
100
|
-
var ZERO_WIDTH_RE = new RegExp(
|
|
101
|
-
"[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
|
|
102
|
-
);
|
|
103
99
|
|
|
104
100
|
var PO_STATUSES = Object.freeze([
|
|
105
101
|
"draft", "submitted", "confirmed", "partially_received",
|
|
@@ -117,6 +113,7 @@ var ALLOWED_UPDATE_COLUMNS = Object.freeze([
|
|
|
117
113
|
|
|
118
114
|
// Framework handle (the vendored blamejs); index.js re-exports this as .framework.
|
|
119
115
|
var b = require("./vendor/blamejs");
|
|
116
|
+
var textGuard = require("./text-guard");
|
|
120
117
|
|
|
121
118
|
// ---- validators ---------------------------------------------------------
|
|
122
119
|
|
|
@@ -212,10 +209,10 @@ function _shortText(s, label, max) {
|
|
|
212
209
|
if (s.length > max) {
|
|
213
210
|
throw new TypeError("purchase-orders: " + label + " must be <= " + max + " characters");
|
|
214
211
|
}
|
|
215
|
-
if (
|
|
212
|
+
if (textGuard.hasCodepointThreat(s, { singleLine: "reject" })) {
|
|
216
213
|
throw new TypeError("purchase-orders: " + label + " contains control bytes");
|
|
217
214
|
}
|
|
218
|
-
if (
|
|
215
|
+
if (textGuard.hasCodepointThreat(s, { zeroWidth: "reject" })) {
|
|
219
216
|
throw new TypeError("purchase-orders: " + label + " contains zero-width / direction-override bytes");
|
|
220
217
|
}
|
|
221
218
|
return s;
|
|
@@ -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" });
|
|
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" });
|
|
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]) {
|