perimeterx-js-core 0.13.0 → 0.14.0

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 (33) hide show
  1. package/lib/cjs/config/ConfigurationBase.js +7 -0
  2. package/lib/cjs/config/defaults/DefaultCommonConfigurationParams.js +1 -5
  3. package/lib/cjs/impl/url/UrlImpl.js +27 -7
  4. package/lib/cjs/logger/LoggerBase.js +3 -0
  5. package/lib/cjs/phase/flow/PostEnforceFlow.js +1 -1
  6. package/lib/cjs/phase/impl/CreateBlockResponsePhase.js +1 -1
  7. package/lib/cjs/phase/impl/ModifyOutgoingResponsePhase.js +3 -2
  8. package/lib/cjs/phase/impl/SendLogsPhase.js +3 -1
  9. package/lib/cjs/products/bot_defender/first_party/DefaultBotDefenderFirstParty.js +45 -10
  10. package/lib/cjs/pxhd/PXHDUtils.js +7 -6
  11. package/lib/cjs/utils/constants.js +2 -2
  12. package/lib/esm/config/ConfigurationBase.js +3 -0
  13. package/lib/esm/config/defaults/DefaultCommonConfigurationParams.js +1 -5
  14. package/lib/esm/impl/url/UrlImpl.js +26 -7
  15. package/lib/esm/logger/LoggerBase.js +3 -0
  16. package/lib/esm/phase/flow/PostEnforceFlow.js +1 -1
  17. package/lib/esm/phase/impl/CreateBlockResponsePhase.js +1 -1
  18. package/lib/esm/phase/impl/ModifyOutgoingResponsePhase.js +4 -2
  19. package/lib/esm/phase/impl/SendLogsPhase.js +1 -0
  20. package/lib/esm/products/bot_defender/first_party/DefaultBotDefenderFirstParty.js +45 -10
  21. package/lib/esm/pxhd/PXHDUtils.js +7 -6
  22. package/lib/esm/utils/constants.js +2 -2
  23. package/lib/types/config/ConfigurationBase.d.ts +1 -0
  24. package/lib/types/config/IConfiguration.d.ts +4 -0
  25. package/lib/types/config/params/CommonConfigurationParams.d.ts +1 -5
  26. package/lib/types/impl/url/UrlImpl.d.ts +5 -1
  27. package/lib/types/logger/ILogger.d.ts +4 -0
  28. package/lib/types/logger/LoggerBase.d.ts +1 -0
  29. package/lib/types/phase/impl/ModifyOutgoingResponsePhase.d.ts +3 -1
  30. package/lib/types/products/bot_defender/first_party/DefaultBotDefenderFirstParty.d.ts +1 -0
  31. package/lib/types/pxhd/PXHDUtils.d.ts +1274 -3
  32. package/lib/types/utils/constants.d.ts +1 -1
  33. package/package.json +1 -1
@@ -634,6 +634,13 @@ var ConfigurationBase = /** @class */ (function () {
634
634
  enumerable: false,
635
635
  configurable: true
636
636
  });
637
+ Object.defineProperty(ConfigurationBase.prototype, "securedPxhdEnabled", {
638
+ get: function () {
639
+ return this.configParams.px_secured_pxhd_enabled;
640
+ },
641
+ enumerable: false,
642
+ configurable: true
643
+ });
637
644
  return ConfigurationBase;
638
645
  }());
639
646
  exports.ConfigurationBase = ConfigurationBase;
@@ -13,7 +13,6 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
13
13
  px_risk_cookie_max_iterations: 5000,
14
14
  px_logger_severity: logger_1.LoggerSeverity.ERROR,
15
15
  px_ip_headers: [],
16
- px_extract_ip: null,
17
16
  px_module_enabled: true,
18
17
  px_module_mode: utils_1.ModuleMode.MONITOR,
19
18
  px_additional_activity_handler: null,
@@ -21,9 +20,6 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
21
20
  px_max_activity_batch_size: 0,
22
21
  px_batch_activities_timeout_ms: 1000,
23
22
  px_bypass_monitor_header: '',
24
- px_csp_enabled: false,
25
- px_csp_no_updates_max_interval_minutes: 60,
26
- px_csp_policy_refresh_interval_minutes: 5,
27
23
  px_enforced_routes: [],
28
24
  px_first_party_enabled: true,
29
25
  px_custom_first_party_prefix: '',
@@ -104,7 +100,6 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
104
100
  px_sensitive_graphql_operation_names: [],
105
101
  px_sensitive_graphql_operation_types: [],
106
102
  px_enrich_custom_parameters: null,
