@contrast/assess 1.17.0 → 1.19.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/constants.js +26 -0
- package/lib/crypto-analysis/common.js +20 -0
- package/lib/crypto-analysis/index.js +44 -0
- package/lib/crypto-analysis/install/crypto.js +151 -0
- package/lib/crypto-analysis/install/math.js +99 -0
- package/lib/dataflow/propagation/index.js +2 -1
- package/lib/dataflow/propagation/install/JSON/parse.js +12 -11
- package/lib/dataflow/propagation/install/JSON/stringify.js +1 -1
- package/lib/dataflow/propagation/install/array-prototype-join.js +1 -1
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +2 -2
- package/lib/dataflow/propagation/install/ejs/index.js +1 -0
- package/lib/dataflow/propagation/install/ejs/template.js +77 -0
- package/lib/dataflow/propagation/install/sequelize/index.js +31 -0
- package/lib/dataflow/propagation/install/sequelize/query-generator.js +90 -0
- package/lib/dataflow/propagation/install/{sequelize.js → sequelize/sql-string.js} +3 -3
- package/lib/dataflow/propagation/install/util-format.js +126 -0
- package/lib/dataflow/sinks/index.js +1 -0
- package/lib/dataflow/sinks/install/child-process.js +20 -14
- package/lib/dataflow/sinks/install/eval.js +16 -14
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +14 -8
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +12 -5
- package/lib/dataflow/sinks/install/fs.js +7 -7
- package/lib/dataflow/sinks/install/function.js +8 -12
- package/lib/dataflow/sinks/install/http/request.js +16 -8
- package/lib/dataflow/sinks/install/http/server-response.js +11 -2
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +15 -8
- package/lib/dataflow/sinks/install/libxmljs.js +15 -10
- package/lib/dataflow/sinks/install/marsdb.js +13 -8
- package/lib/dataflow/sinks/install/mongodb.js +25 -15
- package/lib/dataflow/sinks/install/mssql.js +20 -9
- package/lib/dataflow/sinks/install/mysql.js +15 -8
- package/lib/dataflow/sinks/install/node-serialize.js +101 -0
- package/lib/dataflow/sinks/install/postgres.js +17 -4
- package/lib/dataflow/sinks/install/sequelize.js +16 -9
- package/lib/dataflow/sinks/install/sqlite3.js +20 -7
- package/lib/dataflow/sinks/install/vm.js +19 -17
- package/lib/dataflow/sources/install/http.js +14 -42
- package/lib/dataflow/sources/install/koa/index.js +1 -0
- package/lib/dataflow/sources/install/koa/koa-multer.js +102 -0
- package/lib/dataflow/sources/install/multer1.js +25 -51
- package/lib/dataflow/sources/install/querystring.js +1 -4
- package/lib/event-factory.js +47 -0
- package/lib/get-policy.js +68 -0
- package/lib/get-source-context.js +62 -0
- package/lib/index.d.ts +50 -0
- package/lib/index.js +20 -19
- package/lib/make-source-context.js +74 -0
- package/lib/response-scanning/handlers/index.js +55 -28
- package/lib/response-scanning/install/http.js +13 -7
- package/lib/rule-scopes.js +48 -0
- package/lib/session-configuration/handlers.js +4 -3
- package/lib/session-configuration/install/express-session.js +8 -2
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -16,32 +16,33 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
|
-
const sessionConfiguration = require('./session-configuration');
|
|
20
|
-
const dataflow = require('./dataflow');
|
|
21
|
-
const responseScanning = require('./response-scanning');
|
|
22
|
-
const eventFactory = require('./event-factory');
|
|
23
19
|
|
|
24
20
|
module.exports = function assess(core) {
|
|
25
|
-
const assess = core.assess = {
|
|
21
|
+
const assess = core.assess = {
|
|
22
|
+
install() {
|
|
23
|
+
if (!core.config.getEffectiveValue('assess.enable')) {
|
|
24
|
+
core.logger.debug('assess is disabled, skipping installation');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
core.rewriter.install('assess');
|
|
29
|
+
callChildComponentMethodsSync(core.assess, 'install');
|
|
30
|
+
},
|
|
31
|
+
};
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
require('./rule-scopes')(core);
|
|
34
|
+
require('./get-policy')(core);
|
|
35
|
+
require('./make-source-context')(core);
|
|
36
|
+
require('./get-source-context')(core);
|
|
37
|
+
require('./event-factory')(core);
|
|
38
|
+
require('./crypto-analysis')(core);
|
|
39
|
+
require('./dataflow')(core);
|
|
40
|
+
require('./response-scanning')(core);
|
|
41
|
+
require('./session-configuration')(core);
|
|
31
42
|
|
|
32
43
|
// todo
|
|
33
44
|
// crypto rule implementations
|
|
34
45
|
// static rule implementations in coordination with (CLI) rewriter
|
|
35
46
|
|
|
36
|
-
assess.install = function() {
|
|
37
|
-
if (!core.config.getEffectiveValue('assess.enable')) {
|
|
38
|
-
core.logger.debug('assess is disabled, skipping installation');
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
core.rewriter.install('assess');
|
|
43
|
-
callChildComponentMethodsSync(core.assess, 'install');
|
|
44
|
-
};
|
|
45
|
-
|
|
46
47
|
return assess;
|
|
47
48
|
};
|
|
@@ -0,0 +1,74 @@
|
|
|
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 { toLowerCase } = require('@contrast/common');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {{
|
|
22
|
+
* assess: import('@contrast/assess').Assess,
|
|
23
|
+
* logger: import('@contrast/logger').Logger,
|
|
24
|
+
* }} core
|
|
25
|
+
*/
|
|
26
|
+
module.exports = function(core) {
|
|
27
|
+
const {
|
|
28
|
+
assess: { getPolicy },
|
|
29
|
+
logger,
|
|
30
|
+
} = core;
|
|
31
|
+
|
|
32
|
+
return core.assess.makeSourceContext = function (req, res) {
|
|
33
|
+
let contentType, queries, uriPath;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const ix = req.url.indexOf('?');
|
|
37
|
+
if (ix >= 0) {
|
|
38
|
+
uriPath = req.url.slice(0, ix);
|
|
39
|
+
queries = req.url.slice(ix + 1);
|
|
40
|
+
} else {
|
|
41
|
+
uriPath = req.url;
|
|
42
|
+
queries = '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// copy to avoid storing tracked values
|
|
46
|
+
const headers = { ...req.headers };
|
|
47
|
+
if (headers['content-type']) {
|
|
48
|
+
contentType = toLowerCase(headers['content-type']);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
propagationEventsCount: 0,
|
|
53
|
+
sourceEventsCount: 0,
|
|
54
|
+
policy: getPolicy(),
|
|
55
|
+
reqData: {
|
|
56
|
+
ip: req.socket.remoteAddress,
|
|
57
|
+
httpVersion: req.httpVersion,
|
|
58
|
+
method: req.method,
|
|
59
|
+
headers,
|
|
60
|
+
uriPath,
|
|
61
|
+
queries,
|
|
62
|
+
contentType,
|
|
63
|
+
},
|
|
64
|
+
responseData: {}
|
|
65
|
+
};
|
|
66
|
+
} catch (err) {
|
|
67
|
+
logger.error(
|
|
68
|
+
{ err },
|
|
69
|
+
'unable to construct assess store. assess will be disabled for request.'
|
|
70
|
+
);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
};
|
|
@@ -33,6 +33,19 @@ const {
|
|
|
33
33
|
checkCspSources
|
|
34
34
|
} = require('./utils');
|
|
35
35
|
|
|
36
|
+
const {
|
|
37
|
+
AUTOCOMPLETE_MISSING,
|
|
38
|
+
CACHE_CONTROLS_MISSING,
|
|
39
|
+
CLICKJACKING_CONTROL_MISSING,
|
|
40
|
+
CSP_HEADER_MISSING,
|
|
41
|
+
CSP_HEADER_INSECURE,
|
|
42
|
+
HSTS_HEADER_MISSING,
|
|
43
|
+
PARAMETER_POLLUTION,
|
|
44
|
+
XCONTENTTYPE_HEADER_MISSING,
|
|
45
|
+
X_POWERED_BY_HEADER,
|
|
46
|
+
XXSPROTECTION_HEADER_DISABLED,
|
|
47
|
+
} = ResponseScanningRule;
|
|
48
|
+
|
|
36
49
|
module.exports = function(core) {
|
|
37
50
|
const {
|
|
38
51
|
assess: {
|
|
@@ -44,7 +57,10 @@ module.exports = function(core) {
|
|
|
44
57
|
} = core;
|
|
45
58
|
|
|
46
59
|
responseScanning.handleAutoCompleteMissing = function(sourceContext, { responseHeaders, responseBody }) {
|
|
47
|
-
if (
|
|
60
|
+
if (
|
|
61
|
+
!isEnabled(AUTOCOMPLETE_MISSING, sourceContext) ||
|
|
62
|
+
!isHtmlContent(responseHeaders)
|
|
63
|
+
) {
|
|
48
64
|
return;
|
|
49
65
|
}
|
|
50
66
|
|
|
@@ -72,7 +88,10 @@ module.exports = function(core) {
|
|
|
72
88
|
const instructions = [];
|
|
73
89
|
|
|
74
90
|
// de-dupe; this will be re-emitted for parseableBody handlers anyway
|
|
75
|
-
if (
|
|
91
|
+
if (
|
|
92
|
+
!isEnabled(CACHE_CONTROLS_MISSING, sourceContext) ||
|
|
93
|
+
(isParseableResponse(responseHeaders) && !responseBody)
|
|
94
|
+
) {
|
|
76
95
|
return;
|
|
77
96
|
}
|
|
78
97
|
const cacheControlHeader = responseHeaders['cache-control'];
|
|
@@ -118,6 +137,8 @@ module.exports = function(core) {
|
|
|
118
137
|
};
|
|
119
138
|
|
|
120
139
|
responseScanning.handleClickJackingControlsMissing = function(sourceContext, { responseHeaders }) {
|
|
140
|
+
if (!isEnabled(CLICKJACKING_CONTROL_MISSING, sourceContext)) return;
|
|
141
|
+
|
|
121
142
|
// look for x-frame-options headers with deny or sameorigin
|
|
122
143
|
const xFrameHeaders = responseHeaders['x-frame-options'];
|
|
123
144
|
let hasFrameBusting = false;
|
|
@@ -135,6 +156,8 @@ module.exports = function(core) {
|
|
|
135
156
|
};
|
|
136
157
|
|
|
137
158
|
responseScanning.handleParameterPollution = function(sourceContext, { responseBody }) {
|
|
159
|
+
if (!isEnabled(PARAMETER_POLLUTION, sourceContext)) return;
|
|
160
|
+
|
|
138
161
|
// look for form tag with missing action attribute.
|
|
139
162
|
// ex: <form method="post">..
|
|
140
163
|
const elements = getElements('form', responseBody);
|
|
@@ -154,21 +177,23 @@ module.exports = function(core) {
|
|
|
154
177
|
};
|
|
155
178
|
|
|
156
179
|
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
180
|
+
* Checks the response headers for the CSP. If found, and insecure, will return
|
|
181
|
+
* the evidence for reporting.
|
|
182
|
+
*
|
|
183
|
+
* @param {Object} responseHeaders - HTTP headers object.
|
|
184
|
+
* @returns {Object} - Evidence for insecure CSP header.
|
|
185
|
+
*/
|
|
163
186
|
responseScanning.handleCspHeader = function(sourceContext, { responseHeaders }) {
|
|
164
187
|
const cspHeaders = getCspHeaders(responseHeaders);
|
|
165
188
|
|
|
166
189
|
// Don't report if not set; this report belongs to 'csp-header-missing'
|
|
167
|
-
if (!cspHeaders) {
|
|
190
|
+
if (!cspHeaders && isEnabled(CSP_HEADER_MISSING, sourceContext)) {
|
|
168
191
|
reportFindings(sourceContext, { ruleId: ResponseScanningRule.CSP_HEADER_MISSING });
|
|
169
192
|
return;
|
|
170
193
|
}
|
|
171
194
|
|
|
195
|
+
if (!isEnabled(CSP_HEADER_INSECURE, sourceContext)) return;
|
|
196
|
+
|
|
172
197
|
const vulnerabilityMetadata = checkCspSources(cspHeaders);
|
|
173
198
|
|
|
174
199
|
if (vulnerabilityMetadata.insecure) {
|
|
@@ -189,6 +214,8 @@ module.exports = function(core) {
|
|
|
189
214
|
};
|
|
190
215
|
|
|
191
216
|
responseScanning.handleHstsHeaderMissing = function(sourceContext, { responseHeaders }) {
|
|
217
|
+
if (!isEnabled(HSTS_HEADER_MISSING, sourceContext)) return;
|
|
218
|
+
|
|
192
219
|
let header = responseHeaders['strict-transport-security'];
|
|
193
220
|
let maxAge;
|
|
194
221
|
|
|
@@ -219,6 +246,8 @@ module.exports = function(core) {
|
|
|
219
246
|
};
|
|
220
247
|
|
|
221
248
|
responseScanning.handleXContentTypeHeaderMissing = function(sourceContext, { responseHeaders }) {
|
|
249
|
+
if (!isEnabled(XCONTENTTYPE_HEADER_MISSING, sourceContext)) return;
|
|
250
|
+
|
|
222
251
|
const headerName = 'x-content-type-options';
|
|
223
252
|
let header = responseHeaders[headerName];
|
|
224
253
|
|
|
@@ -237,44 +266,42 @@ module.exports = function(core) {
|
|
|
237
266
|
});
|
|
238
267
|
};
|
|
239
268
|
|
|
240
|
-
// NODE-3135
|
|
241
269
|
responseScanning.handleXPoweredByHeader = function(sourceContext, { responseHeaders }) {
|
|
270
|
+
if (!isEnabled(X_POWERED_BY_HEADER, sourceContext)) return;
|
|
271
|
+
|
|
242
272
|
const headerName = 'x-powered-by';
|
|
243
273
|
let header = responseHeaders[headerName];
|
|
244
274
|
|
|
245
275
|
if (header) {
|
|
246
276
|
header = toLowerCase(header);
|
|
247
277
|
|
|
248
|
-
const instructions = [
|
|
249
|
-
{
|
|
250
|
-
type: 'Header',
|
|
251
|
-
name: headerName,
|
|
252
|
-
value: header
|
|
253
|
-
}
|
|
254
|
-
];
|
|
255
|
-
|
|
256
278
|
reportFindings(sourceContext, {
|
|
257
279
|
ruleId: ResponseScanningRule.X_POWERED_BY_HEADER,
|
|
258
280
|
vulnerabilityMetadata: {
|
|
259
|
-
|
|
281
|
+
platform: header,
|
|
260
282
|
}
|
|
261
283
|
});
|
|
262
284
|
}
|
|
263
285
|
};
|
|
264
286
|
|
|
265
287
|
responseScanning.handleXxsProtectionHeaderDisabled = function(sourceContext, { responseHeaders }) {
|
|
266
|
-
|
|
288
|
+
if (!isEnabled(XXSPROTECTION_HEADER_DISABLED, sourceContext)) return;
|
|
267
289
|
|
|
268
|
-
|
|
290
|
+
const header = responseHeaders['x-xss-protection'];
|
|
269
291
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
292
|
+
if (header && !header.startsWith('1')) {
|
|
293
|
+
reportFindings(sourceContext, {
|
|
294
|
+
ruleId: ResponseScanningRule.XXSPROTECTION_HEADER_DISABLED,
|
|
295
|
+
vulnerabilityMetadata: {
|
|
296
|
+
data: header,
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
276
300
|
};
|
|
277
301
|
|
|
302
|
+
function isEnabled(ruleId, sourceContext) {
|
|
303
|
+
return !!sourceContext?.policy?.enabledRules?.has?.(ruleId);
|
|
304
|
+
}
|
|
305
|
+
|
|
278
306
|
return responseScanning;
|
|
279
307
|
};
|
|
280
|
-
|
|
@@ -17,12 +17,17 @@
|
|
|
17
17
|
|
|
18
18
|
const { split, substring, toLowerCase, trim } = require('@contrast/common');
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @param {{
|
|
22
|
+
* assess: import('@contrast/assess').Assess,
|
|
23
|
+
* }} core
|
|
24
|
+
*/
|
|
20
25
|
module.exports = function(core) {
|
|
21
26
|
const {
|
|
22
27
|
depHooks,
|
|
23
28
|
patcher,
|
|
24
|
-
scopes: { sources },
|
|
25
29
|
assess: {
|
|
30
|
+
getSourceContext,
|
|
26
31
|
responseScanning: {
|
|
27
32
|
handleAutoCompleteMissing,
|
|
28
33
|
handleCacheControlsMissing,
|
|
@@ -36,6 +41,7 @@ module.exports = function(core) {
|
|
|
36
41
|
}
|
|
37
42
|
}
|
|
38
43
|
} = core;
|
|
44
|
+
|
|
39
45
|
const http = core.assess.responseScanning.httpInstrumentation = {};
|
|
40
46
|
|
|
41
47
|
function parseHeaders(rawHeaders) {
|
|
@@ -90,7 +96,7 @@ module.exports = function(core) {
|
|
|
90
96
|
name,
|
|
91
97
|
patchType,
|
|
92
98
|
post(data) {
|
|
93
|
-
const sourceContext =
|
|
99
|
+
const sourceContext = getSourceContext();
|
|
94
100
|
if (!sourceContext) return;
|
|
95
101
|
|
|
96
102
|
const evaluationContext = {
|
|
@@ -108,7 +114,7 @@ module.exports = function(core) {
|
|
|
108
114
|
name,
|
|
109
115
|
patchType,
|
|
110
116
|
post(data) {
|
|
111
|
-
const sourceContext =
|
|
117
|
+
const sourceContext = getSourceContext();
|
|
112
118
|
if (!sourceContext) return;
|
|
113
119
|
|
|
114
120
|
const evaluationContext = {
|
|
@@ -130,7 +136,7 @@ module.exports = function(core) {
|
|
|
130
136
|
name,
|
|
131
137
|
patchType,
|
|
132
138
|
post(data) {
|
|
133
|
-
const sourceContext =
|
|
139
|
+
const sourceContext = getSourceContext();
|
|
134
140
|
if (!sourceContext) return;
|
|
135
141
|
|
|
136
142
|
const headersSymbol = Object.getOwnPropertySymbols(data.obj).find(symbol => symbol.toString().includes('headers'));
|
|
@@ -149,7 +155,7 @@ module.exports = function(core) {
|
|
|
149
155
|
name,
|
|
150
156
|
patchType,
|
|
151
157
|
post(data) {
|
|
152
|
-
const sourceContext =
|
|
158
|
+
const sourceContext = getSourceContext();
|
|
153
159
|
if (!sourceContext) return;
|
|
154
160
|
|
|
155
161
|
const headersSymbol = Object.getOwnPropertySymbols(data.result).find(symbol => symbol.toString().includes('headers'));
|
|
@@ -182,7 +188,7 @@ module.exports = function(core) {
|
|
|
182
188
|
name: 'Http2Stream.write',
|
|
183
189
|
patchType,
|
|
184
190
|
post(data) {
|
|
185
|
-
const sourceContext =
|
|
191
|
+
const sourceContext = getSourceContext();
|
|
186
192
|
if (!sourceContext) return;
|
|
187
193
|
|
|
188
194
|
const evaluationContext = {
|
|
@@ -198,7 +204,7 @@ module.exports = function(core) {
|
|
|
198
204
|
name: 'Http2Stream.end',
|
|
199
205
|
patchType,
|
|
200
206
|
post(data) {
|
|
201
|
-
const sourceContext =
|
|
207
|
+
const sourceContext = getSourceContext();
|
|
202
208
|
if (!sourceContext) return;
|
|
203
209
|
|
|
204
210
|
const evaluationContext = {
|
|
@@ -0,0 +1,48 @@
|
|
|
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 { AsyncLocalStorage } = require('async_hooks');
|
|
19
|
+
/**
|
|
20
|
+
* @param {Object} core
|
|
21
|
+
* @param {import('@contrast/assess').Assess} core.assess
|
|
22
|
+
*/
|
|
23
|
+
module.exports = function (core) {
|
|
24
|
+
|
|
25
|
+
/** @type {Map<Rule,AsyncLocalStorage>} */
|
|
26
|
+
const scopes = new Map();
|
|
27
|
+
/** @type {{locked:boolean}}*/
|
|
28
|
+
const store = Object.freeze({ locked: true });
|
|
29
|
+
|
|
30
|
+
return core.assess.ruleScopes = {
|
|
31
|
+
/**
|
|
32
|
+
* @param {import('@contrast/common').Rule} ruleId
|
|
33
|
+
* @param {functionn} cb
|
|
34
|
+
*/
|
|
35
|
+
run(ruleId, cb) {
|
|
36
|
+
if (!scopes.has(ruleId)) scopes.set(ruleId, new AsyncLocalStorage());
|
|
37
|
+
return scopes.get(ruleId).run(store, cb);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {Rule} ruleId
|
|
42
|
+
*/
|
|
43
|
+
isLocked(ruleId) {
|
|
44
|
+
return !!scopes.get(ruleId)?.getStore?.()?.locked;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
};
|
|
@@ -27,9 +27,10 @@ module.exports = function (core) {
|
|
|
27
27
|
} = core;
|
|
28
28
|
|
|
29
29
|
const checkCookieValue = (ruleId, sinkEvent, cookieValue, sourceContext) => {
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
if (
|
|
31
|
+
!sourceContext.policy?.enabledRules?.has(ruleId) ||
|
|
32
|
+
cookieValue.includes(ruleId === HTTPONLY ? 'httponly' : 'secure')
|
|
33
|
+
) return;
|
|
33
34
|
|
|
34
35
|
sessionConfiguration.reportFindings(sourceContext, {
|
|
35
36
|
ruleId,
|
|
@@ -18,9 +18,16 @@ const util = require('util');
|
|
|
18
18
|
const { toLowerCase } = require('@contrast/common');
|
|
19
19
|
const { patchType } = require('../common');
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @param {{
|
|
23
|
+
* assess: import('@contrast/assess').Assess,
|
|
24
|
+
* scopes: import('@contrast/scopes').Scopes,
|
|
25
|
+
* }} core
|
|
26
|
+
*/
|
|
21
27
|
module.exports = function (core) {
|
|
22
28
|
const {
|
|
23
29
|
assess: {
|
|
30
|
+
getSourceContext,
|
|
24
31
|
eventFactory: { createSessionEvent },
|
|
25
32
|
sessionConfiguration: {
|
|
26
33
|
handleHttpOnly,
|
|
@@ -29,7 +36,6 @@ module.exports = function (core) {
|
|
|
29
36
|
},
|
|
30
37
|
depHooks,
|
|
31
38
|
patcher,
|
|
32
|
-
scopes: { sources },
|
|
33
39
|
} = core;
|
|
34
40
|
|
|
35
41
|
const expressSession = core.assess.sessionConfiguration.expressSession = {};
|
|
@@ -100,7 +106,7 @@ module.exports = function (core) {
|
|
|
100
106
|
pre(data) {
|
|
101
107
|
const [, res] = data.args;
|
|
102
108
|
|
|
103
|
-
const sourceContext =
|
|
109
|
+
const sourceContext = getSourceContext();
|
|
104
110
|
if (!sourceContext) return;
|
|
105
111
|
|
|
106
112
|
patcher.patch(res, 'setHeader', {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.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)",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test": "../scripts/test.sh"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@contrast/common": "1.
|
|
20
|
+
"@contrast/common": "1.16.0",
|
|
21
21
|
"@contrast/distringuish": "^4.4.0",
|
|
22
22
|
"@contrast/scopes": "1.4.0"
|
|
23
23
|
}
|