@contrast/assess 1.58.2 → 1.59.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/dataflow/propagation/install/util-format.js +44 -21
- package/lib/dataflow/sources/install/body-parser.js +1 -1
- package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +3 -1
- package/lib/dataflow/sources/install/qs6.js +5 -5
- package/lib/dataflow/sources/install/querystring.js +2 -1
- package/lib/index.d.ts +0 -1
- package/lib/make-source-context.js +7 -37
- package/lib/sampler/common.js +7 -11
- package/package.json +12 -11
|
@@ -24,12 +24,35 @@ module.exports = function(core) {
|
|
|
24
24
|
patcher,
|
|
25
25
|
depHooks,
|
|
26
26
|
assess: {
|
|
27
|
+
inspect,
|
|
27
28
|
getPropagatorContext,
|
|
28
29
|
eventFactory: { createPropagationEvent },
|
|
29
30
|
dataflow: { tracker }
|
|
30
31
|
}
|
|
31
32
|
} = core;
|
|
32
33
|
|
|
34
|
+
function traverseObject(obj, result, tags, history, depth = 1) {
|
|
35
|
+
let i = 0;
|
|
36
|
+
for (const val of Object.values(obj)) {
|
|
37
|
+
|
|
38
|
+
if (typeof val === 'object' && depth <= 4) tags = traverseObject(val, result, tags, history, depth += 1);
|
|
39
|
+
|
|
40
|
+
const valInfo = tracker.getData(val);
|
|
41
|
+
if (!valInfo || depth > 4) break;
|
|
42
|
+
|
|
43
|
+
const currIdx = result.indexOf(val, i);
|
|
44
|
+
if (currIdx > -1) {
|
|
45
|
+
i = currIdx + val.length;
|
|
46
|
+
} else {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
tags = createAppendTags(tags, valInfo.tags, currIdx);
|
|
50
|
+
history.push({ ...valInfo });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return tags;
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
return core.assess.dataflow.propagation.utilFormat = {
|
|
34
57
|
install() {
|
|
35
58
|
depHooks.resolve({ name: 'util', version: '*' }, (util) => {
|
|
@@ -57,13 +80,14 @@ module.exports = function(core) {
|
|
|
57
80
|
|
|
58
81
|
for (i; i < args.length; i++) {
|
|
59
82
|
let arg = args[i];
|
|
83
|
+
if (!arg) continue;
|
|
84
|
+
|
|
60
85
|
const formatChar = formatChars[i - 1];
|
|
61
86
|
if (formatChar) {
|
|
62
87
|
switch (formatChar) {
|
|
63
88
|
case 's':
|
|
64
89
|
if (typeof arg === 'object') {
|
|
65
|
-
//
|
|
66
|
-
arg = arg?.toString ? arg.toString() : util.inspect(arg, { depth: 0, colors: false, compact: 3 });
|
|
90
|
+
break; // handled below
|
|
67
91
|
} else {
|
|
68
92
|
arg = String(arg);
|
|
69
93
|
}
|
|
@@ -77,36 +101,35 @@ module.exports = function(core) {
|
|
|
77
101
|
arg = JSON.stringify(arg) ?? 'undefined';
|
|
78
102
|
break;
|
|
79
103
|
case 'o':
|
|
80
|
-
//
|
|
81
|
-
arg = util.inspect(arg, { showHidden: true, showProxy: true });
|
|
82
|
-
break;
|
|
104
|
+
break; // handled below
|
|
83
105
|
case 'O':
|
|
84
|
-
//
|
|
85
|
-
arg = util.inspect(arg);
|
|
86
|
-
break;
|
|
106
|
+
break; // handled below
|
|
87
107
|
case 'c':
|
|
88
108
|
// c is ignored and skipped
|
|
89
109
|
arg = '';
|
|
90
110
|
break;
|
|
91
111
|
}
|
|
92
112
|
} else if (typeof arg !== 'string') {
|
|
93
|
-
arg =
|
|
113
|
+
arg = inspect(arg);
|
|
94
114
|
}
|
|
95
115
|
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
if (typeof arg === 'string') {
|
|
117
|
+
const argInfo = tracker.getData(arg);
|
|
118
|
+
if (!argInfo) continue;
|
|
98
119
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
const currIdx = result.indexOf(arg, idx);
|
|
121
|
+
if (currIdx > -1) {
|
|
122
|
+
idx = currIdx + arg.length;
|
|
123
|
+
} else {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
newTags = createAppendTags(newTags, argInfo.tags, currIdx);
|
|
127
|
+
history.push({ ...argInfo });
|
|
128
|
+
eventArgs.push({ value: argInfo ? argInfo.value : arg, tracked: !!argInfo });
|
|
129
|
+
} else if (typeof arg === 'object') {
|
|
130
|
+
newTags = traverseObject(arg, result, newTags, history);
|
|
131
|
+
eventArgs.push({ value: inspect(arg), tracked: false });
|
|
104
132
|
}
|
|
105
|
-
|
|
106
|
-
newTags = createAppendTags(newTags, argInfo.tags, currIdx);
|
|
107
|
-
|
|
108
|
-
history.push({ ...argInfo });
|
|
109
|
-
eventArgs.push({ value: argInfo ? argInfo.value : arg, tracked: !!argInfo });
|
|
110
133
|
}
|
|
111
134
|
|
|
112
135
|
const resultInfo = tracker.getData(result);
|
|
@@ -85,7 +85,7 @@ module.exports = function init(core) {
|
|
|
85
85
|
},
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
sourceContext.parsedBody = !!Object.keys(_data).length;
|
|
88
|
+
sourceContext.parsedBody = !!(_data && Object.keys(_data).length);
|
|
89
89
|
} catch (err) {
|
|
90
90
|
logger.error({ err, funcKey: data.funcKey }, 'unable to handle source');
|
|
91
91
|
}
|
|
@@ -23,6 +23,7 @@ module.exports = (core) => {
|
|
|
23
23
|
depHooks,
|
|
24
24
|
patcher,
|
|
25
25
|
logger,
|
|
26
|
+
scopes,
|
|
26
27
|
assess: {
|
|
27
28
|
getSourceContext,
|
|
28
29
|
dataflow: { sources }
|
|
@@ -51,7 +52,8 @@ module.exports = (core) => {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
data.args[1] = async function contrastNext(origErr) {
|
|
54
|
-
const
|
|
55
|
+
const contentType = scopes.sources.getStore()?.sourceInfo?.contentType;
|
|
56
|
+
const inputType = contentType?.includes?.('/json')
|
|
55
57
|
? InputType.JSON_VALUE
|
|
56
58
|
: typeof ctx.request.body == 'object'
|
|
57
59
|
? InputType.PARAMETER_VALUE
|
|
@@ -23,6 +23,7 @@ module.exports = (core) => {
|
|
|
23
23
|
depHooks,
|
|
24
24
|
patcher,
|
|
25
25
|
logger,
|
|
26
|
+
scopes,
|
|
26
27
|
assess: {
|
|
27
28
|
getSourceContext,
|
|
28
29
|
dataflow: { sources }
|
|
@@ -38,21 +39,20 @@ module.exports = (core) => {
|
|
|
38
39
|
patchType,
|
|
39
40
|
post({ args, hooked, orig, result, funcKey }) {
|
|
40
41
|
const sourceContext = getSourceContext();
|
|
41
|
-
|
|
42
|
-
if (!sourceContext) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
42
|
+
if (!sourceContext) return;
|
|
45
43
|
|
|
46
44
|
if (sourceContext.parsedQuery) {
|
|
47
45
|
logger.trace({ inputType, funcKey }, 'values already tracked');
|
|
48
46
|
return;
|
|
49
47
|
}
|
|
50
48
|
|
|
49
|
+
const queries = scopes.sources.getStore()?.sourceInfo?.queries;
|
|
50
|
+
|
|
51
51
|
// We need to run analysis for the `qs` result only when it's used as a query parser.
|
|
52
52
|
// `qs` is used also for parsing bodies, but these cases we handle individually with
|
|
53
53
|
// the respective library that's using it (e.g. `formidable`, `co-body`) because in
|
|
54
54
|
// some cases its use is optional and we cannot rely on it.
|
|
55
|
-
if (
|
|
55
|
+
if (queries === args[0]) {
|
|
56
56
|
try {
|
|
57
57
|
sources.handle({
|
|
58
58
|
context: 'req.query',
|
|
@@ -24,6 +24,7 @@ module.exports = (core) => {
|
|
|
24
24
|
depHooks,
|
|
25
25
|
patcher,
|
|
26
26
|
logger,
|
|
27
|
+
scopes,
|
|
27
28
|
} = core;
|
|
28
29
|
|
|
29
30
|
core.assess.dataflow.sources.querystringInstrumentation = {
|
|
@@ -46,7 +47,7 @@ module.exports = (core) => {
|
|
|
46
47
|
|
|
47
48
|
// We only run analysis for the `querystring` result when it's used
|
|
48
49
|
// as the framework's query parser
|
|
49
|
-
if (
|
|
50
|
+
if (scopes.sources.getStore().sourceInfo?.queries === args[0]) {
|
|
50
51
|
try {
|
|
51
52
|
core.assess.dataflow.sources.handle({
|
|
52
53
|
context: 'req.query',
|
package/lib/index.d.ts
CHANGED
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { primordials: { StringPrototypeToLowerCase, StringPrototypeSlice } } = require('@contrast/common');
|
|
19
18
|
const { Core } = require('@contrast/core/lib/ioc/core');
|
|
20
19
|
|
|
21
20
|
/**
|
|
@@ -33,57 +32,28 @@ function factory(core) {
|
|
|
33
32
|
const { assess, logger } = core;
|
|
34
33
|
|
|
35
34
|
/**
|
|
35
|
+
* todo: how to handle non-HTTP sources
|
|
36
36
|
* @returns {import('@contrast/assess').SourceContext}
|
|
37
37
|
*/
|
|
38
|
-
return core.assess.makeSourceContext = function(
|
|
39
|
-
try {
|
|
38
|
+
return core.assess.makeSourceContext = function ({ store, incomingMessage: req }) {
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
try {
|
|
41
|
+
const ctx = store.assess = {
|
|
42
42
|
// default policy to `null` until it is set later below. this will cause
|
|
43
43
|
// all instrumentation to short-circuit, see `./get-source-context.js`.
|
|
44
44
|
policy: null,
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
if (!core.config.getEffectiveValue('assess.enable'))
|
|
48
|
-
return ctx;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// todo: how to handle non-HTTP sources
|
|
52
|
-
const { incomingMessage: req } = sourceData;
|
|
53
|
-
|
|
54
|
-
// minimally process the request data for sampling and exclusions.
|
|
55
|
-
// more request fields will be appended in final result below.
|
|
56
|
-
let uriPath;
|
|
57
|
-
let queries;
|
|
58
|
-
const idx = req.url.indexOf('?');
|
|
59
|
-
if (idx >= 0) {
|
|
60
|
-
uriPath = StringPrototypeSlice.call(req.url, 0, idx);
|
|
61
|
-
queries = StringPrototypeSlice.call(req.url, idx + 1);
|
|
62
|
-
} else {
|
|
63
|
-
uriPath = req.url;
|
|
64
|
-
queries = '';
|
|
65
|
-
}
|
|
66
|
-
ctx.reqData = {
|
|
67
|
-
method: req.method,
|
|
68
|
-
uriPath,
|
|
69
|
-
queries,
|
|
70
|
-
};
|
|
47
|
+
if (!core.config.getEffectiveValue('assess.enable')) return ctx;
|
|
71
48
|
|
|
72
49
|
// check whether sampling allows processing
|
|
73
|
-
ctx.sampleInfo = assess.sampler?.getSampleInfo(
|
|
50
|
+
ctx.sampleInfo = assess.sampler?.getSampleInfo(store.sourceInfo) ?? null;
|
|
74
51
|
if (ctx.sampleInfo?.canSample === false) return ctx;
|
|
75
52
|
|
|
76
53
|
// set policy - can be returned as `null` if request is url-excluded.
|
|
77
|
-
ctx.policy = assess.getPolicy(
|
|
54
|
+
ctx.policy = assess.getPolicy(store.sourceInfo);
|
|
78
55
|
if (!ctx.policy) return ctx;
|
|
79
56
|
|
|
80
|
-
// build remaining reqData
|
|
81
|
-
ctx.reqData.headers = { ...req.headers }; // copy to avoid storing tracked values
|
|
82
|
-
ctx.reqData.ip = req.socket.remoteAddress;
|
|
83
|
-
ctx.reqData.httpVersion = req.httpVersion;
|
|
84
|
-
if (ctx.reqData.headers['content-type'])
|
|
85
|
-
ctx.reqData.contentType = StringPrototypeToLowerCase.call(ctx.reqData.headers['content-type']);
|
|
86
|
-
|
|
87
57
|
ctx.propagationEventsCount = 0;
|
|
88
58
|
ctx.sourceEventsCount = 0;
|
|
89
59
|
ctx.responseData = {};
|
package/lib/sampler/common.js
CHANGED
|
@@ -31,23 +31,22 @@ class RouteAnalysisMonitor {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {string}
|
|
34
|
+
* @param {import('@contrast/common').SourceInfo} sourceInfo
|
|
35
|
+
* @param {string} sourceInfo.normalizedUri
|
|
36
36
|
* @returns {AnalysisInfo}
|
|
37
37
|
*/
|
|
38
|
-
getAnalysisInfo({ method,
|
|
39
|
-
const normalizedUrl = this._core.routeCoverage.uriPathToNormalizedUrl(uriPath);
|
|
38
|
+
getAnalysisInfo({ method, normalizedUri }) {
|
|
40
39
|
const now = Date.now();
|
|
41
40
|
|
|
42
|
-
if (
|
|
43
|
-
const key = `${method}:${
|
|
41
|
+
if (normalizedUri) {
|
|
42
|
+
const key = `${method}:${normalizedUri}`;
|
|
44
43
|
let routeMeta = this._normalCache.get(key);
|
|
45
44
|
|
|
46
45
|
// not in cache, not paused
|
|
47
46
|
if (!routeMeta) {
|
|
48
47
|
routeMeta = {
|
|
49
48
|
pauseEnd: now + this._ttl,
|
|
50
|
-
normalizedUrl,
|
|
49
|
+
normalizedUrl: normalizedUri,
|
|
51
50
|
};
|
|
52
51
|
this._normalCache.set(key, routeMeta);
|
|
53
52
|
|
|
@@ -64,8 +63,6 @@ class RouteAnalysisMonitor {
|
|
|
64
63
|
|
|
65
64
|
// was in cache and still paused
|
|
66
65
|
return { paused: true, ...routeMeta };
|
|
67
|
-
} else {
|
|
68
|
-
// todo - handle "dynamic" routes
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
return this._defaultAnalysisInfo;
|
|
@@ -105,7 +102,6 @@ class ProbabilisticSampler extends BaseSampler {
|
|
|
105
102
|
|
|
106
103
|
getSampleInfo(sourceInfo) {
|
|
107
104
|
const { baseline, base_probability } = this.opts;
|
|
108
|
-
const { reqData } = sourceInfo.store.assess;
|
|
109
105
|
|
|
110
106
|
if (this.baseline < baseline) {
|
|
111
107
|
this.baseline++;
|
|
@@ -119,7 +115,7 @@ class ProbabilisticSampler extends BaseSampler {
|
|
|
119
115
|
|
|
120
116
|
// check route monitoring before sampling
|
|
121
117
|
if (canSample) {
|
|
122
|
-
const routeInfo = this.routeMonitor?.getAnalysisInfo(
|
|
118
|
+
const routeInfo = this.routeMonitor?.getAnalysisInfo(sourceInfo);
|
|
123
119
|
|
|
124
120
|
if (routeInfo) {
|
|
125
121
|
// don't sample if analysis is paused
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.59.0",
|
|
4
4
|
"description": "Contrast service providing framework-agnostic Assess support",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -20,17 +20,18 @@
|
|
|
20
20
|
"test": "bash ../scripts/test.sh"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@contrast/common": "1.
|
|
24
|
-
"@contrast/config": "1.
|
|
25
|
-
"@contrast/core": "1.
|
|
26
|
-
"@contrast/dep-hooks": "1.
|
|
23
|
+
"@contrast/common": "1.35.0",
|
|
24
|
+
"@contrast/config": "1.50.0",
|
|
25
|
+
"@contrast/core": "1.55.0",
|
|
26
|
+
"@contrast/dep-hooks": "1.24.0",
|
|
27
27
|
"@contrast/distringuish": "^5.1.0",
|
|
28
|
-
"@contrast/instrumentation": "1.
|
|
29
|
-
"@contrast/logger": "1.
|
|
30
|
-
"@contrast/patcher": "1.
|
|
31
|
-
"@contrast/rewriter": "1.
|
|
32
|
-
"@contrast/route-coverage": "1.
|
|
33
|
-
"@contrast/scopes": "1.
|
|
28
|
+
"@contrast/instrumentation": "1.34.0",
|
|
29
|
+
"@contrast/logger": "1.28.0",
|
|
30
|
+
"@contrast/patcher": "1.27.0",
|
|
31
|
+
"@contrast/rewriter": "1.31.0",
|
|
32
|
+
"@contrast/route-coverage": "1.46.0",
|
|
33
|
+
"@contrast/scopes": "1.25.0",
|
|
34
|
+
"@contrast/sources": "1.1.0",
|
|
34
35
|
"semver": "^7.6.0"
|
|
35
36
|
}
|
|
36
37
|
}
|