@contrast/assess 1.13.0 → 1.14.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.
Files changed (31) hide show
  1. package/lib/dataflow/propagation/install/buffer.js +6 -5
  2. package/lib/dataflow/propagation/install/contrast-methods/add.js +3 -0
  3. package/lib/dataflow/propagation/install/joi/any.js +46 -0
  4. package/lib/dataflow/propagation/install/joi/boolean.js +109 -0
  5. package/lib/dataflow/propagation/install/joi/expression.js +99 -0
  6. package/lib/dataflow/propagation/install/joi/index.js +145 -8
  7. package/lib/dataflow/propagation/install/joi/number.js +107 -0
  8. package/lib/dataflow/propagation/install/joi/object.js +46 -0
  9. package/lib/dataflow/propagation/install/joi/string-schema.js +15 -51
  10. package/lib/dataflow/propagation/install/joi/utils.js +111 -0
  11. package/lib/dataflow/propagation/install/joi/values.js +21 -8
  12. package/lib/dataflow/propagation/install/path/basename.js +1 -3
  13. package/lib/dataflow/propagation/install/path/join-and-resolve.js +1 -3
  14. package/lib/dataflow/propagation/install/path/normalize.js +1 -3
  15. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +7 -6
  16. package/lib/dataflow/propagation/install/string/match-all.js +6 -5
  17. package/lib/dataflow/propagation/install/string/match.js +14 -11
  18. package/lib/dataflow/propagation/install/string/replace.js +6 -5
  19. package/lib/dataflow/propagation/install/string/slice.js +3 -0
  20. package/lib/dataflow/propagation/install/string/split.js +6 -5
  21. package/lib/dataflow/propagation/install/string/substring.js +1 -3
  22. package/lib/dataflow/propagation/install/string/trim.js +2 -0
  23. package/lib/dataflow/propagation/install/url/parse.js +3 -8
  24. package/lib/dataflow/propagation/install/url/searchParams.js +7 -7
  25. package/lib/dataflow/propagation/install/validator/hooks.js +4 -4
  26. package/lib/dataflow/sinks/index.js +3 -3
  27. package/lib/dataflow/sinks/install/eval.js +63 -67
  28. package/lib/dataflow/sinks/install/function.js +87 -91
  29. package/lib/dataflow/sinks/install/vm.js +7 -7
  30. package/lib/dataflow/tracker.js +1 -3
  31. package/package.json +6 -4
@@ -63,11 +63,12 @@ module.exports = function(core) {
63
63
  target: 'R',
64
64
  });
65
65
 
66
- if (event) {
67
- const { extern } = tracker.track(result, event);
68
- if (extern) {
69
- data.result = extern;
70
- }
66
+ if (!event) return;
67
+
68
+ const { extern } = tracker.track(result, event);
69
+
70
+ if (extern) {
71
+ data.result = extern;
71
72
  }
72
73
  }
73
74
  });
@@ -98,6 +98,9 @@ module.exports = function(core) {
98
98
  tags: newTags,
99
99
  target: 'R',
100
100
  });
101
+
102
+ if (!event) return;
103
+
101
104
  const { extern } = tracker.track(result, event);
102
105
 