107
- px_proxy_url: '',
108
103
  px_jwt_cookie_name: '',
109
104
  px_jwt_cookie_user_id_field_name: '',
110
105
  px_jwt_cookie_additional_field_names: [],
@@ -118,4 +113,5 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
118
113
  px_remote_config_max_fetch_attempts: 5,
119
114
  px_remote_config_retry_interval_ms: 1000,
120
115
  px_url_decode_reserved_characters: false,
116
+ px_secured_pxhd_enabled: false,
121
117
  };
@@ -11,15 +11,18 @@ var UrlImpl = /** @class */ (function () {
11
11
  throw new Error("Invalid UrlImpl: ".concat(rawUrl));
12
12
  }
13
13
  this.protocol = match[1];
14
- this.hostname = match[3];
15
- this.port = match[4] || '';
16
- this.pathname = match[5] || '/';
17
- this.search = match[6] || '';
18
- this.hash = match[7] || '';
14
+ this.username = match[3] || '';
15
+ this.password = match[4] || '';
16
+ this.hostname = match[6];
17
+ this.port = match[7] || '';
18
+ this.pathname = match[8] || '/';
19
+ this.search = match[9] || '';
20
+ this.hash = match[10] || '';
21
+ this.urlUtils = new CustomImplUrlUtils_1.CustomImplUrlUtils();
19
22
  }
