perimeterx-js-core 0.19.0 → 0.20.1

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 (35) hide show
  1. package/lib/cjs/config/ConfigurationBase.js +14 -0
  2. package/lib/cjs/config/defaults/DefaultCommonConfigurationParams.js +3 -1
  3. package/lib/cjs/graphql/DefaultGraphQLParser.js +159 -30
  4. package/lib/cjs/graphql/ExtractGraphQLKeywordsFunction.js +2 -0
  5. package/lib/cjs/graphql/index.js +1 -0
  6. package/lib/cjs/products/credential_intelligence/endpoint/login_successful/BodyLoginSuccessfulParser.js +3 -2
  7. package/lib/cjs/products/credential_intelligence/endpoint/matcher/RegexPathEndpointMatcher.js +1 -1
  8. package/lib/cjs/telemetry/DefaultTelemetry.js +3 -3
  9. package/lib/cjs/utils/constants.js +1 -1
  10. package/lib/cjs/utils/utils.js +13 -5
  11. package/lib/esm/config/ConfigurationBase.js +6 -0
  12. package/lib/esm/config/defaults/DefaultCommonConfigurationParams.js +3 -1
  13. package/lib/esm/graphql/DefaultGraphQLParser.js +112 -25
  14. package/lib/esm/graphql/ExtractGraphQLKeywordsFunction.js +1 -0
  15. package/lib/esm/graphql/index.js +1 -0
  16. package/lib/esm/products/credential_intelligence/endpoint/login_successful/BodyLoginSuccessfulParser.js +1 -1
  17. package/lib/esm/products/credential_intelligence/endpoint/matcher/RegexPathEndpointMatcher.js +1 -1
  18. package/lib/esm/telemetry/DefaultTelemetry.js +4 -4
  19. package/lib/esm/utils/constants.js +1 -1
  20. package/lib/esm/utils/utils.js +11 -3
  21. package/lib/types/activities/utils.d.ts +60 -30
  22. package/lib/types/blocker/utils.d.ts +6 -3
  23. package/lib/types/config/ConfigurationBase.d.ts +4 -1
  24. package/lib/types/config/IConfiguration.d.ts +14 -2
  25. package/lib/types/config/params/CommonConfigurationParams.d.ts +4 -1
  26. package/lib/types/graphql/DefaultGraphQLParser.d.ts +20 -11
  27. package/lib/types/graphql/ExtractGraphQLKeywordsFunction.d.ts +1 -0
  28. package/lib/types/graphql/index.d.ts +1 -0
  29. package/lib/types/graphql/model/GraphQLData.d.ts +2 -1
  30. package/lib/types/monitored_request/MonitoredRequestUtils.d.ts +18 -9
  31. package/lib/types/pxhd/PXHDUtils.d.ts +12 -6
  32. package/lib/types/sensitive_request/SensitiveRequestUtils.d.ts +12 -6
  33. package/lib/types/utils/constants.d.ts +1 -1
  34. package/lib/types/utils/utils.d.ts +1 -1
  35. package/package.json +1 -1
@@ -427,6 +427,20 @@ var ConfigurationBase = /** @class */ (function () {
427
427
  enumerable: false,
428
428
  configurable: true
429
429
  });
