@contrast/protect 1.36.0 → 1.37.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.
@@ -25,10 +25,11 @@ module.exports = function(core) {
25
25
  require('./init-domain')(core);
26
26
 
27
27
  // installers
28
- require('./install/fastify')(core);
29
- require('./install/koa2')(core);
30
28
  require('./install/express4')(core);
29
+ require('./install/fastify')(core);
31
30
  require('./install/hapi')(core);
31
+ require('./install/koa2')(core);
32
+ require('./install/restify')(core);
32
33
 
33
34
  errorHandlers.install = function() {
34
35
  callChildComponentMethodsSync(errorHandlers, 'install');
@@ -0,0 +1,52 @@
1
+ /*
2
+ * Copyright: 2024 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { isSecurityException } = require('../../security-exception');
19
+ const { patchType } = require('../constants');
20
+
21
+ module.exports = function init(core) {
22
+ const { depHooks, logger, patcher, protect } = core;
23
+
24
+ return protect.errorHandlers.restifyErrorHandler = {
25
+ install() {
26
+ depHooks.resolve(
27
+ { name: 'restify', file: 'lib/server.js', version: '>=8' },
28
+ (Server) => {
29
+ patcher.patch(Server.prototype, '_onHandlerError', {
30
+ name: 'restify.Server.prototype._onHandlerError',
31
+ patchType,
32
+ around(orig, { args: [err], funcKey }) {
33
+ if (isSecurityException(err)) {
34
+ const sourceContext = protect.getSourceContext();
35
+
36
+ if (!sourceContext) {
37
+ logger.info({ funcKey }, 'source context not found; unable to handle response');
38
+ return;
39
+ }
40
+
41
+ sourceContext.block(...sourceContext.securityException);
42
+ return;
43
+ }
44
+
45
+ return orig();
46
+ }
47
+ });
48
+ }
49
+ );
50
+ }
51
+ };
52
+ };
@@ -24,9 +24,9 @@ const {
24
24
  traverseKeysAndValues,
25
25
  traverseValues,
26
26
  InputType,
27
- toLowerCase,
28
- split,
29
- join
27
+ ArrayPrototypeJoin,
28
+ StringPrototypeToLowerCase,
29
+ StringPrototypeSplit,
30
30
  } = require('@contrast/common');
31
31
 
32
32
  //
@@ -595,7 +595,7 @@ module.exports = function (core) {
595
595
  const probe = Object.assign({}, resultByRuleId, result, {
596
596
  mappedId: result.ruleId,
597
597
  });
598
- const key = join([
598
+ const key = ArrayPrototypeJoin.call([
599
599
  probe.ruleId,
600
600
  probe.inputType,
601
601
  ...probe.path,
@@ -725,7 +725,7 @@ module.exports = function (core) {
725
725
 
726
726
  for (let i = 0; i < reqHeaders.length; i++) {
727
727
  if (reqHeaders[i] === 'x-forwarded-for') {
728
- const ipsFromHeaders = split(reqHeaders[i + 1], /[,;]+/);
728
+ const ipsFromHeaders = StringPrototypeSplit.call(reqHeaders[i + 1], /[,;]+/);
729
729
  forwardedIps.push(...ipsFromHeaders);
730
730
  }
731
731
  }
@@ -797,7 +797,7 @@ function isResultExcluded(sourceContext, result) {
797
797
  }
798
798
  case 'HeaderKey':
799
799
  case 'HeaderValue': {
800
- if (path[0] && toLowerCase(path[0]) === 'cookie') {
800
+ if (path[0] && StringPrototypeToLowerCase.call(path[0]) === 'cookie') {
801
801
  inputExclusions = exclusions.cookie;
802
802
  checkCookiesInHeader = true;
803
803
  } else {
@@ -41,6 +41,7 @@ module.exports = function(core) {
41
41
  require('./install/koa2')(core);
42
42
  require('./install/express4')(core);
43
43
  require('./install/hapi')(core);
44
+ require('./install/restify')(core);
44
45
 
45
46
  // virtual patches
46
47
  require('./virtual-patches')(core);
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { Event, toLowerCase } = require('@contrast/common');
18
+ const { Event, StringPrototypeToLowerCase } = require('@contrast/common');
19
19
  const { patchType } = require('../constants');
20
20
 
21
21
  module.exports = function (core) {
@@ -94,7 +94,7 @@ module.exports = function (core) {
94
94
  const connectInputs = {
95
95
  headers: removeCookies(headers),
96
96
  uriPath,
97
- method: toLowerCase(method),
97
+ method:StringPrototypeToLowerCase.call(method),
98
98
  };
99
99
 
100
100
  if (inputAnalysis.virtualPatchesEvaluators?.length) {
@@ -0,0 +1,67 @@
1
+ /*
2
+ * Copyright: 2024 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { isSecurityException } = require('../../security-exception');
19
+ const { patchType } = require('../constants');
20
+
21
+ module.exports = function init(core) {
22
+ const { depHooks, patcher, logger, protect } = core;
23
+ const { inputAnalysis } = protect;
24
+
25
+ return inputAnalysis.restifyInstrumentation = {
26
+ install() {
27
+ depHooks.resolve(
28
+ { name: 'restify', file: 'lib/server.js', version: '>=8' },
29
+ (Server) => {
30
+ patcher.patch(Server.prototype, '_afterUse', {
31
+ name: 'restify.Server.prototype._afterUse',
32
+ patchType,
33
+ pre(data) {
34
+ const sourceContext = protect.getSourceContext();
35
+ if (!sourceContext) return;
36
+
37
+ const req = data.args[1];
38
+ try {
39
+ if (req.body && !sourceContext.parsedBody) {
40
+ sourceContext.parsedBody = req.body;
41
+ inputAnalysis.handleParsedBody(sourceContext, req.body);
42
+ }
43
+ if (req.params && !sourceContext.parsedParams) {
44
+ sourceContext.parsedParams = req.params;
45
+ inputAnalysis.handleUrlParams(sourceContext, req.params);
46
+ }
47
+ if (req.query && !sourceContext.parsedQuery) {
48
+ sourceContext.parsedQuery = req.query;
49
+ inputAnalysis.handleQueryParams(sourceContext, req.query);
50
+ }
51
+ } catch (err) {
52
+ if (isSecurityException(err)) {
53
+ data.args[0] = err;
54
+ } else {
55
+ logger.error(
56
+ { err, funcKey: data.funcKey },
57
+ 'Unexpected error during input analysis'
58
+ );
59
+ }
60
+ }
61
+ }
62
+ });
63
+ },
64
+ );
65
+ }
66
+ };
67
+ };
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { Event, substr } = require('@contrast/common');
18
+ const { Event, StringPrototypeSubstr } = require('@contrast/common');
19
19
  const address = require('ipaddr.js');
20
20
 
21
21
  module.exports = (core) => {
@@ -57,7 +57,7 @@ function ipEntryMap(ipEntry, startTime) {
57
57
  const slashIdx = ip.indexOf('/');
58
58
  const isCIDR = slashIdx >= 0;
59
59
  const ipInstance = isCIDR
60
- ? address.process(substr(ip, 0, slashIdx))
60
+ ? address.process(StringPrototypeSubstr.call(ip, 0, slashIdx))
61
61
  : address.process(ip);
62
62
 
63
63
  const normalizedValue = ipInstance.toNormalizedString();
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { Event, toLowerCase } = require('@contrast/common');
18
+ const { Event, StringPrototypeToLowerCase } = require('@contrast/common');
19
19
 
20
20
  module.exports = (core) => {
21
21
  const {
@@ -47,7 +47,7 @@ function buildVPEvaluators(virtualPatches, evaluatorsArray) {
47
47
  acc.push(...entry);
48
48
  return acc;
49
49
  }, []);
50
- const keyIndex = headersArray.indexOf(toLowerCase(name));
50
+ const keyIndex = headersArray.indexOf(StringPrototypeToLowerCase.call(name));
51
51
 
52
52
  result = keyIndex !== -1 && evalCheck(headersArray[keyIndex + 1], value);
53
53
  if (!result) break;
@@ -20,7 +20,7 @@ const {
20
20
  ProtectRuleMode: { OFF },
21
21
  BLOCKING_MODES,
22
22
  isString,
23
- stringify,
23
+ JSONStringify,
24
24
  traverseKeys,
25
25
  traverseKeysAndValues,
26
26
  agentLibIDListTypes,
@@ -351,7 +351,7 @@ function handleObjectValue(result, object) {
351
351
  obj = obj[value];
352
352
  // does the found object in the query equal the saved object?
353
353
  if (util.isDeepStrictEqual(obj, result.mongoContext.inputToCheck)) {
354
- const start = stringify(object).indexOf(value);
354
+ const start = JSONStringify(object).indexOf(value);
355
355
  const end = start + value.length;
356
356
  const inputBoundaryIndex = 0;
357
357
  findings = { start, end, boundaryOverrunIndex: start, inputBoundaryIndex };
@@ -29,12 +29,14 @@ module.exports = function(core) {
29
29
  require('./install/fs')(core);
30
30
  require('./install/function')(core);
31
31
  require('./install/http')(core);
32
+ require('./install/http2')(core);
32
33
  require('./install/marsdb')(core);
33
34
  require('./install/mongodb')(core);
34
35
  require('./install/mssql')(core);
35
36
  require('./install/mysql')(core);
36
37
  require('./install/postgres')(core);
37
38
  require('./install/sequelize')(core);
39
+ require('./install/spdy')(core);
38
40
  require('./install/sqlite3')(core);
39
41
  require('./install/vm')(core);
40
42
  // TODO: NODE-2360 (oracledb)
@@ -37,10 +37,9 @@ module.exports = function(core) {
37
37
  if (instrumentation.isLocked()) return;
38
38
 
39
39
  const sourceContext = protect.getSourceContext(name);
40
-
41
40
  if (!sourceContext) return;
42
41
 
43
- const value = data.args[0]?.toString();
42
+ const value = data.args[0]?.toString?.();
44
43
  if (!value) return;
45
44
 
46
45
  const sinkContext = {
@@ -0,0 +1,63 @@
1
+ /*
2
+ * Copyright: 2024 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { patchType } = require('../constants');
19
+
20
+ module.exports = function(core) {
21
+ const {
22
+ scopes: { instrumentation },
23
+ patcher,
24
+ depHooks,
25
+ protect,
26
+ protect: { inputTracing }
27
+ } = core;
28
+
29
+ function install() {
30
+ depHooks.resolve({ name: 'http2' }, http2 => {
31
+ for (const method of ['write', 'end']) {
32
+ const name = `http2.Http2ServerResponse.prototype.${method}`;
33
+ patcher.patch(http2.Http2ServerResponse.prototype, method, {
34
+ name,
35
+ patchType,
36
+ pre(data) {
37
+ if (instrumentation.isLocked()) return;
38
+
39
+ const sourceContext = protect.getSourceContext(name);
40
+
41
+ if (!sourceContext) return;
42
+
43
+ const value = data.args[0]?.toString();
44
+ if (!value) return;
45
+
46
+ const sinkContext = {
47
+ name,
48
+ value,
49
+ stacktraceOpts: { constructorOpt: data.hooked },
50
+ };
51
+ inputTracing.handleReflectedXss(sourceContext, sinkContext);
52
+ }
53
+ });
54
+ }
55
+ });
56
+ }
57
+
58
+ const http2Instrumentation = inputTracing.http2Instrumentation = {
59
+ install
60
+ };
61
+
62
+ return http2Instrumentation;
63
+ };
@@ -0,0 +1,62 @@
1
+ /*
2
+ * Copyright: 2024 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { patchType } = require('../constants');
19
+
20
+ module.exports = function(core) {
21
+ const {
22
+ scopes: { instrumentation },
23
+ patcher,
24
+ depHooks,
25
+ protect,
26
+ protect: { inputTracing }
27
+ } = core;
28
+
29
+ function install() {
30
+ depHooks.resolve({ name: 'spdy' }, spdy => {
31
+ const name = 'spdy.response.end';
32
+ patcher.patch(spdy.response, 'end', {
33
+ name,
34
+ patchType,
35
+ pre(data) {
36
+ if (instrumentation.isLocked()) return;
37
+
38
+ const sourceContext = protect.getSourceContext(name);
39
+
40
+ if (!sourceContext) return;
41
+
42
+ const value = data.args[0]?.toString();
43
+ if (!value) return;
44
+
45
+ const sinkContext = {
46
+ name,
47
+ value,
48
+ stacktraceOpts: { constructorOpt: data.hooked },
49
+ };
50
+
51
+ inputTracing.handleReflectedXss(sourceContext, sinkContext);
52
+ }
53
+ });
54
+ });
55
+ }
56
+
57
+ const spdyInstrumentation = inputTracing.spdyInstrumentation = {
58
+ install
59
+ };
60
+
61
+ return spdyInstrumentation;
62
+ };
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { toUpperCase } = require('@contrast/common');
18
+ const { StringPrototypeToUpperCase } = require('@contrast/common');
19
19
 
20
20
  module.exports = function(core) {
21
21
  // i think this should be a weakset. we don't want to accumulate
@@ -29,7 +29,7 @@ module.exports = function(core) {
29
29
  if (blocked.has(res)) return;
30
30
 
31
31
  blocked.add(res);
32
- mode = toUpperCase(mode);
32
+ mode = StringPrototypeToUpperCase.call(mode);
33
33
  const end = patcher.unwrap(res.end);
34
34
  const writeHead = patcher.unwrap(res.writeHead);
35
35
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { toLowerCase, slice } = require('@contrast/common');
18
+ const { StringPrototypeToLowerCase, StringPrototypeSlice } = require('@contrast/common');
19
19
 
20
20
  module.exports = function(core) {
21
21
  const {
@@ -36,8 +36,8 @@ module.exports = function(core) {
36
36
  const ix = req.url.indexOf('?');
37
37
 
38
38
  if (ix >= 0) {
39
- uriPath = slice(req.url, 0, ix);
40
- queries = slice(req.url, ix + 1);
39
+ uriPath = StringPrototypeSlice.call(req.url, 0, ix);
40
+ queries = StringPrototypeSlice.call(req.url, ix + 1);
41
41
  } else {
42
42
  uriPath = req.url;
43
43
  queries = '';
@@ -55,10 +55,10 @@ module.exports = function(core) {
55
55
  const headers = Array(req.rawHeaders.length);
56
56
 
57
57
  for (let i = 0; i < req.rawHeaders.length; i += 2) {
58
- headers[i] = toLowerCase(req.rawHeaders[i]);
58
+ headers[i] = StringPrototypeToLowerCase.call(req.rawHeaders[i]);
59
59
  headers[i + 1] = req.rawHeaders[i + 1];
60
60
  if (headers[i] === 'content-type') {
61
- contentType = toLowerCase(headers[i + 1]);
61
+ contentType = StringPrototypeToLowerCase.call(headers[i + 1]);
62
62
  }
63
63
  }
64
64
 
package/lib/policy.js CHANGED
@@ -19,9 +19,9 @@ const {
19
19
  Rule,
20
20
  ProtectRuleMode,
21
21
  Event,
22
- toLowerCase,
23
- split,
24
- join
22
+ ArrayPrototypeJoin,
23
+ StringPrototypeToLowerCase,
24
+ StringPrototypeSplit,
25
25
  } = require('@contrast/common');
26
26
  const { ConfigSource } = require('@contrast/config');
27
27
 
@@ -88,7 +88,7 @@ module.exports = function (core) {
88
88
  }
89
89
  }
90
90
  if (regExpNeeded) {
91
- const rx = new RegExp(`^${join(urls, '|')}$`);
91
+ const rx = new RegExp(`^${ArrayPrototypeJoin.call(urls, '|')}$`);
92
92
 
93
93
  return (uriPath) => rx ? rx.test(uriPath) : false;
94
94
  }
@@ -305,7 +305,7 @@ module.exports = function (core) {
305
305
  exclusionDtm.type = exclusionDtm.type || 'URL';
306
306
 
307
307
  const { name, protect_rules, urls, type } = exclusionDtm;
308
- const key = toLowerCase(type);
308
+ const key = StringPrototypeToLowerCase.call(type);
309
309
 
310
310
  if (!compiled[key]) continue;
311
311
 
@@ -340,8 +340,8 @@ module.exports = function (core) {
340
340
  }
341
341
  if (key === 'cookie') {
342
342
  e.checkCookieInHeader = (cookieHeader) => {
343
- for (const cookiePair of split(cookieHeader, ';')) {
344
- const cookieKey = split(cookiePair, '=')[0];
343
+ for (const cookiePair of StringPrototypeSplit.call(cookieHeader, ';')) {
344
+ const cookieKey = StringPrototypeSplit.call(cookiePair, '=')[0];
345
345
  if (e.matchesInputName(cookieKey)) {
346
346
  return true;
347
347
  }
@@ -21,7 +21,7 @@ const {
21
21
  ProtectRuleMode: { OFF },
22
22
  InputType,
23
23
  traverseValues,
24
- replace
24
+ StringPrototypeReplace,
25
25
  } = require('@contrast/common');
26
26
 
27
27
  const {
@@ -29,7 +29,7 @@ const {
29
29
  } = require('./utils/xml-analysis');
30
30
 
31
31
  const SINK_EXPLOIT_PATTERN_START = /(?:^|\\|\/)(?:sh|bash|zsh|ksh|tcsh|csh|fish|cmd)/;
32
- const stripWhiteSpace = (str) => replace(str, /\s/g, '');
32
+ const stripWhiteSpace = (str) => StringPrototypeReplace.call(str, /\s/g, '');
33
33
 
34
34
  const getRuleResults = function(obj, prop) {
35
35
  return obj[prop] || (obj[prop] = []);
@@ -14,7 +14,7 @@
14
14
  */
15
15
  'use strict';
16
16
 
17
- const { substr, toLowerCase } = require('@contrast/common');
17
+ const { StringPrototypeSubstr, StringPrototypeToLowerCase } = require('@contrast/common');
18
18
 
19
19
  const PROTOCOLS = {
20
20
  FTP: 'FTP',
@@ -23,9 +23,9 @@ const PROTOCOLS = {
23
23
  TCP: 'TCP'
24
24
  };
25
25
 
26
- const FTP = `${toLowerCase(PROTOCOLS.FTP)}:`;
27
- const HTTP = `${toLowerCase(PROTOCOLS.HTTP)}:`;
28
- const HTTPS = `${toLowerCase(PROTOCOLS.HTTPS)}:`;
26
+ const FTP = `${StringPrototypeToLowerCase.call(PROTOCOLS.FTP)}:`;
27
+ const HTTP = `${StringPrototypeToLowerCase.call(PROTOCOLS.HTTP)}:`;
28
+ const HTTPS = `${StringPrototypeToLowerCase.call(PROTOCOLS.HTTPS)}:`;
29
29
  const DTD_EXTENSION = '.dtd';
30
30
  const FILE_START = 'file:';
31
31
  const GOPHER_START = 'gopher:';
@@ -101,7 +101,7 @@ module.exports.findExternalEntities = function(xml = '') {
101
101
 
102
102
  return {
103
103
  entities,
104
- prolog: len && substr(xml, 0, entities[len - 1].finish) || null
104
+ prolog: len && StringPrototypeSubstr.call(xml, 0, entities[len - 1].finish) || null
105
105
  };
106
106
  };
107
107
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.36.0",
3
+ "version": "1.37.0",
4
4
  "description": "Contrast service providing framework-agnostic Protect support",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -11,17 +11,17 @@
11
11
  "types": "lib/index.d.ts",
12
12
  "engines": {
13
13
  "npm": ">=6.13.7 <7 || >= 8.3.1",
14
- "node": ">= 14.18.0"
14
+ "node": ">= 16.9.1"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "../scripts/test.sh"
18
18
  },
19
19
  "dependencies": {
20
20
  "@contrast/agent-lib": "^7.0.1",
21
- "@contrast/common": "1.21.0",
22
- "@contrast/config": "1.28.0",
23
- "@contrast/core": "1.32.0",
24
- "@contrast/esm-hooks": "2.6.0",
21
+ "@contrast/common": "1.21.2",
22
+ "@contrast/config": "1.28.2",
23
+ "@contrast/core": "1.32.2",
24
+ "@contrast/esm-hooks": "2.6.2",
25
25
  "@contrast/scopes": "1.4.1",
26
26
  "ipaddr.js": "^2.0.1",
27
27
  "semver": "^7.3.7"