103
106
  if (extern) {
@@ -0,0 +1,46 @@
1
+ /*
2
+ * Copyright: 2023 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
+ module.exports = function(core) {
19
+ const {
20
+ depHooks,
21
+ assess: {
22
+ dataflow: {
23
+ propagation: { joiInstrumentation },
24
+ },
25
+ },
26
+ } = core;
27
+
28
+ joiInstrumentation.any = {
29
+ install() {
30
+ depHooks.resolve(
31
+ { name: 'joi', file: 'lib/types/any', version: '>=17.0.0' },
32
+ (exp) => {
33
+ const anyTypePrototype = Object.getPrototypeOf(exp);
34
+ const def = anyTypePrototype?._definition;
35
+
36
+ anyTypePrototype &&
37
+ joiInstrumentation.patchValidateAsync(
38
+ anyTypePrototype,
39
+ 'any.external'
40
+ );
41
+ def && joiInstrumentation.patchCustomValidate(def, 'any');
42
+ }
43
+ );
44
+ },
45
+ };
46
+ };
@@ -0,0 +1,109 @@
1
+ /*
2
+ * Copyright: 2023 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 {
19
+ DataflowTag: { ALPHANUM_SPACE_HYPHEN },
20
+ inspect,
21
+ } = require('@contrast/common');
22
+ const { patchType } = require('../../common');
23
+
24
+ module.exports = function(core) {
25
+ const {
26
+ depHooks,
27
+ scopes: { sources, instrumentation },
28
+ patcher,
29
+ assess: {
30
+ eventFactory: { createPropagationEvent },
31
+ dataflow: { tracker },
32
+ },
33
+ } = core;
34
+
35
+ /**
36
+ * Patch Joi's boolean coerce function so it tags the input w/
37
+ * alphanum-space-hyphen if the coercion was successful. Tag is added when
38
+ * conditions are met.
39
+ * @param {Object} the boolean export
40
+ */
41
+ function instrumentJoiBoolean(boolean) {
42
+ const def = Object.getPrototypeOf(boolean)?._definition;
43
+ def &&
44
+ patcher.patch(def.coerce, 'method', {
45
+ name: 'joi.boolean.coerce',
46
+ patchType,
47
+ post(data) {
48
+ if (
49
+ !data.result?.value ||
50
+ typeof data.result.value !== 'boolean' ||
51
+ !sources.getStore()?.assess ||
52
+ instrumentation.isLocked()
53
+ )
54
+ return;
55
+
56
+ const argInfo = tracker.getData(data.args[0]);
57
+
58
+ if (!argInfo) return;
59
+
60
+ const event = createPropagationEvent({
61
+ name: 'Joi.boolean.coerce',
62
+ moduleName: 'joi',
63
+ methodName: 'boolean.coerce',
64
+ history: [{ ...argInfo }],
65
+ object: {
66
+ tracked: false,
67
+ value: 'Joi.boolean',
68
+ },
69
+ args: [
70
+ { tracked: true, value: argInfo.value },
71
+ {
72
+ tracked: false,
73
+ value: {
74
+ ...inspect(data.args[1]),
75
+ original: argInfo.value,
76
+ },
77
+ },
78
+ ],
79
+ result: {
80
+ tracked: false,
81
+ value: data.result,
82
+ },
83
+ source: 'P0',
84
+ tags: {
85
+ ...argInfo.tags,
86
+ [ALPHANUM_SPACE_HYPHEN]: [0, argInfo.value.length - 1],
87
+ },
88
+ target: 'P0',
89
+ stacktraceOpts: {
90
+ prependFrames: [data.orig],
91
+ },
92
+ });
93
+
94
+ if (event) {
95
+ Object.assign(argInfo, event);
96
+ }
97
+ },
98
+ });
99
+ }
100
+
101
+ return (core.assess.dataflow.propagation.joiInstrumentation.booleanCoerce = {
102
+ install() {
103
+ depHooks.resolve(
104
+ { name: 'joi', file: 'lib/types/boolean.js', version: '>=17.0.0' },
105
+ instrumentJoiBoolean
106
+ );
107
+ },
108
+ });
109
+ };
@@ -0,0 +1,99 @@
1
+ /*
2
+ * Copyright: 2023 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 {
19
+ DataflowTag: { HTML_ENCODED },
20
+ inspect,
21
+ } = require('@contrast/common');
22
+ const { patchType } = require('../../common');
23
+
24
+ module.exports = function(core) {
25
+ const {
26
+ depHooks,
27
+ scopes: { sources, instrumentation },
28
+ patcher,
29
+ assess: {
30
+ eventFactory: { createPropagationEvent },
31
+ dataflow: { tracker },
32
+ },
33
+ } = core;
34
+
35
+ /**
36
+ * Patch joi.expression so that it tags input with html-encoded if rendered
37
+ * @param {Object} expression
38
+ */
39
+ function instrumentJoiExpression(joi, method) {
40
+ return patcher.patch(joi, method, {
41
+ name: `joi.${method}`,
42
+ patchType,
43
+ post(data) {
44
+ if (
45
+ !data.result ||
46
+ !sources.getStore()?.assess ||
47
+ instrumentation.isLocked()
48
+ )
49
+ return;
50
+
51
+ const argInfo = tracker.getData(data.args[0]);
52
+
53
+ if (argInfo && data.result._template) {
54
+ const event = createPropagationEvent({
55
+ addedTags: [HTML_ENCODED],
56
+ name: `Joi.${method}`,
57
+ moduleName: 'joi',
58
+ methodName: method,
59
+ history: [{ ...argInfo }],
60
+ object: {
61
+ tracked: false,
62
+ value: `Joi.${method}`,
63
+ },
64
+ args: [{ tracked: true, value: argInfo.value }],
65
+ result: {
66
+ tracked: false,
67
+ value: inspect({ ...data.result, source: argInfo.value }),
68
+ },
69
+ source: 'P0',
70
+ tags: {
71
+ ...argInfo.tags,
72
+ [HTML_ENCODED]: [0, argInfo.value.length - 1],
73
+ },
74
+ target: 'P0',
75
+ stacktraceOpts: {
76
+ prependFrames: [data.orig],
77
+ },
78
+ });
79
+
80
+ if (event) {
81
+ Object.assign(argInfo, event);
82
+ }
83
+ }
84
+ },
85
+ });
86
+ }
87
+
88
+ core.assess.dataflow.propagation.joiInstrumentation.expression = {
89
+ install() {
90
+ depHooks.resolve(
91
+ { name: 'joi', file: 'lib/index.js', version: '>=17.0.0' },
92
+ (joi) => {
93
+ instrumentJoiExpression(joi, 'expression');
94
+ instrumentJoiExpression(joi, 'x');
95
+ }
96
+ );
97
+ },
98
+ };
99
+ };
@@ -15,20 +15,157 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { callChildComponentMethodsSync } = require('@contrast/common');
18
+ const {
19
+ callChildComponentMethodsSync,
20
+ isString,
21
+ isNonEmptyObject,
22
+ traverseValues,
23
+ inspect,
24
+ } = require('@contrast/common');
25
+ const { patchType } = require('../../common');
26
+ const { tagCustomValidatedString, handleReferences } = require('./utils');
19
27
 