430
+ Object.defineProperty(ConfigurationBase.prototype, "graphqlKeywords", {
431
+ get: function () {
432
+ return this.activeConfigParams.px_graphql_keywords;
433
+ },
434
+ enumerable: false,
435
+ configurable: true
436
+ });
437
+ Object.defineProperty(ConfigurationBase.prototype, "extractGraphQLKeywords", {
438
+ get: function () {
439
+ return this.activeConfigParams.px_extract_graphql_keywords;
440
+ },
441
+ enumerable: false,
442
+ configurable: true
443
+ });
430
444
  Object.defineProperty(ConfigurationBase.prototype, "sensitiveGraphqlOperationNames", {
431
445
  get: function () {
432
446
  return this.activeConfigParams.px_sensitive_graphql_operation_names;
@@ -19,7 +19,7 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
19
19
  px_advanced_blocking_response_enabled: true,
20
20
  px_max_activity_batch_size: 0,
21
21
  px_batch_activities_timeout_ms: 1000,
22
- px_bypass_monitor_header: '',
22
+ px_bypass_monitor_header: 'x-px-block',
23
23
  px_enforced_routes: [],
24
24
  px_first_party_enabled: true,
25
25
  px_custom_first_party_prefix: '',
@@ -97,6 +97,7 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
97
97
  px_custom_logo: '',
98
98
  px_graphql_enabled: true,
99
99
  px_graphql_routes: ['/graphql'],
100
+ px_graphql_keywords: [],
100
101
  px_sensitive_graphql_operation_names: [],
101
102
  px_sensitive_graphql_operation_types: [],
102
103
  px_enrich_custom_parameters: null,
@@ -118,4 +119,5 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
118
119
  px_custom_is_monitored_request: null,
119
120
  px_custom_is_enforced_request: null,
120
121
  px_custom_is_filtered_request: null,
122
+ px_extract_graphql_keywords: null,
121
123
  };
@@ -42,13 +42,14 @@ var http_1 = require("../http");
42
42
  var model_1 = require("./model");
43
43
  var DefaultGraphQLParser = /** @class */ (function () {
44
44
  function DefaultGraphQLParser(config) {
45
- this.graphqlRoutes = config.graphqlRoutes;
46
- this.sensitiveOperationNames = config.sensitiveGraphqlOperationNames;
47
- this.sensitiveOperationTypes = config.sensitiveGraphqlOperationTypes;
45
+ this.config = config;
46
+ this.maxCharactersInGraphqlKeyword = 100;
47
+ this.maxGraphqlKeywordCount = 500;
48
48
  }
49
49
  DefaultGraphQLParser.prototype.isGraphQLRequest = function (_a) {
50
50
  var requestData = _a.requestData;
51
- return (requestData.method === http_1.HttpMethod.POST && (0, utils_1.isRouteInPatterns)(requestData.url.pathname, this.graphqlRoutes));
51
+ return (requestData.method === http_1.HttpMethod.POST &&
52
+ (0, utils_1.isRouteInPatterns)(requestData.url.pathname, this.config.graphqlRoutes));
52
53
  };
53
54
  DefaultGraphQLParser.prototype.parseGraphQLRequest = function (context) {
54
55
  return __awaiter(this, void 0, void 0, function () {
@@ -56,7 +57,7 @@ var DefaultGraphQLParser = /** @class */ (function () {
56
57
  return __generator(this, function (_a) {
57
58
  switch (_a.label) {
58
59
  case 0:
59
- _a.trys.push([0, 2, , 3]);
60
+ _a.trys.push([0, 3, , 4]);
60
61
  requestData = context.requestData;
61
62
  return [4 /*yield*/, this.getGraphQLOperationsFromBody(requestData.request, context)];
62
63
  case 1:
@@ -65,18 +66,20 @@ var DefaultGraphQLParser = /** @class */ (function () {
65
66
  context.logger.debug('unable to get graphql operations from request body');
66
67
  return [2 /*return*/, null];
67
68
  }
68
- data = this.parseGraphQLOperations(graphQLOperations);
69
+ return [4 /*yield*/, this.parseGraphQLOperations(graphQLOperations, context)];
70
+ case 2:
71
+ data = _a.sent();
69
72
  if (!data || data.length === 0) {
70
73
  context.logger.debug('unable to parse graphql operations');
71
74
  return [2 /*return*/, null];
72
75
  }
73
76
  context.logger.debug("".concat(data.length, " graphql operation").concat(data.length === 1 ? '' : 's', " parsed successfully"));
74
77
  return [2 /*return*/, data];
75
- case 2:
78
+ case 3:
76
79
  e_1 = _a.sent();
77
80
  context.logger.debug("unable to parse graphql request: ".concat(e_1));
78
81
  return [2 /*return*/, null];
79
- case 3: return [2 /*return*/];
82
+ case 4: return [2 /*return*/];
80
83
  }
81
84
  });
82
85
  });
@@ -92,6 +95,7 @@ var DefaultGraphQLParser = /** @class */ (function () {
92
95
  case 1:
93
96
  body = _a.sent();
94
97
  if (!body) {
98
+ context.logger.debug("received empty graphql body when calling .json()");
95
99
  return [2 /*return*/, null];
96
100
  }
97
101
  return [2 /*return*/, Array.isArray(body) ? body : [body]];
@@ -104,19 +108,31 @@ var DefaultGraphQLParser = /** @class */ (function () {
104
108
  });
105
109
  });
106
110
  };
107
- DefaultGraphQLParser.prototype.parseGraphQLOperations = function (operations) {
108
- var _this = this;
109
- return operations.map(function (operation) { return _this.parseGraphQlOperation(operation); }).filter(function (x) { return x; });
111
+ DefaultGraphQLParser.prototype.parseGraphQLOperations = function (operations, context) {
112
+ return __awaiter(this, void 0, void 0, function () {
113
+ var data;
114
+ var _this = this;
115
+ return __generator(this, function (_a) {
116
+ switch (_a.label) {
117
+ case 0: return [4 /*yield*/, Promise.all(operations.map(function (operation) { return _this.parseGraphQLOperation(operation, context); }))];
118
+ case 1:
119
+ data = _a.sent();
120
+ return [2 /*return*/, data.filter(Boolean)];
121
+ }
122
+ });
123
+ });
110
124
  };
111
- DefaultGraphQLParser.prototype.parseGraphQlOperation = function (operation) {
125
+ DefaultGraphQLParser.prototype.parseGraphQLOperation = function (operation, context) {
112
126
  if (!operation.query || typeof operation.query !== 'string') {
127
+ context.logger.debug('no query found');
113
128
  return null;
114
129
  }
115
130
  var operationNameToTypeMap = this.getOperationNameToTypeMap(operation.query);
116
131
  if (!operationNameToTypeMap) {
132
+ context.logger.debug('operationNameToTypeMap returned null');
117
133
  return null;
118
134
  }
119
- return this.getGraphQLData(operationNameToTypeMap, operation);
135
+ return this.getGraphQLData(operationNameToTypeMap, operation, context);
120
136
  };
121
137
  DefaultGraphQLParser.prototype.getOperationNameToTypeMap = function (query) {
122
138
  var operationTypesString = Object.values(model_1.GraphQLOperationType).join('|');
@@ -136,28 +152,132 @@ var DefaultGraphQLParser = /** @class */ (function () {
136
152
  }
137
153
  return map;
138
154
  };
139
- DefaultGraphQLParser.prototype.getGraphQLData = function (operationNameToTypeMap, operation) {
140
- var name = operation.operationName ||
141
- (Object.keys(operationNameToTypeMap).length === 1 ? Object.keys(operationNameToTypeMap)[0] : undefined);
142
- var type = operationNameToTypeMap[name];
143
- if (!type && /^\s*{/.test(operation.query)) {
144
- type = model_1.GraphQLOperationType.QUERY;
155
+ DefaultGraphQLParser.prototype.getGraphQLData = function (operationNameToTypeMap, operation, context) {
156
+ return __awaiter(this, void 0, void 0, function () {
157
+ var name, type, data, keywords;
158
+ return __generator(this, function (_a) {
159
+ switch (_a.label) {
160
+ case 0:
161
+ name = this.getOperationName(operationNameToTypeMap, operation);
162
+ type = this.getOperationType(operation, name, operationNameToTypeMap);
163
+ if (!type) {
164
+ return [2 /*return*/, null];
165
+ }
166
+ data = { type: type };
167
+ if (name) {
168
+ data.name = name;
169
+ }
170
+ return [4 /*yield*/, this.getQueryKeywords(operation.query, context)];
171
+ case 1:
172
+ keywords = _a.sent();
173
+ if (keywords) {
174
+ data.keywords = this.cleanKeywords(keywords);
175
+ }
176
+ if (this.isSensitiveOperation(name, type, keywords)) {
177
+ data.sensitive = true;
178
+ }
179
+ if (operation.variables && typeof operation.variables === 'object') {
180
+ data.variables = this.extractGraphQLVariableNames(operation.variables);
181
+ }
182
+ return [2 /*return*/, data];
183
+ }
184
+ });
185
+ });
186
+ };
187
+ DefaultGraphQLParser.prototype.getOperationType = function (operation, operationName, operationNameToTypeMap) {
188
+ if (operationName && operationNameToTypeMap[operationName]) {
189
+ return operationNameToTypeMap[operationName];
145
190
  }
146
- if (!type) {
147
- return null;
191
+ if (this.isGraphqlQueryShorthand(operation.query)) {
192
+ return model_1.GraphQLOperationType.QUERY;
193
+ }
194
+ var match = operation.query.match(new RegExp("^\\s*(".concat(Object.values(model_1.GraphQLOperationType).join('|'), ")(?:\\s|{)")));
195
+ if ((match === null || match === void 0 ? void 0 : match[1]) && !operationName) {
196
+ return match[1];
148
197
  }
149
- var data = { name: name, type: type };
150
- if (this.isSensitiveOperation(name, type)) {
151
- data.sensitive = true;
198
+ return null;
199
+ };
200
+ DefaultGraphQLParser.prototype.isGraphqlQueryShorthand = function (query) {
201
+ return /^\s*{/.test(query);
202
+ };
203
+ DefaultGraphQLParser.prototype.getOperationName = function (operationNameToTypeMap, operation) {
204
+ var _a;
205
+ return (operation.operationName ||
206
+ (Object.keys(operationNameToTypeMap).length === 1 ? (_a = Object.keys(operationNameToTypeMap)) === null || _a === void 0 ? void 0 : _a[0] : undefined));
207
+ };
208
+ DefaultGraphQLParser.prototype.getQueryKeywords = function (query, context) {
209
+ var _a;
210
+ return __awaiter(this, void 0, void 0, function () {
211
+ var keywords;
212
+ return __generator(this, function (_b) {
213
+ switch (_b.label) {
214
+ case 0:
215
+ if (!(this.config.extractGraphQLKeywords && typeof this.config.extractGraphQLKeywords === 'function')) return [3 /*break*/, 2];
216
+ return [4 /*yield*/, this.getQueryKeywordsFromCustomFunction(query, context)];
217
+ case 1:
218
+ keywords = _b.sent();
219
+ if (Array.isArray(keywords)) {
220
+ return [2 /*return*/, keywords];
221
+ }
222
+ _b.label = 2;
223
+ case 2:
224
+ if (((_a = this.config.graphqlKeywords) === null || _a === void 0 ? void 0 : _a.length) > 0) {
225
+ return [2 /*return*/, this.getQueryKeywordsFromArray(query, context)];
226
+ }
227
+ return [2 /*return*/, null];
228
+ }
229
+ });
230
+ });
231
+ };
232
+ DefaultGraphQLParser.prototype.cleanKeywords = function (keywords) {
233
+ var _this = this;
234
+ return keywords
235
+ .slice(0, this.maxGraphqlKeywordCount)
236
+ .map(function (kw) { return kw.trim().substring(0, _this.maxCharactersInGraphqlKeyword); });
237
+ };
238
+ DefaultGraphQLParser.prototype.getQueryKeywordsFromCustomFunction = function (query, context) {
239
+ return __awaiter(this, void 0, void 0, function () {
240
+ return __generator(this, function (_a) {
241
+ try {
242
+ return [2 /*return*/, this.config.extractGraphQLKeywords(query)];
243
+ }
244
+ catch (e) {
245
+ context.logger.debug("unable to extract graphql keywords via custom function: ".concat(e));
246
+ return [2 /*return*/, null];
247
+ }
248
+ return [2 /*return*/];
249
+ });
250
+ });
251
+ };
252
+ DefaultGraphQLParser.prototype.getQueryKeywordsFromArray = function (query, context) {
253
+ var _this = this;
254
+ var keywords = [];
255
+ try {
256
+ this.config.graphqlKeywords.forEach(function (keyword) {
257
+ var pattern = _this.toGlobalRegExp(keyword);
258
+ var matchGroup = query.match(pattern);
259
+ if (!matchGroup) {
260
+ return;
261
+ }
262
+ keywords = keywords.concat(matchGroup);
263
+ });
152
264
  }
153
- if (operation.variables && typeof operation.variables === 'object') {
154
- data.variables = this.extractGraphQLVariableNames(operation.variables);
265
+ catch (e) {
266
+ context.logger.debug("unable to extract graphql keywords via array: ".concat(e));
267
+ return null;
155
268
  }
156
- return data;
269
+ return keywords;
157
270
  };
158
- DefaultGraphQLParser.prototype.isSensitiveOperation = function (operationName, operationType) {
159
- return (this.sensitiveOperationTypes.some(function (type) { return type === operationType; }) ||
160
- this.sensitiveOperationNames.some(function (name) { return name === operationName; }));
271
+ DefaultGraphQLParser.prototype.isSensitiveOperation = function (operationName, operationType, keywords) {
272
+ var _this = this;
273
+ return (this.config.sensitiveGraphqlOperationTypes.some(function (type) { return type === operationType; }) ||
274
+ this.config.sensitiveGraphqlOperationNames.some(function (name) {
275
+ var pattern = _this.toGlobalRegExp(name);
276
+ if (!pattern) {
277
+ return false;
278
+ }
279
+ return !!(operationName === null || operationName === void 0 ? void 0 : operationName.match(pattern)) || (keywords === null || keywords === void 0 ? void 0 : keywords.some(function (kw) { return !!kw.match(pattern); }));
280
+ }));
161
281
  };
162
282
  DefaultGraphQLParser.prototype.extractGraphQLVariableNames = function (variables) {
163
283
  var processVariables = function (variablesObj, prefix) {
@@ -174,6 +294,15 @@ var DefaultGraphQLParser = /** @class */ (function () {
174
294
  };
175
295
  return processVariables(variables, '');
176
296
  };
297
+ DefaultGraphQLParser.prototype.toGlobalRegExp = function (pattern) {
298
+ if (typeof pattern === 'string') {
299
+ return new RegExp(pattern, 'g');
300
+ }
301
+ if (pattern.global) {
302
+ return pattern;
303
+ }
304
+ return new RegExp(pattern, pattern.flags + 'g');
305
+ };
177
306
  return DefaultGraphQLParser;
178
307
  }());
179
308
  exports.DefaultGraphQLParser = DefaultGraphQLParser;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./model"), exports);
18
18
  __exportStar(require("./IGraphQLParser"), exports);
19
19
  __exportStar(require("./DefaultGraphQLParser"), exports);
20
+ __exportStar(require("./ExtractGraphQLKeywordsFunction"), exports);
@@ -42,10 +42,11 @@ var BodyLoginSuccessfulParser = /** @class */ (function () {
42
42
  this.bodyRegex = new RegExp(regex);
43
43
  }
44
44
  BodyLoginSuccessfulParser.prototype.isLoginSuccessful = function (response) {
45
+ var _a, _b;
45
46
  return __awaiter(this, void 0, void 0, function () {
46
- return __generator(this, function (_a) {
47
+ return __generator(this, function (_c) {
47
48
  // TODO: Possibly add IBody methods to IOutgoingResponse interface?
48
- return [2 /*return*/, this.bodyRegex.test(response.body)];
49
+ return [2 /*return*/, !!((_b = (_a = response.body) === null || _a === void 0 ? void 0 : _a.match) === null || _b === void 0 ? void 0 : _b.call(_a, this.bodyRegex))];
49
50
  });
50
51
  });
51
52
  };
@@ -8,7 +8,7 @@ var RegexPathEndpointMatcher = /** @class */ (function () {
8
8
  }
9
9
  RegexPathEndpointMatcher.prototype.matches = function (_a) {
10
10
  var method = _a.method, url = _a.url;
11
- return method === this.method && this.pathnameRegex.test(url.pathname);
11
+ return method === this.method && !!url.pathname.match(this.pathnameRegex);
12
12
  };
13
13
  return RegexPathEndpointMatcher;
14
14
  }());
@@ -143,9 +143,9 @@ var DefaultTelemetry = /** @class */ (function () {
143
143
  'px_remote_config_auth_token',
144
144
  ];
145
145
  var telemetryConfig = {
146
- active_config: (0, utils_1.removeSensitiveFields)(this.config.getActiveConfig(), SENSITIVE_CONFIG_FIELDS),
147
- static_config: (0, utils_1.removeSensitiveFields)(this.config.getStaticConfig(), SENSITIVE_CONFIG_FIELDS),
148
- remote_config: (0, utils_1.removeSensitiveFields)(this.config.getRemoteConfig(), SENSITIVE_CONFIG_FIELDS),
146
+ active_config: (0, utils_1.redactSensitiveFields)(this.config.getActiveConfig(), SENSITIVE_CONFIG_FIELDS),
147
+ static_config: (0, utils_1.redactSensitiveFields)(this.config.getStaticConfig(), SENSITIVE_CONFIG_FIELDS),
148
+ remote_config: (0, utils_1.redactSensitiveFields)(this.config.getRemoteConfig(), SENSITIVE_CONFIG_FIELDS),
149
149
  };
150
150
  var activity = {
151
151
  type: activities_1.ActivityType.ENFORCER_TELEMETRY,
@@ -13,4 +13,4 @@ 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
15
  exports.URL_REGEX = /^(https?\:)\/\/(([^@\s:]+):?([^@\s]*)@)?(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/;
16
- exports.CORE_MODULE_VERSION = 'JS Core 0.19.0';
16
+ exports.CORE_MODULE_VERSION = 'JS Core 0.20.1';
@@ -36,7 +36,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
36
36
  }
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.telemetryConfigReplacer = exports.algoToCryptoString = exports.algoToSubtleCryptoString = exports.sleep = exports.getPropertyFromObject = exports.rejectOnTimeout = exports.transferExistingProperties = exports.isRouteMatch = exports.isRouteInPatterns = exports.removeSensitiveHeaders = exports.removeSensitiveFields = exports.getExtension = exports.getAuthorizationHeader = exports.getCollectorDomain = exports.getScoreApiDomain = exports.isEmailAddress = exports.isValidUuid = exports.isValidEnumValue = void 0;
39
+ exports.telemetryConfigReplacer = exports.algoToCryptoString = exports.algoToSubtleCryptoString = exports.sleep = exports.getPropertyFromObject = exports.rejectOnTimeout = exports.transferExistingProperties = exports.isRouteMatch = exports.isRouteInPatterns = exports.removeSensitiveHeaders = exports.redactSensitiveFields = exports.getExtension = exports.getAuthorizationHeader = exports.getCollectorDomain = exports.getScoreApiDomain = exports.isEmailAddress = exports.isValidUuid = exports.isValidEnumValue = void 0;
40
40
  var http_1 = require("../http");
41
41
  var error_1 = require("./error");
42
42
  var constants_1 = require("./constants");
@@ -77,14 +77,22 @@ var getExtension = function (route) {
77
77
  return endOfPath.substring(extensionIndex);
78
78
  };
79
79
  exports.getExtension = getExtension;
80
- var removeSensitiveFields = function (object, sensitiveFields) {
80
+ var redactSensitiveFields = function (object, sensitiveFields) {
81
+ var NUMBER_OF_TRAILING_CHARS_TO_EXPOSE = 5;
82
+ var SENSITIVE_VALUE_MINIMUM_LENGTH_TO_EXPOSE_TRAILING_CHARS = NUMBER_OF_TRAILING_CHARS_TO_EXPOSE * 10;
81
83
  var newObj = Object.assign({}, object);
82
84
  sensitiveFields.forEach(function (fieldName) {
83
- delete newObj[fieldName];
85
+ var sensitiveValue = object[fieldName];
86
+ if (!sensitiveValue) {
87
+ return;
88
+ }
89
+ var trailingCharsExposed = sensitiveValue.length >= SENSITIVE_VALUE_MINIMUM_LENGTH_TO_EXPOSE_TRAILING_CHARS;
90
+ var trailingChars = sensitiveValue.substring(sensitiveValue.length - (trailingCharsExposed ? NUMBER_OF_TRAILING_CHARS_TO_EXPOSE : 0));
91
+ newObj[fieldName] = "***REDACTED***".concat(trailingChars);
84
92
  });
85
93
  return newObj;
86
94
  };
87
- exports.removeSensitiveFields = removeSensitiveFields;
95
+ exports.redactSensitiveFields = redactSensitiveFields;
88
96
  var removeSensitiveHeaders = function (headers, sensitiveHeaderNames) {
89
97
  var ret = (0, http_1.toMutableHeaders)(headers);
90
98
  sensitiveHeaderNames.forEach(function (name) {
@@ -101,7 +109,7 @@ var isRouteMatch = function (route, pattern) {
101
109
  if (!route || !pattern) {
102
110
  return false;
103
111
  }
104
- if (pattern instanceof RegExp && pattern.test(route)) {
112
+ if (pattern instanceof RegExp && !!route.match(pattern)) {
105
113
  return true;
106
114
  }
107
115
  if (typeof pattern === 'string' && route.startsWith(pattern)) {
@@ -223,6 +223,12 @@ export class ConfigurationBase {
223
223
  get graphqlRoutes() {
224
224
  return this.activeConfigParams.px_graphql_routes;
225
225
  }
226
+ get graphqlKeywords() {
227
+ return this.activeConfigParams.px_graphql_keywords;
228
+ }
229
+ get extractGraphQLKeywords() {
230
+ return this.activeConfigParams.px_extract_graphql_keywords;
231
+ }
226
232
  get sensitiveGraphqlOperationNames() {
227
233
  return this.activeConfigParams.px_sensitive_graphql_operation_names;
228
234
  }
@@ -16,7 +16,7 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
16
16
  px_advanced_blocking_response_enabled: true,
17
17
  px_max_activity_batch_size: 0,
18
18
  px_batch_activities_timeout_ms: 1000,
19
- px_bypass_monitor_header: '',
19
+ px_bypass_monitor_header: 'x-px-block',
20
20
  px_enforced_routes: [],
21
21
  px_first_party_enabled: true,
22
22
  px_custom_first_party_prefix: '',
@@ -94,6 +94,7 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
94
94
  px_custom_logo: '',
95
95
  px_graphql_enabled: true,
96
96
  px_graphql_routes: ['/graphql'],
97
+ px_graphql_keywords: [],
97
98
  px_sensitive_graphql_operation_names: [],
98
99
  px_sensitive_graphql_operation_types: [],
99
100
  px_enrich_custom_parameters: null,
@@ -115,4 +116,5 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
115
116
  px_custom_is_monitored_request: null,
116
117
  px_custom_is_enforced_request: null,
117
118
  px_custom_is_filtered_request: null,
119
+ px_extract_graphql_keywords: null,
118
120
  };
@@ -2,16 +2,17 @@ import { isRouteInPatterns } from '../utils';
2
2
  import { HttpMethod } from '../http';
3
3
  import { GraphQLOperationType } from './model';
4
4
  export class DefaultGraphQLParser {
5
- graphqlRoutes;
6
- sensitiveOperationTypes;
7
- sensitiveOperationNames;
5
+ config;
6
+ maxCharactersInGraphqlKeyword;
7
+ maxGraphqlKeywordCount;
8
8
  constructor(config) {
9
- this.graphqlRoutes = config.graphqlRoutes;
10
- this.sensitiveOperationNames = config.sensitiveGraphqlOperationNames;
11
- this.sensitiveOperationTypes = config.sensitiveGraphqlOperationTypes;
9
+ this.config = config;
10
+ this.maxCharactersInGraphqlKeyword = 100;
11
+ this.maxGraphqlKeywordCount = 500;
12
12
  }
13
13
  isGraphQLRequest({ requestData }) {
14
- return (requestData.method === HttpMethod.POST && isRouteInPatterns(requestData.url.pathname, this.graphqlRoutes));
14
+ return (requestData.method === HttpMethod.POST &&
15
+ isRouteInPatterns(requestData.url.pathname, this.config.graphqlRoutes));
15
16
  }
16
17
  async parseGraphQLRequest(context) {
17
18
  try {
@@ -21,7 +22,7 @@ export class DefaultGraphQLParser {
21
22
  context.logger.debug('unable to get graphql operations from request body');
22
23
  return null;
23
24
  }
24
- const data = this.parseGraphQLOperations(graphQLOperations);
25
+ const data = await this.parseGraphQLOperations(graphQLOperations, context);
25
26
  if (!data || data.length === 0) {
26
27
  context.logger.debug('unable to parse graphql operations');
27
28
  return null;
@@ -36,8 +37,9 @@ export class DefaultGraphQLParser {
36
37
  }
37
38
  async getGraphQLOperationsFromBody(request, context) {
38
39
  try {
39
- let body = await request.json();
40
+ const body = await request.json();
40
41
  if (!body) {
42
+ context.logger.debug(`received empty graphql body when calling .json()`);
41
43
  return null;
42
44
  }
43
45
  return Array.isArray(body) ? body : [body];
@@ -47,18 +49,21 @@ export class DefaultGraphQLParser {
47
49
  return null;
48
50
  }
49
51
  }
50
- parseGraphQLOperations(operations) {
51
- return operations.map((operation) => this.parseGraphQlOperation(operation)).filter((x) => x);
52
+ async parseGraphQLOperations(operations, context) {
53
+ const data = await Promise.all(operations.map((operation) => this.parseGraphQLOperation(operation, context)));
54
+ return data.filter(Boolean);
52
55
  }
53
- parseGraphQlOperation(operation) {
56
+ parseGraphQLOperation(operation, context) {
54
57
  if (!operation.query || typeof operation.query !== 'string') {
58
+ context.logger.debug('no query found');
55
59
  return null;
56
60
  }
57
61
  const operationNameToTypeMap = this.getOperationNameToTypeMap(operation.query);
58
62
  if (!operationNameToTypeMap) {
63
+ context.logger.debug('operationNameToTypeMap returned null');
59
64
  return null;
60
65
  }
61
- return this.getGraphQLData(operationNameToTypeMap, operation);
66
+ return this.getGraphQLData(operationNameToTypeMap, operation, context);
62
67
  }
63
68
  getOperationNameToTypeMap(query) {
64
69
  const operationTypesString = Object.values(GraphQLOperationType).join('|');
@@ -78,18 +83,21 @@ export class DefaultGraphQLParser {
78
83
  }
79
84
  return map;
80
85
  }
81
- getGraphQLData(operationNameToTypeMap, operation) {
82
- let name = operation.operationName ||
83
- (Object.keys(operationNameToTypeMap).length === 1 ? Object.keys(operationNameToTypeMap)[0] : undefined);
84
- let type = operationNameToTypeMap[name];
85
- if (!type && /^\s*{/.test(operation.query)) {
86
- type = GraphQLOperationType.QUERY;
87
- }
86
+ async getGraphQLData(operationNameToTypeMap, operation, context) {
87
+ const name = this.getOperationName(operationNameToTypeMap, operation);
88
+ const type = this.getOperationType(operation, name, operationNameToTypeMap);
88
89
  if (!type) {
89
90
  return null;
90
91
  }
91
- const data = { name, type };
92
- if (this.isSensitiveOperation(name, type)) {
92
+ const data = { type };
93
+ if (name) {
94
+ data.name = name;
95
+ }
96
+ const keywords = await this.getQueryKeywords(operation.query, context);
97
+ if (keywords) {
98
+ data.keywords = this.cleanKeywords(keywords);
99
+ }
100
+ if (this.isSensitiveOperation(name, type, keywords)) {
93
101
  data.sensitive = true;
94
102
  }
95
103
  if (operation.variables && typeof operation.variables === 'object') {
@@ -97,9 +105,79 @@ export class DefaultGraphQLParser {
97
105
  }
98
106
  return data;
99
107
  }
100
- isSensitiveOperation(operationName, operationType) {
101
- return (this.sensitiveOperationTypes.some((type) => type === operationType) ||
102
- this.sensitiveOperationNames.some((name) => name === operationName));
108
+ getOperationType(operation, operationName, operationNameToTypeMap) {
109
+ if (operationName && operationNameToTypeMap[operationName]) {
110
+ return operationNameToTypeMap[operationName];
111
+ }
112
+ if (this.isGraphqlQueryShorthand(operation.query)) {
113
+ return GraphQLOperationType.QUERY;
114
+ }
115
+ const match = operation.query.match(new RegExp(`^\\s*(${Object.values(GraphQLOperationType).join('|')})(?:\\s|{)`));
116
+ if (match?.[1] && !operationName) {
117
+ return match[1];
118
+ }
119
+ return null;
120
+ }
121
+ isGraphqlQueryShorthand(query) {
122
+ return /^\s*{/.test(query);
123
+ }
124
+ getOperationName(operationNameToTypeMap, operation) {
125
+ return (operation.operationName ||
126
+ (Object.keys(operationNameToTypeMap).length === 1 ? Object.keys(operationNameToTypeMap)?.[0] : undefined));
127
+ }
128
+ async getQueryKeywords(query, context) {
129
+ if (this.config.extractGraphQLKeywords && typeof this.config.extractGraphQLKeywords === 'function') {
130
+ const keywords = await this.getQueryKeywordsFromCustomFunction(query, context);
131
+ if (Array.isArray(keywords)) {
132
+ return keywords;
133
+ }
134
+ }
135
+ if (this.config.graphqlKeywords?.length > 0) {
136
+ return this.getQueryKeywordsFromArray(query, context);
137
+ }
138
+ return null;
139
+ }
140
+ cleanKeywords(keywords) {
141
+ return keywords
142
+ .slice(0, this.maxGraphqlKeywordCount)
143
+ .map((kw) => kw.trim().substring(0, this.maxCharactersInGraphqlKeyword));
144
+ }
145
+ async getQueryKeywordsFromCustomFunction(query, context) {
146
+ try {
147
+ return this.config.extractGraphQLKeywords(query);
148
+ }
149
+ catch (e) {
150
+ context.logger.debug(`unable to extract graphql keywords via custom function: ${e}`);
151
+ return null;
152
+ }
153
+ }
154
+ getQueryKeywordsFromArray(query, context) {
155
+ let keywords = [];
156
+ try {
157
+ this.config.graphqlKeywords.forEach((keyword) => {
158
+ const pattern = this.toGlobalRegExp(keyword);
159
+ let matchGroup = query.match(pattern);
160
+ if (!matchGroup) {
161
+ return;
162
+ }
163
+ keywords = keywords.concat(matchGroup);
164
+ });
165
+ }
166
+ catch (e) {
167
+ context.logger.debug(`unable to extract graphql keywords via array: ${e}`);
168
+ return null;
169
+ }
170
+ return keywords;
171
+ }
172
+ isSensitiveOperation(operationName, operationType, keywords) {
173
+ return (this.config.sensitiveGraphqlOperationTypes.some((type) => type === operationType) ||
174
+ this.config.sensitiveGraphqlOperationNames.some((name) => {
175
+ const pattern = this.toGlobalRegExp(name);
176
+ if (!pattern) {
177
+ return false;
178
+ }
179
+ return !!operationName?.match(pattern) || keywords?.some((kw) => !!kw.match(pattern));
180
+ }));
103
181
  }
104
182
  extractGraphQLVariableNames(variables) {
105
183
  const processVariables = (variablesObj, prefix) => Object.entries(variablesObj).reduce((total, [key, value]) => {
@@ -113,4 +191,13 @@ export class DefaultGraphQLParser {
113
191
  }, []);
114
192
  return processVariables(variables, '');
115
193
  }
194
+ toGlobalRegExp(pattern) {
195
+ if (typeof pattern === 'string') {
196
+ return new RegExp(pattern, 'g');
197
+ }
198
+ if (pattern.global) {
199
+ return pattern;
200
+ }
201
+ return new RegExp(pattern, pattern.flags + 'g');
202
+ }
116
203
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,4 @@
1
1
  export * from './model';
2
2
  export * from './IGraphQLParser';
3
3
  export * from './DefaultGraphQLParser';
4
+ export * from './ExtractGraphQLKeywordsFunction';