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.
- package/lib/cjs/config/ConfigurationBase.js +14 -0
- package/lib/cjs/config/defaults/DefaultCommonConfigurationParams.js +3 -1
- package/lib/cjs/graphql/DefaultGraphQLParser.js +159 -30
- package/lib/cjs/graphql/ExtractGraphQLKeywordsFunction.js +2 -0
- package/lib/cjs/graphql/index.js +1 -0
- package/lib/cjs/products/credential_intelligence/endpoint/login_successful/BodyLoginSuccessfulParser.js +3 -2
- package/lib/cjs/products/credential_intelligence/endpoint/matcher/RegexPathEndpointMatcher.js +1 -1
- package/lib/cjs/telemetry/DefaultTelemetry.js +3 -3
- package/lib/cjs/utils/constants.js +1 -1
- package/lib/cjs/utils/utils.js +13 -5
- package/lib/esm/config/ConfigurationBase.js +6 -0
- package/lib/esm/config/defaults/DefaultCommonConfigurationParams.js +3 -1
- package/lib/esm/graphql/DefaultGraphQLParser.js +112 -25
- package/lib/esm/graphql/ExtractGraphQLKeywordsFunction.js +1 -0
- package/lib/esm/graphql/index.js +1 -0
- package/lib/esm/products/credential_intelligence/endpoint/login_successful/BodyLoginSuccessfulParser.js +1 -1
- package/lib/esm/products/credential_intelligence/endpoint/matcher/RegexPathEndpointMatcher.js +1 -1
- package/lib/esm/telemetry/DefaultTelemetry.js +4 -4
- package/lib/esm/utils/constants.js +1 -1
- package/lib/esm/utils/utils.js +11 -3
- package/lib/types/activities/utils.d.ts +60 -30
- package/lib/types/blocker/utils.d.ts +6 -3
- package/lib/types/config/ConfigurationBase.d.ts +4 -1
- package/lib/types/config/IConfiguration.d.ts +14 -2
- package/lib/types/config/params/CommonConfigurationParams.d.ts +4 -1
- package/lib/types/graphql/DefaultGraphQLParser.d.ts +20 -11
- package/lib/types/graphql/ExtractGraphQLKeywordsFunction.d.ts +1 -0
- package/lib/types/graphql/index.d.ts +1 -0
- package/lib/types/graphql/model/GraphQLData.d.ts +2 -1
- package/lib/types/monitored_request/MonitoredRequestUtils.d.ts +18 -9
- package/lib/types/pxhd/PXHDUtils.d.ts +12 -6
- package/lib/types/sensitive_request/SensitiveRequestUtils.d.ts +12 -6
- package/lib/types/utils/constants.d.ts +1 -1
- package/lib/types/utils/utils.d.ts +1 -1
- 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.
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
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 &&
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
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.
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 (
|
|
147
|
-
return
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
154
|
-
|
|
265
|
+
catch (e) {
|
|
266
|
+
context.logger.debug("unable to extract graphql keywords via array: ".concat(e));
|
|
267
|
+
return null;
|
|
155
268
|
}
|
|
156
|
-
return
|
|
269
|
+
return keywords;
|
|
157
270
|
};
|
|
158
|
-
DefaultGraphQLParser.prototype.isSensitiveOperation = function (operationName, operationType) {
|
|
159
|
-
|
|
160
|
-
|
|
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;
|
package/lib/cjs/graphql/index.js
CHANGED
|
@@ -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 (
|
|
47
|
+
return __generator(this, function (_c) {
|
|
47
48
|
// TODO: Possibly add IBody methods to IOutgoingResponse interface?
|
|
48
|
-
return [2 /*return*/,
|
|
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
|
};
|
package/lib/cjs/products/credential_intelligence/endpoint/matcher/RegexPathEndpointMatcher.js
CHANGED
|
@@ -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 &&
|
|
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.
|
|
147
|
-
static_config: (0, utils_1.
|
|
148
|
-
remote_config: (0, utils_1.
|
|
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.
|
|
16
|
+
exports.CORE_MODULE_VERSION = 'JS Core 0.20.1';
|
package/lib/cjs/utils/utils.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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 &&
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
config;
|
|
6
|
+
maxCharactersInGraphqlKeyword;
|
|
7
|
+
maxGraphqlKeywordCount;
|
|
8
8
|
constructor(config) {
|
|
9
|
-
this.
|
|
10
|
-
this.
|
|
11
|
-
this.
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.maxCharactersInGraphqlKeyword = 100;
|
|
11
|
+
this.maxGraphqlKeywordCount = 500;
|
|
12
12
|
}
|
|
13
13
|
isGraphQLRequest({ requestData }) {
|
|
14
|
-
return (requestData.method === HttpMethod.POST &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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 = {
|
|
92
|
-
if (
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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 {};
|
package/lib/esm/graphql/index.js
CHANGED