@contrast/assess 1.12.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 (42) hide show
  1. package/LICENSE +12 -0
  2. package/lib/dataflow/propagation/index.js +2 -0
  3. package/lib/dataflow/propagation/install/buffer.js +6 -5
  4. package/lib/dataflow/propagation/install/contrast-methods/add.js +3 -0
  5. package/lib/dataflow/propagation/install/joi/any.js +46 -0
  6. package/lib/dataflow/propagation/install/joi/boolean.js +109 -0
  7. package/lib/dataflow/propagation/install/joi/expression.js +99 -0
  8. package/lib/dataflow/propagation/install/joi/index.js +172 -0
  9. package/lib/dataflow/propagation/install/joi/keys.js +140 -0
  10. package/lib/dataflow/propagation/install/joi/number.js +107 -0
  11. package/lib/dataflow/propagation/install/joi/object.js +46 -0
  12. package/lib/dataflow/propagation/install/joi/string-schema.js +233 -0
  13. package/lib/dataflow/propagation/install/joi/utils.js +111 -0
  14. package/lib/dataflow/propagation/install/joi/values.js +154 -0
  15. package/lib/dataflow/propagation/install/path/basename.js +1 -3
  16. package/lib/dataflow/propagation/install/path/join-and-resolve.js +1 -3
  17. package/lib/dataflow/propagation/install/path/normalize.js +1 -3
  18. package/lib/dataflow/propagation/install/pug/index.js +2 -2
  19. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +7 -6
  20. package/lib/dataflow/propagation/install/send.js +60 -0
  21. package/lib/dataflow/propagation/install/sequelize.js +4 -4
  22. package/lib/dataflow/propagation/install/string/match-all.js +6 -5
  23. package/lib/dataflow/propagation/install/string/match.js +14 -11
  24. package/lib/dataflow/propagation/install/string/replace.js +6 -5
  25. package/lib/dataflow/propagation/install/string/slice.js +3 -0
  26. package/lib/dataflow/propagation/install/string/split.js +6 -5
  27. package/lib/dataflow/propagation/install/string/substring.js +1 -3
  28. package/lib/dataflow/propagation/install/string/trim.js +2 -0
  29. package/lib/dataflow/propagation/install/url/parse.js +3 -8
  30. package/lib/dataflow/propagation/install/url/searchParams.js +7 -7
  31. package/lib/dataflow/propagation/install/validator/hooks.js +4 -4
  32. package/lib/dataflow/sinks/index.js +3 -3
  33. package/lib/dataflow/sinks/install/eval.js +63 -67
  34. package/lib/dataflow/sinks/install/fs.js +2 -2
  35. package/lib/dataflow/sinks/install/function.js +87 -91
  36. package/lib/dataflow/sinks/install/vm.js +7 -7
  37. package/lib/dataflow/sources/install/body-parser1.js +2 -2
  38. package/lib/dataflow/sources/install/fastify/fastify.js +1 -1
  39. package/lib/dataflow/sources/install/http.js +11 -10
  40. package/lib/dataflow/tracker.js +1 -3
  41. package/lib/response-scanning/install/http.js +3 -2
  42. package/package.json +14 -11
