From 81c320b21de3edf3d03b5d1dc1c3e7f6bd2008a7 Mon Sep 17 00:00:00 2001 From: chenzimmer2 Date: Mon, 27 Mar 2023 16:15:51 +0300 Subject: [PATCH 1/7] Added custom sensitive request function Support graphQL with no query section --- lib/pxconfig.js | 6 +++++- lib/pxcontext.js | 21 ++++++++++++++++++++- lib/pxcookie.js | 2 +- lib/pxutil.js | 13 +++++++------ test/graphql.test.js | 9 +++++++++ 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/lib/pxconfig.js b/lib/pxconfig.js index feab4d3..27807ce 100644 --- a/lib/pxconfig.js +++ b/lib/pxconfig.js @@ -102,6 +102,7 @@ class PxConfig { ['JWT_HEADER_NAME', 'px_jwt_header_name'], ['JWT_HEADER_USER_ID_FIELD_NAME', 'px_jwt_header_user_id_field_name'], ['JWT_HEADER_ADDITIONAL_FIELD_NAMES', 'px_jwt_header_additional_field_names'], + ['CUSTOM_IS_SENSITIVE_REQUEST', 'px_custom_is_sensitive_request'] ]; configKeyMapping.forEach(([targetKey, sourceKey]) => { @@ -176,7 +177,8 @@ class PxConfig { userInput === 'px_login_successful_custom_callback' || userInput === 'px_modify_context' || userInput === 'px_cors_create_custom_block_response_headers' || - userInput === 'px_cors_custom_preflight_handler' + userInput === 'px_cors_custom_preflight_handler' || + userInput === 'px_custom_is_sensitive_request' ) { if (typeof params[userInput] === 'function') { return params[userInput]; @@ -359,6 +361,7 @@ function pxDefaultConfig() { JWT_HEADER_NAME: '', JWT_HEADER_USER_ID_FIELD_NAME: '', JWT_HEADER_ADDITIONAL_FIELD_NAMES: [], + CUSTOM_IS_SENSITIVE_REQUEST: '' }; } @@ -431,6 +434,7 @@ const allowedConfigKeys = [ 'px_jwt_header_name', 'px_jwt_header_user_id_field_name', 'px_jwt_header_additional_field_names', + 'px_custom_is_sensitive_request' ]; module.exports = PxConfig; diff --git a/lib/pxcontext.js b/lib/pxcontext.js index 672e866..cc752f6 100644 --- a/lib/pxcontext.js +++ b/lib/pxcontext.js @@ -23,7 +23,7 @@ class PxContext { this.originalRequest = req.originalRequest || req; this.httpVersion = req.httpVersion || ''; this.httpMethod = req.method || ''; - this.sensitiveRoute = this.isSpecialRoute(config.SENSITIVE_ROUTES, this.uri); + this.sensitiveRoute = () => this.isSensitiveRequest(req, config); this.enforcedRoute = this.isSpecialRoute(config.ENFORCED_ROUTES, this.uri); this.whitelistRoute = this.isSpecialRoute(config.WHITELIST_ROUTES, this.uri); this.monitoredRoute = !this.enforcedRoute && this.isSpecialRoute(config.MONITORED_ROUTES, this.uri); @@ -85,6 +85,25 @@ class PxContext { } } + isSensitiveRequest(request, config) { + return this.isSpecialRoute(config.SENSITIVE_ROUTES, this.uri) || + this.isCustomSensitiveRequest(request, config); + } + + isCustomSensitiveRequest(request, config) { + const customIsSensitiveRequest = config.CUSTOM_IS_SENSITIVE_REQUEST; + try { + if (customIsSensitiveRequest && customIsSensitiveRequest(request)) { + config.logger.debug('Custom sensitive request matched'); + return true; + } + } catch (err) { + config.logger.debug(`Caught exception on custom sensitive request function: ${err}`); + } + + return false; + } + getGraphqlDataFromBody(body) { let jsonBody = null; if (typeof body === 'string') { diff --git a/lib/pxcookie.js b/lib/pxcookie.js index 8fc9cdc..fa69cde 100644 --- a/lib/pxcookie.js +++ b/lib/pxcookie.js @@ -81,7 +81,7 @@ function evalCookie(ctx, config) { return ScoreEvaluateAction.COOKIE_INVALID; } - if (ctx.sensitiveRoute) { + if (ctx.sensitiveRoute()) { config.logger.debug(`Sensitive route match, sending Risk API. path: ${ctx.uri}`); ctx.s2sCallReason = 'sensitive_route'; return ScoreEvaluateAction.SENSITIVE_ROUTE; diff --git a/lib/pxutil.js b/lib/pxutil.js index 204010a..300e71f 100644 --- a/lib/pxutil.js +++ b/lib/pxutil.js @@ -288,6 +288,10 @@ function isGraphql(req, config) { // query: string (not null) // output: Record [ OperationName -> OperationType ] function parseGraphqlBody(query) { + if (!query) { + return null; + } + const pattern = /\s*(query|mutation|subscription)\s+(\w+)/gm; let match; const ret = {}; @@ -322,25 +326,22 @@ function isSensitiveGraphqlOperation(graphqlData, config) { // graphqlBodyObject: {query: string?, operationName: string?, variables: any[]?} // output: GraphqlData? function getGraphqlData(graphqlBodyObject) { - if (!graphqlBodyObject || !graphqlBodyObject.query) { + if (!graphqlBodyObject) { return null; } const parsedData = parseGraphqlBody(graphqlBodyObject.query); - if (!parsedData) { - return null; - } const selectedOperationName = graphqlBodyObject['operationName'] || (Object.keys(parsedData).length === 1 && Object.keys(parsedData)[0]); - if (!selectedOperationName || !parsedData[selectedOperationName]) { + if (!selectedOperationName || (parsedData && !parsedData[selectedOperationName])) { return null; } const variables = extractVariables(graphqlBodyObject.variables); - return new GraphqlData(parsedData[selectedOperationName], selectedOperationName, variables); + return new GraphqlData(parsedData && parsedData[selectedOperationName], selectedOperationName, variables); } // input: object representing variables diff --git a/test/graphql.test.js b/test/graphql.test.js index 167c7d3..be14bd0 100644 --- a/test/graphql.test.js +++ b/test/graphql.test.js @@ -30,6 +30,15 @@ describe('Graphql Testing', () => { graphqlData.type.should.be.exactly('query'); }); + it('should extract operation name if !query', () => { + const gqlObj = { + operationName: 'q1', + }; + + const graphqlData = pxutil.getGraphqlData(gqlObj); + graphqlData.name.should.be.exactly('q1'); + }); + it('extract with many queries', () => { const gqlObj = { query: 'query q1 { \n abc \n }\nmutation q2 {\n def\n }', From 7e35997cc28f75f3597a0027ece440fbf4532150 Mon Sep 17 00:00:00 2001 From: chenzimmer2 Date: Mon, 27 Mar 2023 17:45:58 +0300 Subject: [PATCH 2/7] Fixed comments --- lib/pxcontext.js | 2 +- test/pxenforcer.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pxcontext.js b/lib/pxcontext.js index cc752f6..9f349f7 100644 --- a/lib/pxcontext.js +++ b/lib/pxcontext.js @@ -23,7 +23,7 @@ class PxContext { this.originalRequest = req.originalRequest || req; this.httpVersion = req.httpVersion || ''; this.httpMethod = req.method || ''; - this.sensitiveRoute = () => this.isSensitiveRequest(req, config); + this.sensitiveRequest = () => this.isSensitiveRequest(req, config); this.enforcedRoute = this.isSpecialRoute(config.ENFORCED_ROUTES, this.uri); this.whitelistRoute = this.isSpecialRoute(config.WHITELIST_ROUTES, this.uri); this.monitoredRoute = !this.enforcedRoute && this.isSpecialRoute(config.MONITORED_ROUTES, this.uri); diff --git a/test/pxenforcer.test.js b/test/pxenforcer.test.js index ee308cf..10cea39 100644 --- a/test/pxenforcer.test.js +++ b/test/pxenforcer.test.js @@ -847,7 +847,7 @@ describe('PX Enforcer - pxenforcer.js', () => { return callback ? callback(null, data) : ''; }); - const modifyCtx = sinon.stub().callsFake((ctx) => ctx.sensitiveRoute = true); + const modifyCtx = sinon.stub().callsFake((ctx) => ctx.sensitiveRequest = true); const curParams = { ...params, px_modify_context: modifyCtx, @@ -857,7 +857,7 @@ describe('PX Enforcer - pxenforcer.js', () => { enforcer = new pxenforcer(curParams, pxClient); enforcer.enforce(req, null, () => { (modifyCtx.calledOnce).should.equal(true); - (req.locals.pxCtx.sensitiveRoute).should.equal(true); + (req.locals.pxCtx.sensitiveRequest).should.equal(true); done(); }); }); From 5dc161b436fc8fe7e8607306c19092ffbcf6fea4 Mon Sep 17 00:00:00 2001 From: chenzimmer2 Date: Tue, 28 Mar 2023 13:03:10 +0300 Subject: [PATCH 3/7] release version v4.0.0 --- CHANGELOG.md | 6 ++++++ README.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bfa920..7591a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [4.0.0] - 2023-03-28 + +### Added +- Support for handle graphQL request with empty operation name or type +- Support custom is sensitive request via function + ## [3.9.0] - 2023-01-29 ### Added diff --git a/README.md b/README.md index 0c2d0ea..092d81e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [PerimeterX](http://www.perimeterx.com) Shared base for NodeJS enforcers ============================================================= -> Latest stable version: [v3.9.0](https://www.npmjs.com/package/perimeterx-node-core) +> Latest stable version: [v4.0.0](https://www.npmjs.com/package/perimeterx-node-core) This is a shared base implementation for PerimeterX Express enforcer and future NodeJS enforcers. For a fully functioning implementation example, see the [Node-Express enforcer](https://github.com/PerimeterX/perimeterx-node-express/) implementation. diff --git a/package-lock.json b/package-lock.json index b300bf2..cbe9637 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "perimeterx-node-core", - "version": "3.9.0", + "version": "4.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "perimeterx-node-core", - "version": "3.9.0", + "version": "4.0.0", "license": "ISC", "dependencies": { "agent-phin": "^1.0.4", diff --git a/package.json b/package.json index 20f15fa..82e386e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "perimeterx-node-core", - "version": "3.9.0", + "version": "4.0.0", "description": "PerimeterX NodeJS shared core for various applications to monitor and block traffic according to PerimeterX risk score", "main": "index.js", "scripts": { From 6df7f6eaf85dde4e83c6bc58dd490ba02cc87713 Mon Sep 17 00:00:00 2001 From: chenzimmer2 Date: Tue, 28 Mar 2023 13:09:23 +0300 Subject: [PATCH 4/7] changed changlog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7591a3e..246f5aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [4.0.0] - 2023-03-28 ### Added -- Support for handle graphQL request with empty operation name or type +- Support for handle graphQL requests with empty operation name or type - Support custom is sensitive request via function ## [3.9.0] - 2023-01-29 From 39d86686ece4480fa751a993fa410478533fae44 Mon Sep 17 00:00:00 2001 From: chenzimmer2 Date: Tue, 28 Mar 2023 13:10:19 +0300 Subject: [PATCH 5/7] changed changlog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 246f5aa..95666ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [4.0.0] - 2023-03-28 ### Added -- Support for handle graphQL requests with empty operation name or type +- Support for handling graphQL requests with empty operation name or type - Support custom is sensitive request via function ## [3.9.0] - 2023-01-29 From 7ab24c86935c328d6cbd8f0b4743206560f69e9c Mon Sep 17 00:00:00 2001 From: chenzimmer2 Date: Tue, 28 Mar 2023 14:01:12 +0300 Subject: [PATCH 6/7] changed changlog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95666ed..ca1aa84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [4.0.0] - 2023-03-28 ### Added -- Support for handling graphQL requests with empty operation name or type +- Support for handling graphQL requests with empty query field - Support custom is sensitive request via function ## [3.9.0] - 2023-01-29 From 1e70847585970ae3934bf40b078e81a94ceaed00 Mon Sep 17 00:00:00 2001 From: chenzimmer2 Date: Tue, 28 Mar 2023 14:09:42 +0300 Subject: [PATCH 7/7] changed changlog --- CHANGELOG.md | 2 +- README.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca1aa84..e7b8453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [4.0.0] - 2023-03-28 +## [3.10.0] - 2023-03-28 ### Added - Support for handling graphQL requests with empty query field diff --git a/README.md b/README.md index 092d81e..ab602ed 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [PerimeterX](http://www.perimeterx.com) Shared base for NodeJS enforcers ============================================================= -> Latest stable version: [v4.0.0](https://www.npmjs.com/package/perimeterx-node-core) +> Latest stable version: [v3.10.0](https://www.npmjs.com/package/perimeterx-node-core) This is a shared base implementation for PerimeterX Express enforcer and future NodeJS enforcers. For a fully functioning implementation example, see the [Node-Express enforcer](https://github.com/PerimeterX/perimeterx-node-express/) implementation. diff --git a/package-lock.json b/package-lock.json index cbe9637..7945277 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "perimeterx-node-core", - "version": "4.0.0", + "version": "3.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "perimeterx-node-core", - "version": "4.0.0", + "version": "3.10.0", "license": "ISC", "dependencies": { "agent-phin": "^1.0.4", diff --git a/package.json b/package.json index 82e386e..1cf40f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "perimeterx-node-core", - "version": "4.0.0", + "version": "3.10.0", "description": "PerimeterX NodeJS shared core for various applications to monitor and block traffic according to PerimeterX risk score", "main": "index.js", "scripts": {