20
28
  module.exports = function(core) {
21
- const joiInstrumentation = core.assess.dataflow.propagation.joiInstrumentation = {
22
- install() {
23
- callChildComponentMethodsSync(joiInstrumentation, 'install');
29
+ const {
30
+ patcher,
31
+ scopes: { sources, instrumentation },
32
+ assess: {
33
+ eventFactory: { createPropagationEvent },
34
+ dataflow: { tracker },
24
35
  },
25
- uninstall() {
26
- callChildComponentMethodsSync(joiInstrumentation, 'uninstall');
27
- },
28
- };
36
+ } = core;
37
+
38
+ const joiInstrumentation =
39
+ (core.assess.dataflow.propagation.joiInstrumentation = {
40
+ install() {
41
+ callChildComponentMethodsSync(joiInstrumentation, 'install');
42
+ },
43
+ uninstall() {
44
+ callChildComponentMethodsSync(joiInstrumentation, 'uninstall');
45
+ },
46
+ patchValidateAsync(parentObj, parentObjType) {
47
+ patcher.patch(parentObj, 'validateAsync', {
48
+ name: `Joi.${parentObjType}.validateAsync`,
49
+ patchType,
50
+ post(data) {
51
+ const childNodes = data.obj?.$_terms.items?.length
52
+ ? data.obj.$_terms.items
53
+ : data.obj?.$_terms.keys?.length
54
+ ? data.obj.$_terms.keys
55
+ : null;
56
+
57
+ if (
58
+ (!childNodes && !data.obj?.$_terms?.externals?.length) ||
59
+ (childNodes &&
60
+ !childNodes.find((i) => {
61
+ const schema = i?.schema || i;
62
+ return schema.$_terms?.externals?.length;
63
+ })) ||
64
+ !core.config.assess.trust_custom_validators ||
65
+ !sources.getStore()?.assess ||
66
+ instrumentation.isLocked()
67
+ )
68
+ return;
69
+
70
+ data.result.then((result) => {
71
+ const metadata = {
72
+ origFn: data.orig,
73
+ methodName: parentObjType,
74
+ target: 'R',
75
+ };
76
+
77
+ if (isString(result)) {
78
+ tagCustomValidatedString(
79
+ createPropagationEvent,
80
+ tracker.getData(result),
81
+ metadata
82
+ );
83
+ } else if (isNonEmptyObject(result)) {
84
+ traverseValues(result, (_path, _key, value) => {
85
+ tagCustomValidatedString(
86
+ createPropagationEvent,
87
+ tracker.getData(value),
88
+ metadata
89
+ );
90
+ });
91
+ }
92
+ })
93
+ .catch(err => err);
94
+ },
95
+ });
96
+ },
97
+ patchCustomValidate(parentObj, objName) {
98
+ patcher.patch(parentObj.rules.custom, 'validate', {
99
+ name: `joi.${objName}.custom.valdiate`,
100
+ patchType,
101
+ post(data) {
102
+ const {
103
+ args: [input, schema],
104
+ result,
105
+ orig,
106
+ } = data;
107
+
108
+ if (
109
+ !result ||
110
+ !input ||
111
+ (result.value === input &&
112
+ (result.messages?.source || result.local?.error)) ||
113
+ !core.config.assess.trust_custom_validators ||
114
+ !sources.getStore()?.assess ||
115
+ instrumentation.isLocked()
116
+ )
117
+ return;
118
+
119
+ const inspectedSecondArg = inspect(schema);
120
+ const metadata = {
121
+ origFn: orig,
122
+ inspectedSecondArg,
123
+ methodName: `${objName}.custom.validate`,
124
+ target: 'R',
125
+ };
126
+ const validation = (trackingInfo) => {
127
+ tagCustomValidatedString(
128
+ createPropagationEvent,
129
+ trackingInfo,
130
+ metadata
131
+ );
132
+ };
133
+
134
+ handleReferences(tracker, schema, validation);
135
+
136
+ if (isString(result)) {
137
+ validation(tracker.getData(result));
138
+ input === result &&
139
+ tagCustomValidatedString(
140
+ createPropagationEvent,
141
+ tracker.getData(input),
142
+ { ...metadata, target: 'P0' }
143
+ );
144
+ } else if (isNonEmptyObject(result)) {
145
+ traverseValues(result, (path, _key, value) => {
146
+ const argValue = path.reduce((obj, k) => obj[k] || obj, input);
147
+
148
+ validation(tracker.getData(result));
149
+ argValue === value &&
150
+ tagCustomValidatedString(
151
+ createPropagationEvent,
152
+ tracker.getData(argValue),
153
+ { ...metadata, target: 'P0' }
154
+ );
155
+ });
156
+ }
157
+ },
158
+ });
159
+ },
160
+ });
29
161
 
162
+ require('./any')(core);
163
+ require('./expression')(core);
30
164
  require('./keys')(core);
165
+ require('./object')(core);
31
166
  require('./string-schema')(core);
167
+ require('./number')(core);
168
+ require('./boolean')(core);
32
169
  require('./values')(core);
33
170
 
34
171
  return joiInstrumentation;
@@ -0,0 +1,107 @@
1
+ /*
2
+ * Copyright: 2023 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 {
19
+ DataflowTag: { LIMITED_CHARS },
20
+ inspect,
21
+ } = require('@contrast/common');
22
+ const { patchType } = require('../../common');
23
+
24
+ module.exports = function(core) {
25
+ const {
26
+ depHooks,
27
+ scopes: { sources, instrumentation },
28
+ patcher,
29
+ assess: {
30
+ eventFactory: { createPropagationEvent },
31
+ dataflow: { tracker },
32
+ },
33
+ } = core;
34
+
35
+ /**
36
+ * patch Joi's number coerce function so it tags the input w/ limited-chars if the coercion was successful
37
+ * @param {Object} the number export
38
+ */
39
+ function instrumentJoiNumber(number) {
40
+ const def = Object.getPrototypeOf(number)?._definition;
41
+ def &&
42
+ patcher.patch(def.coerce, 'method', {
43
+ name: 'joi.number.coerce',
44
+ patchType,
45
+ post(data) {
46
+ if (
47
+ !data.result?.value ||
48
+ data.result.errors ||
49
+ !sources.getStore()?.assess ||
50
+ instrumentation.isLocked()
51
+ )
52
+ return;
53
+
54
+ const argInfo = tracker.getData(data.args[0]);
55
+
56
+ if (!argInfo) return;
57
+
58
+ const event = createPropagationEvent({
59
+ name: 'Joi.number.coerce',
60
+ moduleName: 'joi',
61
+ methodName: 'number.coerce',
62
+ history: [{ ...argInfo }],
63
+ object: {
64
+ tracked: false,
65
+ value: 'Joi.number',
66
+ },
67
+ args: [
68
+ { tracked: true, value: argInfo.value },
69
+ {
70
+ tracked: false,
71
+ value: {
72
+ ...inspect(data.args[1]),
73
+ original: argInfo.value,
74
+ },
75
+ },
76
+ ],
77
+ result: {
78
+ tracked: false,
79
+ value: data.result,
80
+ },
81
+ source: 'P0',
82
+ tags: {
83
+ ...argInfo.tags,
84
+ [LIMITED_CHARS]: [0, argInfo.value.length - 1],
85
+ },
86
+ target: 'P0',
87
+ stacktraceOpts: {
88
+ prependFrames: [data.orig],
89
+ },
90
+ });
91
+
92
+ if (event) {
93
+ Object.assign(argInfo, event);
94
+ }
95
+ },
96
+ });
97
+ }
98
+
99
+ return (core.assess.dataflow.propagation.joiInstrumentation.numberCoerce = {
100
+ install() {
101
+ depHooks.resolve(
102
+ { name: 'joi', file: 'lib/types/number.js', version: '>=17.0.0' },
103
+ instrumentJoiNumber
104
+ );
105
+ },
106
+ });
107
+ };
@@ -0,0 +1,46 @@
1
+ /*
2
+ * Copyright: 2023 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
+ module.exports = function(core) {
19
+ const {
20
+ depHooks,
21
+ assess: {
22
+ dataflow: {
23
+ propagation: { joiInstrumentation },
24
+ },
25
+ },
26
+ } = core;
27
+
28
+ joiInstrumentation.object = {
29
+ install() {
30
+ depHooks.resolve(
31
+ { name: 'joi', file: 'lib/types/object', version: '>=17.0.0' },
32
+ (exp) => {
33
+ const objectTypePrototype = Object.getPrototypeOf(exp);
34
+ const def = objectTypePrototype?._definition;
35
+
36
+ objectTypePrototype &&
37
+ joiInstrumentation.patchValidateAsync(
38
+ objectTypePrototype,
39
+ 'object.external'
40
+ );
41
+ def && joiInstrumentation.patchCustomValidate(def, 'object');
42
+ }
43
+ );
44
+ },
45
+ };
46
+ };