package/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ Copyright: 2023 Contrast Security, Inc
2
+ Contact: support@contrastsecurity.com
3
+ License: Commercial
4
+
5
+ NOTICE: This Software and the patented inventions embodied within may only be
6
+ used as part of Contrast Security’s commercial offerings. Even though it is
7
+ made available through public repositories, use of this Software is subject to
8
+ the applicable End User Licensing Agreement found at
9
+ https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
10
+ between Contrast Security and the End User. The Software may not be reverse
11
+ engineered, modified, repackaged, sold, redistributed or otherwise used in a
12
+ way not consistent with the End User License Agreement.
@@ -45,6 +45,8 @@ module.exports = function(core) {
45
45
  require('./install/JSON')(core);
46
46
  require('./install/path')(core);
47
47
  require('./install/reg-exp-prototype-exec')(core);
48
+ require('./install/joi')(core);
49
+ require('./install/send')(core);
48
50
 
49
51
  propagation.install = function() {
50
52
  callChildComponentMethodsSync(propagation, 'install');
@@ -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
+ };
@@ -0,0 +1,172 @@
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
+ callChildComponentMethodsSync,
20
+ isString,
21
+ isNonEmptyObject,
22
+ traverseValues,
23
+ inspect,
24
+ } = require('@contrast/common');
25
+ const { patchType } = require('../../common');
26
+ const { tagCustomValidatedString, handleReferences } = require('./utils');
27
+
28
+ module.exports = function(core) {
29
+ const {
30
+ patcher,
31
+ scopes: { sources, instrumentation },
32
+ assess: {
33
+ eventFactory: { createPropagationEvent },
34
+ dataflow: { tracker },
35
+ },
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
+ });
161
+
162
+ require('./any')(core);
163
+ require('./expression')(core);
164
+ require('./keys')(core);
165
+ require('./object')(core);
166
+ require('./string-schema')(core);
167
+ require('./number')(core);
168
+ require('./boolean')(core);
169
+ require('./values')(core);
170
+
171
+ return joiInstrumentation;
172
+ };
@@ -0,0 +1,140 @@
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
+ isNonEmptyObject, join,
20
+ } = require('@contrast/common');
21
+ const { patchType } = require('../../common');
22
+
23
+ module.exports = function(core) {
24
+ const {
25
+ depHooks,
26
+ patcher,
27
+ } = core;
28
+
29
+ function addMetadata(schema, refTargetPath, refPath, isInReference) {
30
+ if (!schema.__CONTRAST__) {
31
+ Object.defineProperty(schema, '__CONTRAST__', {
32
+ enumerable: false,
33
+ configurable: true,
34
+ value: {
35
+ inReferenceTargets: new Set(),
36
+ refTargets: {}
37
+ },
38
+ writable: true,
39
+ });
40
+ }
41
+
42
+ let path = join(refTargetPath, '.');
43
+
44
+ if (isInReference) {
45
+ path = join(refTargetPath.slice(0, -1), '.');
46
+ schema.__CONTRAST__.inReferenceTargets.add(path);
47
+ refPath = refPath.slice(0, -1);
48
+ }
49
+
50
+ const refs = schema.__CONTRAST__.refTargets[path] || [];
51
+ refs.push(join(refPath, '.'));
52
+ schema.__CONTRAST__.refTargets[path] = refs;
53
+ }
54
+
55
+ function traverseChildSchemas(schema, refTargetPath, currentPath, refOpts) {
56
+ const traversable = schema.type === 'object' ? schema._ids._byKey.entries() : Object.entries(schema.$_terms.items);
57
+
58
+ for (const [childKey, item] of traversable) {
59
+ const value = schema.type === 'object' ? item.schema : item;
60
+ const newRefTargetPath = [...refTargetPath, childKey];
61
+ const newCurrentPath = [...currentPath, childKey];
62
+
63
+ if (value.type === 'string') {
64
+ addMetadata(value, newRefTargetPath, newCurrentPath, refOpts.in);
65
+ } else if (value.type === 'object') {
66
+ traverseChildSchemas(value, newRefTargetPath, newCurrentPath, refOpts);
67
+ }
68
+ }
69
+ }
70
+
71
+ function traverseSchemas(
72
+ joi,
73
+ currentSchema,
74
+ obj,
75
+ ancestorRef = [],
76
+ currentPath = [],
77
+ ) {
78
+ ancestorRef.unshift(obj);
79
+ if (joi.isSchema(currentSchema) || joi.isExpression(currentSchema)) return;
80
+ if (currentSchema && joi.isRef(currentSchema) && !currentSchema.__CONTRAST__?.isVisited) {
81
+ const targetSchemaRef = currentSchema.path.reduce((acc, value) => {
82
+ let ret = acc?.[value] || acc;
83
+
84
+ if (joi.isSchema(acc)) {
85
+ ret = acc?.$_terms?.keys.find(i => i.key === value);
86
+ }
87
+
88
+ return ret;
89
+ }, ancestorRef[currentSchema.ancestor - 1]);
90
+
91
+ const refTargetPath = currentSchema.absolute({ path: currentPath });
92
+ const targetSchemaInstace =
93
+ typeof targetSchemaRef?.schema === 'object'
94
+ ? targetSchemaRef.schema
95
+ : targetSchemaRef;
96
+
97
+ if (!targetSchemaInstace) return;
98
+
99
+ if (['object', 'array'].includes(targetSchemaInstace.type)) {
100
+ traverseChildSchemas(targetSchemaInstace, refTargetPath, currentPath, currentSchema);
101
+ } else {
102
+ addMetadata(targetSchemaInstace, refTargetPath, currentPath);
103
+ }
104
+ Object.defineProperty(currentSchema, '__CONTRAST__', {
105
+ enumerable: false,
106
+ configurable: true,
107
+ value: {
108
+ isVisited: true,
109
+ },
110
+ writable: true
111
+ });
112
+ } else {
113
+ if (isNonEmptyObject(currentSchema)) {
114
+ for (const [objKey, objValue] of Object.entries(currentSchema)) {
115
+ traverseSchemas(joi, objValue, currentSchema, ancestorRef, [...currentPath, objKey]);
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ return (core.assess.dataflow.propagation.joiInstrumentation.keys = {
122
+ install() {
123
+ depHooks.resolve(
124
+ { name: 'joi', file: 'lib/types/keys.js', version: '>=17.0.0' },
125
+ (joi) => {
126
+ patcher.patch(Object.getPrototypeOf(joi), 'keys', {
127
+ name: 'joi.keys',
128
+ patchType,
129
+ pre(data) {
130
+ const [value] = data.args;
131
+ const joi = data.obj.$_root;
132
+
133
+ traverseSchemas(joi, value, value);
134
+ },
135
+ });
136
+ }
137
+ );
138
+ },
139
+ });
140
+ };