20
23
  Object.defineProperty(UrlImpl.prototype, "href", {
21
24
  get: function () {
22
- return "".concat(this.origin).concat(this.pathname).concat(this.search).concat(this.hash);
25
+ return "".concat(this.protocol, "//").concat(this.credentials).concat(this.host).concat(this.pathname).concat(this.search).concat(this.hash);
23
26
  },
24
27
  enumerable: false,
25
28
  configurable: true
@@ -57,7 +60,7 @@ var UrlImpl = /** @class */ (function () {
57
60
  });
58
61
  Object.defineProperty(UrlImpl.prototype, "searchParams", {
59
62
  get: function () {
60
- return new UrlSearchParamsImpl_1.UrlSearchParamsImpl(new CustomImplUrlUtils_1.CustomImplUrlUtils(), this.search);
63
+ return new UrlSearchParamsImpl_1.UrlSearchParamsImpl(this.urlUtils, this.search);
61
64
  },
62
65
  enumerable: false,
63
66
  configurable: true
@@ -69,6 +72,23 @@ var UrlImpl = /** @class */ (function () {
69
72
  };
70
73
  return PROTOCOL_TO_DEFAULT_PORT[this.protocol] === port;
71
74
  };
75
+ Object.defineProperty(UrlImpl.prototype, "credentials", {
76
+ get: function () {
77
+ if (!this.username && !this.password) {
78
+ return '';
79
+ }
80
+ var credentials = '';
81
+ if (this.username) {
82
+ credentials += this.urlUtils.encodeUriComponent(this.username);
83
+ }
84
+ if (this.password) {
85
+ credentials += ":".concat(this.urlUtils.encodeUriComponent(this.password));
86
+ }
87
+ return "".concat(credentials, "@");
88
+ },
89
+ enumerable: false,
90
+ configurable: true
91
+ });
72
92
  return UrlImpl;
73
93
  }());
74
94
  exports.UrlImpl = UrlImpl;
@@ -54,6 +54,9 @@ var LoggerBase = /** @class */ (function () {
54
54
  var logRecord = __assign(__assign({}, metadata), { message: message, severity: loggerSeverity, messageTimestamp: Date.now() });
55
55
  this.logs.push(logRecord);
56
56
  };
57
+ LoggerBase.prototype.clearLogs = function () {
58
+ this.logs = [];
59
+ };
57
60
  return LoggerBase;
58
61
  }());
59
62
  exports.LoggerBase = LoggerBase;
@@ -23,7 +23,7 @@ var PostEnforceFlow = /** @class */ (function (_super) {
23
23
  var products = _a.products, activityClient = _a.activityClient;
24
24
  return _super.call(this, [
25
25
  new impl_1.EnrichContextFromResponsePhase(config, products),
26
- new impl_1.ModifyOutgoingResponsePhase(Object.values(products)),
26
+ new impl_1.ModifyOutgoingResponsePhase(config, Object.values(products)),
27
27
  new impl_1.SendAsyncActivitiesOnResponsePhase(activityClient),
28
28
  ]) || this;
29
29
  }
@@ -92,7 +92,7 @@ var CreateBlockResponsePhase = /** @class */ (function () {
92
92
  switch (_c.label) {
93
93
  case 0:
94
94
  if (!context.isMobile && ((_a = context.pxhd) === null || _a === void 0 ? void 0 : _a.source) === pxhd_1.PXHDSource.RISK) {
95
- response = pxhd_1.PXHDUtils.addPxhdToMinimalResponse(context, response);
95
+ response = pxhd_1.PXHDUtils.addPxhdToMinimalResponse(this.config, context, response);
96
96
  }
97
97
  if (!(this.config.corsSupportEnabled && ((_b = this.cors) === null || _b === void 0 ? void 0 : _b.isCorsRequest(context)))) return [3 /*break*/, 2];
98
98
  return [4 /*yield*/, this.cors.getCorsBlockHeaders(context)];
@@ -39,7 +39,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.ModifyOutgoingResponsePhase = void 0;
40
40
  var pxhd_1 = require("../../pxhd");
41
41
  var ModifyOutgoingResponsePhase = /** @class */ (function () {
42
- function ModifyOutgoingResponsePhase(products) {
42
+ function ModifyOutgoingResponsePhase(config, products) {
43
+ this.config = config;
43
44
  this.products = products;
44
45
  }
45
46
  ModifyOutgoingResponsePhase.prototype.execute = function (context) {
@@ -51,7 +52,7 @@ var ModifyOutgoingResponsePhase = /** @class */ (function () {
51
52
  case 1:
52
53
  _b.sent();
53
54
  if (((_a = context.pxhd) === null || _a === void 0 ? void 0 : _a.source) === pxhd_1.PXHDSource.RISK) {
54
- pxhd_1.PXHDUtils.addPxhdToOutgoingResponse(context, context.response);
55
+ pxhd_1.PXHDUtils.addPxhdToOutgoingResponse(this.config, context, context.response);
55
56
  }
56
57
  return [2 /*return*/, { done: false }];
57
58
  }
@@ -52,7 +52,9 @@ var SendLogsPhase = /** @class */ (function () {
52
52
  case 1:
53
53
  _a.sent();
54
54
  _a.label = 2;
55
- case 2: return [2 /*return*/, { done: false }];
55
+ case 2:
56
+ this.config.logger.clearLogs();
57
+ return [2 /*return*/, { done: false }];
56
58
  }
57
59
  });
58
60
  });
@@ -85,6 +85,9 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
85
85
  return [2 /*return*/, { defaultResponse: defaultResponse }];
86
86
  }
87
87
  url = this.getThirdPartySensorScriptUrl();
88
+ if (!url) {
89
+ return [2 /*return*/, { defaultResponse: defaultResponse }];
90
+ }
88
91
  return [4 /*yield*/, this.getOutgoingRequest(url, context)];
89
92
  case 1:
90
93
  request = _a.sent();
@@ -105,6 +108,9 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
105
108
  return [2 /*return*/, { defaultResponse: defaultResponse }];
106
109
  }
107
110
  url = this.getThirdPartyXhrUrl(context, prefix);
111
+ if (!url) {
112
+ return [2 /*return*/, { defaultResponse: defaultResponse }];
113
+ }
108
114
  return [4 /*yield*/, this.getOutgoingRequest(url, context)];
109
115
  case 1:
110
116
  request = _a.sent();
@@ -133,6 +139,9 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
133
139
  return [2 /*return*/, { defaultResponse: defaultResponse }];
134
140
  }
135
141
  url = this.getThirdPartyCaptchaScriptUrl(context);
142
+ if (!url) {
143
+ return [2 /*return*/, { defaultResponse: defaultResponse }];
144
+ }
136
145
  return [4 /*yield*/, this.getOutgoingRequest(url, context)];
137
146
  case 1:
138
147
  request = _a.sent();
@@ -147,7 +156,7 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
147
156
  return __awaiter(this, void 0, void 0, function () {
148
157
  return __generator(this, function (_b) {
149
158
  return [2 /*return*/, new http_1.OutgoingRequestImpl({
150
- url: url,
159
+ url: url.href,
151
160
  method: requestData.method,
152
161
  headers: this.prepareFirstPartyHeaders(url, requestData, vid),
153
162
  body: requestData.request.body,
@@ -173,8 +182,7 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
173
182
  return headers;
174
183
  };
175
184
  DefaultBotDefenderFirstParty.prototype.setHostHeader = function (headers, url) {
176
- var host = this.urlUtils.createUrl(url).host;
177
- headers[http_1.HOST_HEADER_NAME] = [host];
185
+ headers[http_1.HOST_HEADER_NAME] = [url.host];
178
186
  };
179
187
  DefaultBotDefenderFirstParty.prototype.setXffHeader = function (headers, ip) {
180
188
  var xffValue = headers[http_1.X_FORWARDED_FOR_HEADER_NAME] || [];
@@ -185,17 +193,44 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
185
193
  headers[constants_1.X_PX_ENFORCER_TRUE_IP_HEADER_NAME] = [ip];
186
194
  };
187
195
  DefaultBotDefenderFirstParty.prototype.getThirdPartySensorScriptUrl = function () {
188
- return "".concat(this.config.backendClientUrl, "/").concat(this.config.appId, "/main.min.js");
196
+ try {
197
+ return this.urlUtils.createUrl("".concat(this.config.backendClientUrl, "/").concat(this.config.appId, "/main.min.js"));
198
+ }
199
+ catch (e) {
200
+ this.config.logger.debug("unable to create third party sensor URL: ".concat(e));
201
+ return null;
202
+ }
189
203
  };
190
204
  DefaultBotDefenderFirstParty.prototype.getThirdPartyCaptchaScriptUrl = function (context) {
191
- var originalUrl = context.requestData.url;
192
- var _a = this.config, appId = _a.appId, backendCaptchaUrl = _a.backendCaptchaUrl;
193
- return "".concat(backendCaptchaUrl, "/").concat(appId).concat(FirstPartySuffix_1.FirstPartySuffix.CAPTCHA, ".js").concat(originalUrl.search);
205
+ try {
206
+ var originalUrl = context.requestData.url;
207
+ var _a = this.config, appId = _a.appId, backendCaptchaUrl = _a.backendCaptchaUrl;
208
+ return this.urlUtils.createUrl("".concat(backendCaptchaUrl, "/").concat(appId).concat(FirstPartySuffix_1.FirstPartySuffix.CAPTCHA, ".js").concat(originalUrl.search));
209
+ }
210
+ catch (e) {
211
+ this.config.logger.debug("unable to create third party captcha URL: ".concat(e));
212
+ return null;
213
+ }
194
214
  };
195
215
  DefaultBotDefenderFirstParty.prototype.getThirdPartyXhrUrl = function (context, prefix) {
196
- var originalUrl = context.requestData.url;
197
- var backendCollectorUrl = this.config.backendCollectorUrl;
198
- return "".concat(backendCollectorUrl).concat(originalUrl.pathname.replace(prefix, '')).concat(originalUrl.search);
216
+ try {
217
+ var originalUrl = context.requestData.url;
218
+ var pathname = originalUrl.pathname.replace(prefix, '');
219
+ var thirdPartyUrl = this.urlUtils.createUrl("".concat(this.config.backendCollectorUrl).concat(pathname).concat(originalUrl.search));
220
+ var host = this.urlUtils.createUrl(this.config.backendCollectorUrl).host;
221
+ if (!this.isValidThirdPartyUrl(thirdPartyUrl, host, pathname)) {
222
+ this.config.logger.debug("invalid third party url: ".concat(thirdPartyUrl));
223
+ return null;
224
+ }
225
+ return thirdPartyUrl;
226
+ }
227
+ catch (e) {
228
+ this.config.logger.debug("unable to create third party XHR URL: ".concat(e));
229
+ return null;
230
+ }
231
+ };
232
+ DefaultBotDefenderFirstParty.prototype.isValidThirdPartyUrl = function (url, expectedHost, expectedPath) {
233
+ return url.host.toLowerCase() === expectedHost.toLowerCase() && url.pathname.startsWith(expectedPath);
199
234
  };
200
235
  return DefaultBotDefenderFirstParty;
201
236
  }());
@@ -7,25 +7,26 @@ var PXHDUtils;
7
7
  (function (PXHDUtils) {
8
8
  PXHDUtils.PXHD_SAMESITE_VALUE = 'Lax';
9
9
  PXHDUtils.PXHD_PATH_VALUE = '/';
10
- PXHDUtils.addPxhdToOutgoingResponse = function (context, response) {
10
+ PXHDUtils.addPxhdToOutgoingResponse = function (config, context, response) {
11
11
  if (!(context === null || context === void 0 ? void 0 : context.pxhd)) {
12
12
  return;
13
13
  }
14
- var setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd);
14
+ var setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd, config.securedPxhdEnabled);
15
15
  response.headers.append(http_1.SET_COOKIE_HEADER_NAME, setPxhdCookie);
16
16
  };
17
- PXHDUtils.addPxhdToMinimalResponse = function (context, response) {
17
+ PXHDUtils.addPxhdToMinimalResponse = function (config, context, response) {
18
18
  if (context === null || context === void 0 ? void 0 : context.pxhd) {
19
- var setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd);
19
+ var setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd, config.securedPxhdEnabled);
20
20
  return http_1.MinimalResponseUtils.appendHeader(response, http_1.SET_COOKIE_HEADER_NAME, setPxhdCookie);
21
21
  }
22
22
  return response;
23
23
  };
24
- PXHDUtils.getPxhdCookieValue = function (pxhd) {
24
+ PXHDUtils.getPxhdCookieValue = function (pxhd, isSecure) {
25
25
  var value = "".concat(utils_1.PXHD_COOKIE_NAME, "=").concat(pxhd.value);
26
+ var secure = isSecure ? 'Secure' : '';
26
27
  var domain = pxhd.domain && "domain=".concat(pxhd.domain);
27
28
  var path = "path=".concat(PXHDUtils.PXHD_PATH_VALUE);
28
29
  var sameSite = "SameSite=".concat(PXHDUtils.PXHD_SAMESITE_VALUE);
29
- return [value, domain, path, sameSite].filter(Boolean).join('; ');
30
+ return [value, secure, domain, path, sameSite].filter(Boolean).join('; ');
30
31
  };
31
32
  })(PXHDUtils || (exports.PXHDUtils = PXHDUtils = {}));
@@ -12,5 +12,5 @@ exports.X_PX_BYPASS_REASON_HEADER_NAME = 'x-px-bypass-reason';
12
12
  exports.PUSH_DATA_HMAC_HEADER_NAME = 'x-px-pushdata';
13
13
  exports.PUSH_DATA_FEATURE_HEADER_NAME = 'x-px-feature';
14
14
  exports.EMAIL_ADDRESS_REGEX = /^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,7}$/;
15
- exports.URL_REGEX = /^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/;
16
- exports.CORE_MODULE_VERSION = 'JS Core 0.13.0';
15
+ exports.URL_REGEX = /^(https?\:)\/\/(([^@\s:]+):?([^@\s]*)@)?(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/;
16
+ exports.CORE_MODULE_VERSION = 'JS Core 0.14.0';
@@ -304,4 +304,7 @@ export class ConfigurationBase {
304
304
  get urlDecodeReservedCharacters() {
305
305
  return this.configParams.px_url_decode_reserved_characters;
306
306
  }
307
+ get securedPxhdEnabled() {
308
+ return this.configParams.px_secured_pxhd_enabled;
309
+ }
307
310
  }
@@ -10,7 +10,6 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
10
10
  px_risk_cookie_max_iterations: 5000,
11
11
  px_logger_severity: LoggerSeverity.ERROR,
12
12
  px_ip_headers: [],
13
- px_extract_ip: null,
14
13
  px_module_enabled: true,
15
14
  px_module_mode: ModuleMode.MONITOR,
16
15
  px_additional_activity_handler: null,
@@ -18,9 +17,6 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
18
17
  px_max_activity_batch_size: 0,
19
18
  px_batch_activities_timeout_ms: 1000,
20
19
  px_bypass_monitor_header: '',
21
- px_csp_enabled: false,
22
- px_csp_no_updates_max_interval_minutes: 60,
23
- px_csp_policy_refresh_interval_minutes: 5,
24
20
  px_enforced_routes: [],
25
21
  px_first_party_enabled: true,
26
22
  px_custom_first_party_prefix: '',
@@ -101,7 +97,6 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
101
97
  px_sensitive_graphql_operation_names: [],
102
98
  px_sensitive_graphql_operation_types: [],
103
99
  px_enrich_custom_parameters: null,
104
- px_proxy_url: '',
105
100
  px_jwt_cookie_name: '',
106
101
  px_jwt_cookie_user_id_field_name: '',
107
102
  px_jwt_cookie_additional_field_names: [],
@@ -115,4 +110,5 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
115
110
  px_remote_config_max_fetch_attempts: 5,
116
111
  px_remote_config_retry_interval_ms: 1000,
117
112
  px_url_decode_reserved_characters: false,
113
+ px_secured_pxhd_enabled: false,
118
114
  };
@@ -7,21 +7,27 @@ export class UrlImpl {
7
7
  pathname;
8
8
  search;
9
9
  hash;
10
+ username;
11
+ password;
10
12
  _port;
13
+ urlUtils;
11
14
  constructor(rawUrl) {
12
15
  const match = rawUrl.match(URL_REGEX);
13
16
  if (!match) {
14
17
  throw new Error(`Invalid UrlImpl: ${rawUrl}`);
15
18
  }
16
19
  this.protocol = match[1];
17
- this.hostname = match[3];
18
- this.port = match[4] || '';
19
- this.pathname = match[5] || '/';
20
- this.search = match[6] || '';
21
- this.hash = match[7] || '';
20
+ this.username = match[3] || '';
21
+ this.password = match[4] || '';
22
+ this.hostname = match[6];
23
+ this.port = match[7] || '';
24
+ this.pathname = match[8] || '/';
25
+ this.search = match[9] || '';
26
+ this.hash = match[10] || '';
27
+ this.urlUtils = new CustomImplUrlUtils();
22
28
  }
23
29
  get href() {
24
- return `${this.origin}${this.pathname}${this.search}${this.hash}`;
30
+ return `${this.protocol}//${this.credentials}${this.host}${this.pathname}${this.search}${this.hash}`;
25
31
  }
26
32
  get origin() {
27
33
  return `${this.protocol}//${this.host}`;
@@ -43,7 +49,7 @@ export class UrlImpl {
43
49
  }
44
50
  }
45
51
  get searchParams() {
46
- return new UrlSearchParamsImpl(new CustomImplUrlUtils(), this.search);
52
+ return new UrlSearchParamsImpl(this.urlUtils, this.search);
47
53
  }
48
54
  isDefaultPort(port) {
49
55
  const PROTOCOL_TO_DEFAULT_PORT = {
@@ -52,4 +58,17 @@ export class UrlImpl {
52
58
  };
53
59
  return PROTOCOL_TO_DEFAULT_PORT[this.protocol] === port;
54
60
  }
61
+ get credentials() {
62
+ if (!this.username && !this.password) {
63
+ return '';
64
+ }
65
+ let credentials = '';
66
+ if (this.username) {
67
+ credentials += this.urlUtils.encodeUriComponent(this.username);
68
+ }
69
+ if (this.password) {
70
+ credentials += `:${this.urlUtils.encodeUriComponent(this.password)}`;
71
+ }
72
+ return `${credentials}@`;
73
+ }
55
74
  }
@@ -45,4 +45,7 @@ export class LoggerBase {
45
45
  };
46
46
  this.logs.push(logRecord);
47
47
  }
48
+ clearLogs() {
49
+ this.logs = [];
50
+ }
48
51
  }
@@ -3,7 +3,7 @@ export class PostEnforceFlow extends CompositePhase {
3
3
  constructor(config, { products, activityClient }) {
4
4
  super([
5
5
  new EnrichContextFromResponsePhase(config, products),
6
- new ModifyOutgoingResponsePhase(Object.values(products)),
6
+ new ModifyOutgoingResponsePhase(config, Object.values(products)),
7
7
  new SendAsyncActivitiesOnResponsePhase(activityClient),
8
8
  ]);
9
9
  }
@@ -39,7 +39,7 @@ export class CreateBlockResponsePhase {
39
39
  }
40
40
  async addHeadersToResponse(response, context) {
41
41
  if (!context.isMobile && context.pxhd?.source === PXHDSource.RISK) {
42
- response = PXHDUtils.addPxhdToMinimalResponse(context, response);
42
+ response = PXHDUtils.addPxhdToMinimalResponse(this.config, context, response);
43
43
  }
44
44
  if (this.config.corsSupportEnabled && this.cors?.isCorsRequest(context)) {
45
45
  const corsHeaders = await this.cors.getCorsBlockHeaders(context);
@@ -1,13 +1,15 @@
1
1
  import { PXHDSource, PXHDUtils } from '../../pxhd';
2
2
  export class ModifyOutgoingResponsePhase {
3
+ config;
3
4
  products;
4
- constructor(products) {
5
+ constructor(config, products) {
6
+ this.config = config;
5
7
  this.products = products;
6
8
  }
7
9
  async execute(context) {
8
10
  await Promise.all(this.products.map((product) => product?.modifyOutgoingResponse(context)));
9
11
  if (context.pxhd?.source === PXHDSource.RISK) {
10
- PXHDUtils.addPxhdToOutgoingResponse(context, context.response);
12
+ PXHDUtils.addPxhdToOutgoingResponse(this.config, context, context.response);
11
13
  }
12
14
  return { done: false };
13
15
  }
@@ -9,6 +9,7 @@ export class SendLogsPhase {
9
9
  if (context.shouldSendLogs) {
10
10
  await this.logServiceClient.sendLogs(context, this.config.logger.getLogs());
11
11
  }
12
+ this.config.logger.clearLogs();
12
13
  return { done: false };
13
14
  }
14
15
  }
@@ -31,6 +31,9 @@ export class DefaultBotDefenderFirstParty {
31
31
  return { defaultResponse };
32
32
  }
33
33
  const url = this.getThirdPartySensorScriptUrl();
34
+ if (!url) {
35
+ return { defaultResponse };
36
+ }
34
37
  const request = await this.getOutgoingRequest(url, context);
35
38
  this.config.logger.debug(`proxying first party sensor script ${context.requestData.url.pathname} to ${url}`);
36
39
  return { request, defaultResponse };
@@ -41,6 +44,9 @@ export class DefaultBotDefenderFirstParty {
41
44
  return { defaultResponse };
42
45
  }
43
46
  const url = this.getThirdPartyXhrUrl(context, prefix);
47
+ if (!url) {
48
+ return { defaultResponse };
49
+ }
44
50
  const request = await this.getOutgoingRequest(url, context);
45
51
  this.config.logger.debug(`proxying first party XHR request ${context.requestData.url.pathname} to ${url}`);
46
52
  return { request, defaultResponse };
@@ -59,13 +65,16 @@ export class DefaultBotDefenderFirstParty {
59
65
  return { defaultResponse };
60
66
  }
61
67
  const url = this.getThirdPartyCaptchaScriptUrl(context);
68
+ if (!url) {
69
+ return { defaultResponse };
70
+ }
62
71
  const request = await this.getOutgoingRequest(url, context);
63
72
  this.config.logger.debug(`proxying first party captcha script ${context.requestData.url.pathname} to ${url}`);
64
73
  return { request, defaultResponse };
65
74
  }
66
75
  async getOutgoingRequest(url, { requestData, vid }) {
67
76
  return new OutgoingRequestImpl({
68
- url: url,
77
+ url: url.href,
69
78
  method: requestData.method,
70
79
  headers: this.prepareFirstPartyHeaders(url, requestData, vid),
71
80
  body: requestData.request.body,
@@ -89,8 +98,7 @@ export class DefaultBotDefenderFirstParty {
89
98
  return headers;
90
99
  }
91
100
  setHostHeader(headers, url) {
92
- const { host } = this.urlUtils.createUrl(url);
93
- headers[HOST_HEADER_NAME] = [host];
101
+ headers[HOST_HEADER_NAME] = [url.host];
94
102
  }
95
103
  setXffHeader(headers, ip) {
96
104
  const xffValue = headers[X_FORWARDED_FOR_HEADER_NAME] || [];
@@ -101,16 +109,43 @@ export class DefaultBotDefenderFirstParty {
101
109
  headers[X_PX_ENFORCER_TRUE_IP_HEADER_NAME] = [ip];
102
110
  }
103
111
  getThirdPartySensorScriptUrl() {
104
- return `${this.config.backendClientUrl}/${this.config.appId}/main.min.js`;
112
+ try {
113
+ return this.urlUtils.createUrl(`${this.config.backendClientUrl}/${this.config.appId}/main.min.js`);
114
+ }
115
+ catch (e) {
116
+ this.config.logger.debug(`unable to create third party sensor URL: ${e}`);
117
+ return null;
118
+ }
105
119
  }
106
120
  getThirdPartyCaptchaScriptUrl(context) {
107
- const originalUrl = context.requestData.url;
108
- const { appId, backendCaptchaUrl } = this.config;
109
- return `${backendCaptchaUrl}/${appId}${FirstPartySuffix.CAPTCHA}.js${originalUrl.search}`;
121
+ try {
122
+ const originalUrl = context.requestData.url;
123
+ const { appId, backendCaptchaUrl } = this.config;
124
+ return this.urlUtils.createUrl(`${backendCaptchaUrl}/${appId}${FirstPartySuffix.CAPTCHA}.js${originalUrl.search}`);
125
+ }
126
+ catch (e) {
127
+ this.config.logger.debug(`unable to create third party captcha URL: ${e}`);
128
+ return null;
129
+ }
110
130
  }
111
131
  getThirdPartyXhrUrl(context, prefix) {
112
- const originalUrl = context.requestData.url;
113
- const { backendCollectorUrl } = this.config;
114
- return `${backendCollectorUrl}${originalUrl.pathname.replace(prefix, '')}${originalUrl.search}`;
132
+ try {
133
+ const originalUrl = context.requestData.url;
134
+ const pathname = originalUrl.pathname.replace(prefix, '');
135
+ const thirdPartyUrl = this.urlUtils.createUrl(`${this.config.backendCollectorUrl}${pathname}${originalUrl.search}`);
136
+ const { host } = this.urlUtils.createUrl(this.config.backendCollectorUrl);
137
+ if (!this.isValidThirdPartyUrl(thirdPartyUrl, host, pathname)) {
138
+ this.config.logger.debug(`invalid third party url: ${thirdPartyUrl}`);
139
+ return null;
140
+ }
141
+ return thirdPartyUrl;
142
+ }
143
+ catch (e) {
144
+ this.config.logger.debug(`unable to create third party XHR URL: ${e}`);
145
+ return null;
146
+ }
147
+ }
148
+ isValidThirdPartyUrl(url, expectedHost, expectedPath) {
149
+ return url.host.toLowerCase() === expectedHost.toLowerCase() && url.pathname.startsWith(expectedPath);
115
150
  }
116
151
  }
@@ -4,25 +4,26 @@ export var PXHDUtils;
4
4
  (function (PXHDUtils) {
5
5
  PXHDUtils.PXHD_SAMESITE_VALUE = 'Lax';
6
6
  PXHDUtils.PXHD_PATH_VALUE = '/';
7
- PXHDUtils.addPxhdToOutgoingResponse = (context, response) => {
7
+ PXHDUtils.addPxhdToOutgoingResponse = (config, context, response) => {
8
8
  if (!context?.pxhd) {
9
9
  return;
10
10
  }
11
- const setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd);
11
+ const setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd, config.securedPxhdEnabled);
12
12
  response.headers.append(SET_COOKIE_HEADER_NAME, setPxhdCookie);
13
13
  };
14
- PXHDUtils.addPxhdToMinimalResponse = (context, response) => {
14
+ PXHDUtils.addPxhdToMinimalResponse = (config, context, response) => {
15
15
  if (context?.pxhd) {
16
- const setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd);
16
+ const setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd, config.securedPxhdEnabled);
17
17
  return MinimalResponseUtils.appendHeader(response, SET_COOKIE_HEADER_NAME, setPxhdCookie);
18
18
  }
19
19
  return response;
20
20
  };
21
- PXHDUtils.getPxhdCookieValue = (pxhd) => {
21
+ PXHDUtils.getPxhdCookieValue = (pxhd, isSecure) => {
22
22
  const value = `${PXHD_COOKIE_NAME}=${pxhd.value}`;
23
+ const secure = isSecure ? 'Secure' : '';
23
24
  const domain = pxhd.domain && `domain=${pxhd.domain}`;
24
25
  const path = `path=${PXHDUtils.PXHD_PATH_VALUE}`;
25
26
  const sameSite = `SameSite=${PXHDUtils.PXHD_SAMESITE_VALUE}`;
26
- return [value, domain, path, sameSite].filter(Boolean).join('; ');
27
+ return [value, secure, domain, path, sameSite].filter(Boolean).join('; ');
27
28
  };
28
29
  })(PXHDUtils || (PXHDUtils = {}));
@@ -9,5 +9,5 @@ export const X_PX_BYPASS_REASON_HEADER_NAME = 'x-px-bypass-reason';
9
9
  export const PUSH_DATA_HMAC_HEADER_NAME = 'x-px-pushdata';
10
10
  export const PUSH_DATA_FEATURE_HEADER_NAME = 'x-px-feature';
11
11
  export const EMAIL_ADDRESS_REGEX = /^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,7}$/;
12
- export const URL_REGEX = /^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/;
13
- export const CORE_MODULE_VERSION = 'JS Core 0.13.0';
12
+ export const URL_REGEX = /^(https?\:)\/\/(([^@\s:]+):?([^@\s]*)@)?(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/;
13
+ export const CORE_MODULE_VERSION = 'JS Core 0.14.0';
@@ -97,4 +97,5 @@ export declare abstract class ConfigurationBase<Req, Res, ParamsType extends Con
97
97
  get remoteConfigRetryIntervalMs(): number;
98
98
  get remoteConfigMaxFetchAttempts(): number;
99
99
  get urlDecodeReservedCharacters(): boolean;
100
+ get securedPxhdEnabled(): boolean;
100
101
  }
@@ -337,6 +337,10 @@ export interface IConfiguration<Req, Res, ParamsType extends ConfigurationParams
337
337
  * Whether reserved URL characters should be percent-decoded when parsing the incoming request URL.
338
338
  */
339
339
  readonly urlDecodeReservedCharacters: boolean;
340
+ /**
341
+ * Whether to add the Secure attribute when setting the PXHD cookie.
342
+ */
343
+ readonly securedPxhdEnabled: boolean;
340
344
  /**
341
345
  * Returns an object representation of the current configuration.
342
346
  */