@contrast/assess 1.28.0 → 1.29.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.
- package/lib/crypto-analysis/install/crypto.js +3 -3
- package/lib/dataflow/propagation/install/JSON/parse-fn.js +5 -5
- package/lib/dataflow/propagation/install/JSON/parse.js +3 -3
- package/lib/dataflow/propagation/install/JSON/stringify.js +24 -17
- package/lib/dataflow/propagation/install/array-prototype-join.js +3 -3
- package/lib/dataflow/propagation/install/buffer.js +60 -2
- package/lib/dataflow/propagation/install/contrast-methods/add.js +1 -3
- package/lib/dataflow/propagation/install/ejs/template.js +3 -3
- package/lib/dataflow/propagation/install/joi/boolean.js +1 -1
- package/lib/dataflow/propagation/install/joi/expression.js +1 -1
- package/lib/dataflow/propagation/install/joi/index.js +1 -1
- package/lib/dataflow/propagation/install/joi/keys.js +5 -4
- package/lib/dataflow/propagation/install/joi/number.js +1 -1
- package/lib/dataflow/propagation/install/joi/string-schema.js +3 -2
- package/lib/dataflow/propagation/install/joi/utils.js +9 -5
- package/lib/dataflow/propagation/install/joi/values.js +4 -3
- package/lib/dataflow/propagation/install/mongoose/schema-map.js +2 -2
- package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +2 -2
- package/lib/dataflow/propagation/install/mongoose/schema-string.js +2 -2
- package/lib/dataflow/propagation/install/path/basename.js +2 -2
- package/lib/dataflow/propagation/install/path/common.js +5 -5
- package/lib/dataflow/propagation/install/path/format.js +7 -4
- package/lib/dataflow/propagation/install/path/join-and-resolve.js +2 -2
- package/lib/dataflow/propagation/install/path/parse.js +4 -5
- package/lib/dataflow/propagation/install/querystring/escape.js +1 -1
- package/lib/dataflow/propagation/install/querystring/parse.js +8 -8
- package/lib/dataflow/propagation/install/querystring/stringify.js +1 -1
- package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +2 -3
- package/lib/dataflow/propagation/install/send.js +2 -2
- package/lib/dataflow/propagation/install/string/concat.js +19 -19
- package/lib/dataflow/propagation/install/string/html-methods.js +1 -1
- package/lib/dataflow/propagation/install/string/index.js +4 -3
- package/lib/dataflow/propagation/install/string/match-all.js +3 -9
- package/lib/dataflow/propagation/install/string/match.js +6 -5
- package/lib/dataflow/propagation/install/string/replace.js +23 -17
- package/lib/dataflow/propagation/install/string/slice.js +5 -5
- package/lib/dataflow/propagation/install/string/split.js +13 -11
- package/lib/dataflow/propagation/install/string/substring.js +6 -5
- package/lib/dataflow/propagation/install/url/parse.js +1 -1
- package/lib/dataflow/propagation/install/url/searchParams.js +2 -1
- package/lib/dataflow/propagation/install/url/url.js +1 -1
- package/lib/dataflow/sinks/index.js +1 -0
- package/lib/dataflow/sinks/install/child-process.js +4 -4
- package/lib/dataflow/sinks/install/express/reflected-xss.js +7 -5
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +1 -2
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +1 -3
- package/lib/dataflow/sinks/install/fs.js +3 -3
- package/lib/dataflow/sinks/install/function.js +3 -3
- package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.js +1 -2
- package/lib/dataflow/sinks/install/http/request.js +6 -5
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +2 -2
- package/lib/dataflow/sinks/install/libxmljs.js +1 -1
- package/lib/dataflow/sinks/install/marsdb.js +1 -2
- package/lib/dataflow/sinks/install/mongodb.js +1 -1
- package/lib/dataflow/sinks/install/mysql.js +1 -1
- package/lib/dataflow/sinks/install/postgres.js +1 -3
- package/lib/dataflow/sinks/install/restify.js +208 -0
- package/lib/dataflow/sinks/install/sequelize.js +1 -2
- package/lib/dataflow/sinks/install/vm.js +5 -5
- package/lib/dataflow/sources/handler.js +2 -2
- package/lib/dataflow/sources/index.js +1 -0
- package/lib/dataflow/sources/install/http.js +4 -4
- package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.js +85 -0
- package/lib/dataflow/sources/install/restify/index.js +32 -0
- package/lib/dataflow/sources/install/restify/jsonBodyParser.js +109 -0
- package/lib/dataflow/sources/install/restify/router.js +77 -0
- package/lib/dataflow/tag-utils.js +20 -4
- package/lib/dataflow/tracker.js +1 -0
- package/lib/event-factory.js +3 -3
- package/lib/get-policy.js +2 -2
- package/lib/index.d.ts +18 -0
- package/lib/index.js +13 -0
- package/lib/make-source-context.js +2 -2
- package/lib/response-scanning/handlers/index.js +10 -10
- package/lib/response-scanning/handlers/utils.js +19 -12
- package/lib/response-scanning/install/http.js +9 -59
- package/lib/session-configuration/install/express-session.js +3 -5
- package/lib/session-configuration/install/fastify-cookie.js +3 -3
- package/lib/session-configuration/install/hapi.js +1 -3
- package/lib/session-configuration/install/koa.js +1 -1
- package/package.json +4 -4
|
@@ -19,7 +19,7 @@ const {
|
|
|
19
19
|
InputType,
|
|
20
20
|
DataflowTag,
|
|
21
21
|
isString,
|
|
22
|
-
|
|
22
|
+
ArrayPrototypeJoin,
|
|
23
23
|
} = require('@contrast/common');
|
|
24
24
|
|
|
25
25
|
module.exports = function (core) {
|
|
@@ -137,7 +137,7 @@ module.exports = function (core) {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
traverse(_data, (path, fieldName, value, obj) => {
|
|
140
|
-
const pathName =
|
|
140
|
+
const pathName = ArrayPrototypeJoin.call(path, '.');
|
|
141
141
|
|
|
142
142
|
if (sourceContext.sourceEventsCount >= max) {
|
|
143
143
|
logger.trace({ inputType, sourceName: name }, 'exiting assess source handling - %s max events exceeded', max);
|
|
@@ -26,6 +26,7 @@ module.exports = function (core) {
|
|
|
26
26
|
require('./install/fastify')(core);
|
|
27
27
|
require('./install/hapi')(core);
|
|
28
28
|
require('./install/koa')(core);
|
|
29
|
+
require('./install/restify')(core);
|
|
29
30
|
require('./install/body-parser1')(core);
|
|
30
31
|
require('./install/busboy')(core);
|
|
31
32
|
require('./install/cookie-parser1')(core);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
const { patchType } = require('../common');
|
|
18
|
-
const {
|
|
18
|
+
const { StringPrototypeToLowerCase, InputType } = require('@contrast/common');
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* @param {{
|
|
@@ -65,13 +65,13 @@ module.exports = function (core) {
|
|
|
65
65
|
const key = obj[i];
|
|
66
66
|
const value = obj[i + 1];
|
|
67
67
|
|
|
68
|
-
if (
|
|
68
|
+
if (StringPrototypeToLowerCase.call(key) === 'content-type') {
|
|
69
69
|
store.assess.responseData.contentType = value;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
} else if (typeof obj === 'object') {
|
|
73
73
|
for (const [key, value] of Object.entries(obj)) {
|
|
74
|
-
if (
|
|
74
|
+
if (StringPrototypeToLowerCase.call(key) === 'content-type') {
|
|
75
75
|
store.assess.responseData.contentType = value;
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -85,7 +85,7 @@ module.exports = function (core) {
|
|
|
85
85
|
patchType,
|
|
86
86
|
pre(data) {
|
|
87
87
|
const [name = '', value] = data.args;
|
|
88
|
-
if (
|
|
88
|
+
if (StringPrototypeToLowerCase.call(name) === 'content-type' && scopes.sources.getStore()?.assess && value) {
|
|
89
89
|
store.assess.responseData.contentType = value;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { InputType: { BODY } } = require('@contrast/common');
|
|
19
|
+
const { InstrumentationType: { SOURCE } } = require('../../../../constants');
|
|
20
|
+
const { patchType } = require('../../common');
|
|
21
|
+
|
|
22
|
+
module.exports = function init(core) {
|
|
23
|
+
const {
|
|
24
|
+
logger,
|
|
25
|
+
depHooks,
|
|
26
|
+
patcher,
|
|
27
|
+
assess: {
|
|
28
|
+
dataflow: { sources },
|
|
29
|
+
getSourceContext,
|
|
30
|
+
},
|
|
31
|
+
} = core;
|
|
32
|
+
|
|
33
|
+
return core.assess.dataflow.sources.restifyInstrumentation.fieldedTextBodyParser = {
|
|
34
|
+
install() {
|
|
35
|
+
depHooks.resolve(
|
|
36
|
+
{ name: 'restify', file: 'lib/plugins/fieldedTextBodyParser.js', version: '>=8' },
|
|
37
|
+
(fieldedTextBodyParser) => patcher.patch(fieldedTextBodyParser, {
|
|
38
|
+
name: 'restify.plugins.fieldedTextBodyParser',
|
|
39
|
+
patchType,
|
|
40
|
+
post(data) {
|
|
41
|
+
data.result = patcher.patch(data.result, {
|
|
42
|
+
name: 'restify.plugins.fieldedTextBodyParser.parseFieldedText',
|
|
43
|
+
patchType,
|
|
44
|
+
pre(data) {
|
|
45
|
+
const { args: [req, , next], name, funcKey } = data;
|
|
46
|
+
data.args[2] = function contrastNext(...args) {
|
|
47
|
+
const sourceContext = getSourceContext(SOURCE);
|
|
48
|
+
|
|
49
|
+
if (!sourceContext) {
|
|
50
|
+
logger.error({ funcKey }, 'unable to handle source. Missing `sourceContext`');
|
|
51
|
+
return next(...args);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (sourceContext.parsedBody) {
|
|
55
|
+
logger.trace({ funcKey }, 'values already tracked');
|
|
56
|
+
return next(...args);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
sources.handle({
|
|
61
|
+
context: 'req.body',
|
|
62
|
+
data: req.body,
|
|
63
|
+
inputType: BODY,
|
|
64
|
+
name,
|
|
65
|
+
stacktraceOpts: {
|
|
66
|
+
constructorOpt: contrastNext,
|
|
67
|
+
},
|
|
68
|
+
sourceContext,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
sourceContext.parsedBody = true;
|
|
72
|
+
} catch (err) {
|
|
73
|
+
logger.error({ err, funcKey }, 'unable to handle Restify source');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return next(...args);
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
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 { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
|
+
|
|
20
|
+
module.exports = function init(core) {
|
|
21
|
+
core.assess.dataflow.sources.restifyInstrumentation = {
|
|
22
|
+
install() {
|
|
23
|
+
callChildComponentMethodsSync(this, 'install');
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
require('./fieldedTextBodyParser')(core);
|
|
28
|
+
require('./jsonBodyParser')(core);
|
|
29
|
+
require('./router')(core);
|
|
30
|
+
|
|
31
|
+
return core.assess.dataflow.sources.restifyInstrumentation;
|
|
32
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
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 { InputType: { JSON_VALUE } } = require('@contrast/common');
|
|
19
|
+
const { InstrumentationType: { SOURCE } } = require('../../../../constants');
|
|
20
|
+
const { patchType } = require('../../common');
|
|
21
|
+
|
|
22
|
+
module.exports = function init(core) {
|
|
23
|
+
const {
|
|
24
|
+
logger,
|
|
25
|
+
depHooks,
|
|
26
|
+
patcher,
|
|
27
|
+
assess: {
|
|
28
|
+
dataflow: { sources },
|
|
29
|
+
getSourceContext,
|
|
30
|
+
},
|
|
31
|
+
} = core;
|
|
32
|
+
|
|
33
|
+
return core.assess.dataflow.sources.restifyInstrumentation.jsonBodyParser = {
|
|
34
|
+
install() {
|
|
35
|
+
depHooks.resolve(
|
|
36
|
+
{ name: 'restify', file: 'lib/plugins/jsonBodyParser.js', version: '>=8' },
|
|
37
|
+
(jsonBodyParser) => patcher.patch(jsonBodyParser, {
|
|
38
|
+
name: 'restify.plugins.jsonBodyParser',
|
|
39
|
+
patchType,
|
|
40
|
+
post(data) {
|
|
41
|
+
const { args: [opts] } = data;
|
|
42
|
+
const index = data.result.length - 1;
|
|
43
|
+
|
|
44
|
+
data.result[index] = patcher.patch(data.result[index], {
|
|
45
|
+
name: 'restify.plugins.jsonBodyParser.parseJson',
|
|
46
|
+
patchType,
|
|
47
|
+
pre(data) {
|
|
48
|
+
const { args: [req, , next], name, funcKey } = data;
|
|
49
|
+
|
|
50
|
+
data.args[2] = function contrastNext(...args) {
|
|
51
|
+
const sourceContext = getSourceContext(SOURCE);
|
|
52
|
+
|
|
53
|
+
if (!sourceContext) {
|
|
54
|
+
logger.error({ funcKey }, 'unable to handle source. Missing `sourceContext`');
|
|
55
|
+
return next(...args);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (sourceContext.parsedBody) {
|
|
59
|
+
logger.trace({ funcKey }, 'values already tracked');
|
|
60
|
+
return next(...args);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
sources.handle({
|
|
65
|
+
context: 'req.body',
|
|
66
|
+
data: req.body,
|
|
67
|
+
inputType: JSON_VALUE,
|
|
68
|
+
name,
|
|
69
|
+
stacktraceOpts: {
|
|
70
|
+
constructorOpt: contrastNext,
|
|
71
|
+
},
|
|
72
|
+
sourceContext,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
sourceContext.parsedBody = true;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
logger.error({ err, funcKey }, 'unable to handle Restify source');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (opts?.mapParams) {
|
|
81
|
+
// we don't check for parsedParams first since these clobber
|
|
82
|
+
try {
|
|
83
|
+
sources.handle({
|
|
84
|
+
context: 'req.params',
|
|
85
|
+
data: req.params,
|
|
86
|
+
inputType: JSON_VALUE,
|
|
87
|
+
name,
|
|
88
|
+
stacktraceOpts: {
|
|
89
|
+
constructorOpt: contrastNext,
|
|
90
|
+
},
|
|
91
|
+
sourceContext,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
sourceContext.parsedParams = true;
|
|
95
|
+
} catch (err) {
|
|
96
|
+
logger.error({ err, funcKey }, 'unable to handle Restify source');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return next(...args);
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
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 { InputType: { URL_PARAMETER } } = require('@contrast/common');
|
|
19
|
+
const { InstrumentationType: { SOURCE } } = require('../../../../constants');
|
|
20
|
+
const { patchType } = require('../../common');
|
|
21
|
+
|
|
22
|
+
module.exports = function init(core) {
|
|
23
|
+
const {
|
|
24
|
+
logger,
|
|
25
|
+
depHooks,
|
|
26
|
+
patcher,
|
|
27
|
+
assess: {
|
|
28
|
+
dataflow: { sources },
|
|
29
|
+
getSourceContext,
|
|
30
|
+
},
|
|
31
|
+
} = core;
|
|
32
|
+
|
|
33
|
+
return core.assess.dataflow.sources.restifyInstrumentation.router = {
|
|
34
|
+
install() {
|
|
35
|
+
depHooks.resolve(
|
|
36
|
+
{ name: 'restify', file: 'lib/router.js', version: '>=8' },
|
|
37
|
+
(Router) => {
|
|
38
|
+
patcher.patch(Router.prototype, 'lookup', {
|
|
39
|
+
name: 'restify.Router.prototype.lookup',
|
|
40
|
+
patchType,
|
|
41
|
+
post({ args: [req], hooked, orig, name, funcKey }) {
|
|
42
|
+
const sourceContext = getSourceContext(SOURCE);
|
|
43
|
+
|
|
44
|
+
if (!sourceContext) {
|
|
45
|
+
logger.error({ funcKey }, 'unable to handle source. Missing `sourceContext`');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (sourceContext.parsedParams) {
|
|
50
|
+
logger.trace({ funcKey }, 'values already tracked');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
sources.handle({
|
|
56
|
+
context: 'req.params',
|
|
57
|
+
data: req.params,
|
|
58
|
+
inputType: URL_PARAMETER,
|
|
59
|
+
name,
|
|
60
|
+
stacktraceOpts: {
|
|
61
|
+
constructorOpt: hooked,
|
|
62
|
+
prependFrames: [orig],
|
|
63
|
+
},
|
|
64
|
+
sourceContext,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
sourceContext.parsedParams = true;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
logger.error({ err, funcKey }, 'unable to handle Restify source');
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const {
|
|
17
|
+
const { StringPrototypeSplit } = require('@contrast/common');
|
|
18
18
|
|
|
19
19
|
//
|
|
20
20
|
// This module implements tag range manipulation functions. There are generally
|
|
@@ -485,8 +485,8 @@ function createAdjustedQueryTags(path, tags, value, argString) {
|
|
|
485
485
|
* - "?test=str","%3Ftest%3Dstr",{"UNTRUSTED":[0,8]} => {"UNTRUSTED":[3,6,10,12]}
|
|
486
486
|
*/
|
|
487
487
|
function createEscapeTagRanges(input, result, tags) {
|
|
488
|
-
const inputArr =
|
|
489
|
-
const escapedArr =
|
|
488
|
+
const inputArr = StringPrototypeSplit.call(input, '');
|
|
489
|
+
const escapedArr = StringPrototypeSplit.call(result, '');
|
|
490
490
|
const overlap = inputArr.filter((x) => {
|
|
491
491
|
if (escapedArr.includes(x)) {
|
|
492
492
|
return x;
|
|
@@ -520,6 +520,21 @@ function createEscapeTagRanges(input, result, tags) {
|
|
|
520
520
|
return ret;
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
+
/**
|
|
524
|
+
* In reporting args, object, and return values, often the exact value isn't important.
|
|
525
|
+
* For untracked values that appear in call contexts it can be enough to just try to
|
|
526
|
+
* report the type of the arg/obj/result.
|
|
527
|
+
* Example: the call
|
|
528
|
+
* http.request('http://tracked-url', { method: 'post' });
|
|
529
|
+
* would have event context string limited to
|
|
530
|
+
* http.request('http://tracked-url,Object);
|
|
531
|
+
*
|
|
532
|
+
* @param {any} origValue value of event result, object, or arg
|
|
533
|
+
* @returns {string} the adjusted value for reporting
|
|
534
|
+
*/
|
|
535
|
+
function getAdjustedUntrackedValue(origValue) {
|
|
536
|
+
return origValue?.constructor?.name ?? (origValue === null ? 'null' : typeof origValue);
|
|
537
|
+
}
|
|
523
538
|
|
|
524
539
|
module.exports = {
|
|
525
540
|
createSubsetTags,
|
|
@@ -529,5 +544,6 @@ module.exports = {
|
|
|
529
544
|
createTagsWithExclusion,
|
|
530
545
|
createAdjustedQueryTags,
|
|
531
546
|
createOverlappingTags,
|
|
532
|
-
createEscapeTagRanges
|
|
547
|
+
createEscapeTagRanges,
|
|
548
|
+
getAdjustedUntrackedValue,
|
|
533
549
|
};
|
package/lib/dataflow/tracker.js
CHANGED
package/lib/event-factory.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { InputType,
|
|
18
|
+
const { InputType, StringPrototypeMatch } = require('@contrast/common');
|
|
19
19
|
const ANNOTATION_REGEX = /^(A|O|R|P|P\d+)$/;
|
|
20
20
|
const SOURCE_EVENT_MSG = 'Source event not created: %s';
|
|
21
21
|
const PROPAGATION_EVENT_MSG = 'Propagation event not created: %s';
|
|
@@ -113,12 +113,12 @@ module.exports = function (core) {
|
|
|
113
113
|
return null;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
if (!source || !
|
|
116
|
+
if (!source || !StringPrototypeMatch.call(source, ANNOTATION_REGEX)) {
|
|
117
117
|
logger.debug({ name }, PROPAGATION_EVENT_MSG, 'invalid source');
|
|
118
118
|
return null;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
if (!target || !
|
|
121
|
+
if (!target || !StringPrototypeMatch.call(target, ANNOTATION_REGEX)) {
|
|
122
122
|
logger.debug({ name }, PROPAGATION_EVENT_MSG, 'invalid target');
|
|
123
123
|
return null;
|
|
124
124
|
}
|
package/lib/get-policy.js
CHANGED
|
@@ -22,7 +22,7 @@ const {
|
|
|
22
22
|
Rule,
|
|
23
23
|
ResponseScanningRule,
|
|
24
24
|
SessionConfigurationRule,
|
|
25
|
-
|
|
25
|
+
ArrayPrototypeJoin,
|
|
26
26
|
} = require('@contrast/common');
|
|
27
27
|
|
|
28
28
|
const ASSESS_RULES = Object.values({
|
|
@@ -279,7 +279,7 @@ class UrlExclusion {
|
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
if (regexSegments.length) {
|
|
282
|
-
this._urlRegex = new RegExp(`^${
|
|
282
|
+
this._urlRegex = new RegExp(`^${ArrayPrototypeJoin.call(regexSegments, '|')}$`);
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -17,6 +17,20 @@ import {
|
|
|
17
17
|
Rule,
|
|
18
18
|
SessionConfigurationRule,
|
|
19
19
|
} from '@contrast/common';
|
|
20
|
+
import { Core as _Core } from '@contrast/core';
|
|
21
|
+
|
|
22
|
+
export interface Core extends _Core {
|
|
23
|
+
config: Config;
|
|
24
|
+
logger: Logger;
|
|
25
|
+
depHooks: RequireHook;
|
|
26
|
+
patcher: Patcher
|
|
27
|
+
rewriter: Rewriter;
|
|
28
|
+
scopes: Scopes;
|
|
29
|
+
deadzones: Deadzones;
|
|
30
|
+
reporter: ReporterBus;
|
|
31
|
+
instrumentation: any;
|
|
32
|
+
metrics: any;
|
|
33
|
+
}
|
|
20
34
|
|
|
21
35
|
export enum InstrumentationType {
|
|
22
36
|
SOURCE = 'source',
|
|
@@ -62,3 +76,7 @@ export interface Assess {
|
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
export function getSourceContext(instrType?: InstrumentationType, ops?: any): SourceContext;
|
|
79
|
+
|
|
80
|
+
declare function init(core: Core): Assess;
|
|
81
|
+
|
|
82
|
+
export = init;
|
package/lib/index.js
CHANGED
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const { inspect } = require('util');
|
|
18
19
|
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
20
|
|
|
20
21
|
module.exports = function assess(core) {
|
|
22
|
+
const { scopes: { instrumentation } } = core;
|
|
23
|
+
|
|
21
24
|
const assess = core.assess = {
|
|
22
25
|
install() {
|
|
23
26
|
if (!core.config.getEffectiveValue('assess.enable')) {
|
|
@@ -30,6 +33,16 @@ module.exports = function assess(core) {
|
|
|
30
33
|
},
|
|
31
34
|
};
|
|
32
35
|
|
|
36
|
+
// todo: this is temporary fix for using inspect during creation of event
|
|
37
|
+
// data. once all uses of inspect are refactored out of remaining sinks and
|
|
38
|
+
// propagators etc, this can also be removed.
|
|
39
|
+
const store = { lock: true, name: 'assess.inspect' };
|
|
40
|
+
assess.inspect = function(val, opts) {
|
|
41
|
+
return instrumentation.isLocked() ?
|
|
42
|
+
inspect(val, opts) :
|
|
43
|
+
instrumentation.run(store, inspect, val, opts);
|
|
44
|
+
};
|
|
45
|
+
|
|
33
46
|
require('./rule-scopes')(core);
|
|
34
47
|
require('./get-policy')(core);
|
|
35
48
|
require('./make-source-context')(core);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const {
|
|
18
|
+
const { StringPrototypeToLowerCase } = require('@contrast/common');
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* @param {{
|
|
@@ -48,7 +48,7 @@ module.exports = function(core) {
|
|
|
48
48
|
// copy to avoid storing tracked values
|
|
49
49
|
const headers = { ...req.headers };
|
|
50
50
|
if (headers['content-type']) {
|
|
51
|
-
contentType =
|
|
51
|
+
contentType = StringPrototypeToLowerCase.call(headers['content-type']);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
return {
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
StringPrototypeToLowerCase,
|
|
20
|
+
JSONStringify,
|
|
21
|
+
StringPrototypeSubstring,
|
|
22
22
|
ResponseScanningRule
|
|
23
23
|
} = require('@contrast/common');
|
|
24
24
|
const {
|
|
@@ -130,7 +130,7 @@ module.exports = function(core) {
|
|
|
130
130
|
reportFindings(sourceContext, {
|
|
131
131
|
ruleId: ResponseScanningRule.CACHE_CONTROLS_MISSING,
|
|
132
132
|
vulnerabilityMetadata: {
|
|
133
|
-
data:
|
|
133
|
+
data: JSONStringify(instructions)
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
136
|
}
|
|
@@ -144,7 +144,7 @@ module.exports = function(core) {
|
|
|
144
144
|
let hasFrameBusting = false;
|
|
145
145
|
|
|
146
146
|
if (xFrameHeaders) {
|
|
147
|
-
const xFrameHeadersLC =
|
|
147
|
+
const xFrameHeadersLC = StringPrototypeToLowerCase.call(xFrameHeaders);
|
|
148
148
|
hasFrameBusting =
|
|
149
149
|
xFrameHeadersLC.indexOf('deny') > -1 ||
|
|
150
150
|
xFrameHeadersLC.indexOf('sameorigin') > -1;
|
|
@@ -220,16 +220,16 @@ module.exports = function(core) {
|
|
|
220
220
|
let maxAge;
|
|
221
221
|
|
|
222
222
|
if (header) {
|
|
223
|
-
header =
|
|
223
|
+
header = StringPrototypeToLowerCase.call(header);
|
|
224
224
|
const flag = header.indexOf('max-age');
|
|
225
225
|
if (flag > -1) {
|
|
226
226
|
const equal = header.indexOf('=', flag);
|
|
227
227
|
if (equal > -1) {
|
|
228
228
|
const semicolon = header.indexOf(';', equal);
|
|
229
229
|
if (semicolon > -1) {
|
|
230
|
-
maxAge =
|
|
230
|
+
maxAge = StringPrototypeSubstring.call(header, equal + 1, semicolon);
|
|
231
231
|
} else {
|
|
232
|
-
maxAge =
|
|
232
|
+
maxAge = StringPrototypeSubstring.call(header, equal + 1);
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
}
|
|
@@ -252,7 +252,7 @@ module.exports = function(core) {
|
|
|
252
252
|
let header = responseHeaders[headerName];
|
|
253
253
|
|
|
254
254
|
if (header) {
|
|
255
|
-
header =
|
|
255
|
+
header = StringPrototypeToLowerCase.call(header);
|
|
256
256
|
if (header === 'nosniff') {
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
@@ -273,7 +273,7 @@ module.exports = function(core) {
|
|
|
273
273
|
let header = responseHeaders[headerName];
|
|
274
274
|
|
|
275
275
|
if (header) {
|
|
276
|
-
header =
|
|
276
|
+
header = StringPrototypeToLowerCase.call(header);
|
|
277
277
|
|
|
278
278
|
reportFindings(sourceContext, {
|
|
279
279
|
ruleId: ResponseScanningRule.X_POWERED_BY_HEADER,
|