@contrast/protect 1.16.0 → 1.18.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.
@@ -169,12 +169,8 @@ module.exports = function(core) {
169
169
  { URLS: connectInputs.rawUrl, HEADERS: connectInputs.headers }
170
170
  );
171
171
 
172
- // initialize findings to the basics
173
- let block = undefined;
174
- if (rulesMask !== 0) {
175
- const findings = agentLib.scoreRequestConnect(rulesMask, connectInputs, preferWW);
176
- block = mergeFindings(sourceContext, findings);
177
- }
172
+ const findings = agentLib.scoreRequestConnect(rulesMask, connectInputs, preferWW);
173
+ let block = mergeFindings(sourceContext, findings);
178
174
 
179
175
  if (!block) {
180
176
  block = inputAnalysis.handleMethodTampering(sourceContext, connectInputs);
@@ -510,7 +506,7 @@ module.exports = function(core) {
510
506
  }
511
507
  }
512
508
 
513
- if (!config.protect.probe_analysis.enable || sourceContext.allowed) return;
509
+ if (!config.protect.probe_analysis.enable) return;
514
510
 
515
511
  // Detecting probes
516
512
  const { resultsMap, policy: { rulesMask } } = sourceContext;
@@ -49,6 +49,20 @@ module.exports = function(core) {
49
49
  let store, block;
50
50
  const { args: [type, req, res] } = data;
51
51
 
52
+ function callNext() {
53
+ setImmediate(() => {
54
+ const domain = initDomain(req, res);
55
+
56
+ if (domain) {
57
+ domain.run(() => {
58
+ next.call(data.obj, ...data.args);
59
+ });
60
+ } else {
61
+ next.call(data.obj, ...data.args);
62
+ }
63
+ });
64
+ }
65
+
52
66
  if (type !== 'request') {
53
67
  return next();
54
68
  }
@@ -61,6 +75,11 @@ module.exports = function(core) {
61
75
  }
62
76
 
63
77
  store.protect = core.protect.makeSourceContext(req, res);
78
+ if (store.protect.allowed) {
79
+ callNext();
80
+ return;
81
+ }
82
+
64
83
  const {
65
84
  reqData: { headers, uriPath, method },
66
85
  resData,
@@ -97,17 +116,7 @@ module.exports = function(core) {
97
116
  }
98
117
 
99
118
  if (!block) {
100
- setImmediate(() => {
101
- const domain = initDomain(req, res);
102
-
103
- if (domain) {
104
- domain.run(() => {
105
- next.call(data.obj, ...data.args);
106
- });
107
- } else {
108
- next.call(data.obj, ...data.args);
109
- }
110
- });
119
+ callNext();
111
120
  } else {
112
121
  store.protect.block(...block);
113
122
  logger.debug({ block }, 'request blocked by not emitting request event');
@@ -15,115 +15,101 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { isString } = require('@contrast/common');
18
+ const { FS_METHODS, isString } = require('@contrast/common');
19
19
  const { patchType } = require('../constants');
20
20
 
21
- const fsMethods = [
22
- { method: 'access' },
23
- { method: 'accessSync' },
24
- { method: 'appendFile' },
25
- { method: 'appendFileSync' },
26
- { method: 'chmod' },
27
- { method: 'chmodSync' },
28
- { method: 'chown' },
29
- { method: 'chownSync' },
30
- { method: 'copyFile', indices: [0, 1] },
31
- { method: 'copyFileSync', indices: [0, 1] },
32
- { method: 'createReadStream' },
33
- { method: 'createWriteStream' },
34
- { method: 'lchmod' },
35
- { method: 'lchmodSync' },
36
- { method: 'lchown' },
37
- { method: 'lchownSync' },
38
- { method: 'mkdir' },
39
- { method: 'mkdirSync' },
40
- { method: 'open' },
41
- { method: 'openSync' },
42
- { method: 'readFile' },
43
- { method: 'readFileSync' },
44
- { method: 'readdir' },
45
- { method: 'readdirSync' },
46
- { method: 'readlink' },
47
- { method: 'readlinkSync' },
48
- { method: 'rename', indices: [0, 1] },
49
- { method: 'renameSync', indices: [0, 1] },
50
- { method: 'rmdir' },
51
- { method: 'rmdirSync' },
52
- { method: 'symlink', indices: [0, 1] },
53
- { method: 'symlinkSync', indices: [0, 1] },
54
- { method: 'truncate' },
55
- { method: 'truncateSync' },
56
- { method: 'unlink' },
57
- { method: 'unlinkSync' },
58
- { method: 'writeFile' },
59
- { method: 'writeFileSync' },
60
- ];
61
-
62
- module.exports = function(core) {
63
- const {
64
- scopes: { instrumentation },
65
- patcher,
66
- depHooks,
67
- protect,
68
- protect: { inputTracing }
69
- } = core;
70
-
71
- function getValues(indices, args) {
72
- return indices.reduce((acc, idx) => {
73
- const value = args[idx];
74
- if (value && isString(value)) acc.push(value);
75
- return acc;
76
- }, []);
77
- }
78
-
79
- function install() {
80
- depHooks.resolve({ name: 'fs' }, fs => {
81
- fsMethods.forEach(({ method, indices = [0] }) => {
82
- const name = `fs.${method}`;
83
-
84
- // may not exist depending on OS
85
- if (!fs[method]) return;
86
-
87
- patcher.patch(fs, method, {
88
- name,
89
- patchType,
90
- pre({ args, hooked, name, orig }) {
91
- // don't proceed if instrumentation is off e.g. within require() call
92
- if (instrumentation.isLocked()) return;
93
-
94
- // obtain the Protect sourceContext
95
- const sourceContext = protect.getSourceContext('fs');
96
-
97
- if (!sourceContext) return;
98
-
99
- // build list of values on which to perform INPUT TRACING
100
- const values = getValues(indices, args);
101
- if (!values.length) return;
102
-
103
- // while we need to check whether instrumentation is locked above, we
104
- // don't need to necessarily need to lock it here - there are no
105
- // lower-level calls that we instrument
106
- for (const value of values) {
107
- const sinkContext = {
108
- name,
109
- value,
110
- stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
111
- };
112
- inputTracing.handlePathTraversal(sourceContext, sinkContext);
113
- core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
21
+ function getValues(indices, args) {
22
+ return indices.reduce((acc, idx) => {
23
+ const value = args[idx];
24
+ if (value && isString(value)) acc.push(value);
25
+ return acc;
26
+ }, []);
27
+ }
28
+
29
+ module.exports = function init(core) {
30
+ const { depHooks, patcher } = core;
31
+
32
+ const preHook = (indices) => ({ args, hooked, name, orig }) => {
33
+ // don't proceed if instrumentation is off e.g. within require() call
34
+ if (core.scopes.instrumentation.isLocked()) return;
35
+
36
+ // obtain the Protect sourceContext
37
+ const sourceContext = core.protect.getSourceContext('fs');
38
+
39
+ if (!sourceContext) return;
40
+
41
+ // build list of values on which to perform INPUT TRACING
42
+ const values = getValues(indices, args);
43
+ if (!values.length) return;
44
+
45
+ // while we need to check whether instrumentation is locked above, we
46
+ // don't need to necessarily need to lock it here - there are no
47
+ // lower-level calls that we instrument
48
+ for (const value of values) {
49
+ const sinkContext = {
50
+ name,
51
+ value,
52
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
53
+ };
54
+
55
+ core.protect.inputTracing.handlePathTraversal(
56
+ sourceContext,
57
+ sinkContext
58
+ );
59
+ core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(
60
+ sourceContext,
61
+ sinkContext,
62
+ );
63
+ }
64
+ };
65
+
66
+ return core.protect.inputTracing.fsInstrumentation = {
67
+ install() {
68
+ depHooks.resolve({ name: 'fs' }, (fs) => {
69
+ for (const method of FS_METHODS) {
70
+ // not all methods are available on every OS or Node version.
71
+ if (fs[method.name]) {
72
+ patcher.patch(fs, method.name, {
73
+ name: `fs.${method.name}`,
74
+ patchType,
75
+ pre: preHook(method.indices)
76
+ });
77
+ }
78
+
79
+ if (method.sync) {
80
+ const syncName = `${method.name}Sync`;
81
+ if (fs[syncName]) {
82
+ patcher.patch(fs, syncName, {
83
+ name: `fs.${syncName}`,
84
+ patchType,
85
+ pre: preHook(method.indices)
86
+ });
114
87
  }
115
88
  }
116
- });
89
+
90
+ if (method.promises && fs.promises && fs.promises[method.name]) {
91
+ patcher.patch(fs.promises, method.name, {
92
+ name: `fs.promises.${method.name}`,
93
+ patchType,
94
+ pre: preHook(method.indices)
95
+ });
96
+ }
97
+ }
117
98
  });
118
- });
119
- }
120
99
 
121
- const fsInstrumentation = inputTracing.fsInstrumentation = {
122
- getValues,
123
- install,
100
+ depHooks.resolve({ name: 'fs/promises' }, (fsPromises) => {
101
+ for (const method of FS_METHODS) {
102
+ if (method.promises && fsPromises[method.name]) {
103
+ patcher.patch(fsPromises, method.name, {
104
+ name: `fsPromises.${method.name}`,
105
+ patchType,
106
+ pre: preHook(method.indices)
107
+ });
108
+ }
109
+ }
110
+ });
111
+ }
124
112
  };
125
-
126
- return fsInstrumentation;
127
113
  };
128
114
 
129
- module.exports.fsMethods = fsMethods;
115
+ module.exports.FS_METHODS = FS_METHODS;
@@ -44,7 +44,7 @@ module.exports = function(core) {
44
44
  const policy = getPolicy({ uriPath });
45
45
 
46
46
  // URL exclusions can disable all rules
47
- if (!policy) {
47
+ if (!policy || policy.rulesMask === 0) {
48
48
  return { allowed: true };
49
49
  }
50
50
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.16.0",
3
+ "version": "1.18.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)",
@@ -18,9 +18,9 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@contrast/agent-lib": "^5.3.4",
21
- "@contrast/common": "1.7.0",
22
- "@contrast/core": "1.14.0",
23
- "@contrast/esm-hooks": "1.10.0",
21
+ "@contrast/common": "1.9.0",
22
+ "@contrast/core": "1.16.0",
23
+ "@contrast/esm-hooks": "1.12.0",
24
24
  "@contrast/scopes": "1.3.0",
25
25
  "ipaddr.js": "^2.0.1",
26
26
  "semver": "^7.3.7"