@contrast/assess 1.33.1 → 1.35.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 +5 -1
- package/lib/crypto-analysis/install/crypto.test.js +5 -5
- package/lib/dataflow/propagation/install/buffer.js +4 -2
- package/lib/dataflow/propagation/install/buffer.test.js +3 -0
- package/lib/dataflow/propagation/install/string/match-all.js +1 -1
- package/lib/dataflow/sources/handler.js +1 -1
- package/lib/dataflow/sources/handler.test.js +38 -0
- package/lib/event-factory.test.js +19 -14
- package/lib/index.d.ts +11 -6
- package/lib/response-scanning/handlers/index.js +0 -7
- package/lib/response-scanning/handlers/index.test.js +2 -8
- package/lib/response-scanning/handlers/utils.js +1 -25
- package/lib/response-scanning/handlers/utils.test.js +0 -11
- package/package.json +12 -11
|
@@ -20,6 +20,7 @@ const {
|
|
|
20
20
|
isString,
|
|
21
21
|
StringPrototypeToLowerCase,
|
|
22
22
|
} = require('@contrast/common');
|
|
23
|
+
const semver = require('semver');
|
|
23
24
|
const { InstrumentationType: { RULE } } = require('../../constants');
|
|
24
25
|
const { PATCH_TYPE: patchType } = require('../common');
|
|
25
26
|
|
|
@@ -124,7 +125,10 @@ module.exports = function (core) {
|
|
|
124
125
|
},
|
|
125
126
|
});
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
// See NODE-3533 'createCipher' not included in Node 22+
|
|
129
|
+
const methods = ['createCipheriv'];
|
|
130
|
+
if (semver.lt(process.version, '22.0.0')) methods.push('createCipher');
|
|
131
|
+
for (const method of methods) {
|
|
128
132
|
patcher.patch(_export, method, {
|
|
129
133
|
name: `crypto.${method}`,
|
|
130
134
|
patchType,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { expect } = require('chai');
|
|
4
3
|
const sinon = require('sinon');
|
|
4
|
+
const semver = require('semver');
|
|
5
|
+
const { expect } = require('chai');
|
|
5
6
|
const { Rule } = require('@contrast/common');
|
|
6
7
|
const { ConfigSource } = require('@contrast/config');
|
|
7
8
|
const { initAssessFixture } = require('@contrast/test/fixtures');
|
|
@@ -93,10 +94,9 @@ describe('assess crypto-analysis node:crypto', function () {
|
|
|
93
94
|
});
|
|
94
95
|
});
|
|
95
96
|
|
|
96
|
-
[
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
].forEach((method) => {
|
|
97
|
+
const methods = ['createCipheriv'];
|
|
98
|
+
if (semver.lt(process.version, '22.0.0')) methods.push('createCipher');
|
|
99
|
+
methods.forEach((method) => {
|
|
100
100
|
describe(`crypto.${method}()`, function() {
|
|
101
101
|
[
|
|
102
102
|
'des-ede',
|
|
@@ -78,9 +78,11 @@ module.exports = function(core) {
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
const bufferFrom = 'global.Buffer.from';
|
|
82
|
+
|
|
81
83
|
patcher.patch(global.Buffer, 'from', {
|
|
82
84
|
patchType,
|
|
83
|
-
name,
|
|
85
|
+
name: bufferFrom,
|
|
84
86
|
post(data) {
|
|
85
87
|
const firstArg = data.args[0];
|
|
86
88
|
const argType = isString(firstArg) ? 'string' : Buffer.isBuffer(firstArg) ? 'buffer' : null;
|
|
@@ -112,7 +114,7 @@ module.exports = function(core) {
|
|
|
112
114
|
context: `Buffer.from(${ArrayPrototypeJoin.call(args.map((a) => a.value))})`,
|
|
113
115
|
object: { tracked: true, value: 'Buffer' },
|
|
114
116
|
history: [trkInfo],
|
|
115
|
-
name,
|
|
117
|
+
name: bufferFrom,
|
|
116
118
|
result: {
|
|
117
119
|
tracked: true,
|
|
118
120
|
value: args[0].value,
|
|
@@ -49,7 +49,7 @@ module.exports = function(core) {
|
|
|
49
49
|
name,
|
|
50
50
|
moduleName: 'String',
|
|
51
51
|
methodName: 'prototype.matchAll',
|
|
52
|
-
context: `'${objInfo.value}'.
|
|
52
|
+
context: `'${objInfo.value}'.matchAll(${arg})`,
|
|
53
53
|
history: [objInfo],
|
|
54
54
|
object: {
|
|
55
55
|
value: objInfo.value,
|
|
@@ -63,7 +63,7 @@ module.exports = function (core) {
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
sources.createStacktrace = function (stacktraceOpts) {
|
|
66
|
-
return config.assess.stacktraces === 'NONE'
|
|
66
|
+
return config.assess.stacktraces === 'NONE' || config.assess.stacktraces === 'SINK'
|
|
67
67
|
? emptyStack
|
|
68
68
|
: createSnapshot(stacktraceOpts)();
|
|
69
69
|
};
|
|
@@ -139,6 +139,44 @@ describe('assess dataflow sources handler', function () {
|
|
|
139
139
|
});
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
['SINK', 'NONE'].forEach((option) => {
|
|
143
|
+
it(`does not capture stacktrace for assess.stacktraces=${option}`, function() {
|
|
144
|
+
simulateRequestScope(() => {
|
|
145
|
+
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
146
|
+
core.config.assess.stacktraces = option;
|
|
147
|
+
const trackedObj = sources.handle({
|
|
148
|
+
name: 'test-source-name',
|
|
149
|
+
inputType: InputType.QUERYSTRING,
|
|
150
|
+
data: { prop1: 'foo' },
|
|
151
|
+
sourceContext
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const trackData = tracker.getData(trackedObj.prop1);
|
|
155
|
+
expect(trackData.stack).to.deep.equal([]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
['SOME', 'ALL'].forEach((option) => {
|
|
161
|
+
it(`captures stacktrace for assess.stacktraces=${option}`, function() {
|
|
162
|
+
simulateRequestScope(() => {
|
|
163
|
+
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
164
|
+
core.config.assess.stacktraces = option;
|
|
165
|
+
const trackedObj = sources.handle({
|
|
166
|
+
name: 'test-source-name',
|
|
167
|
+
inputType: InputType.QUERYSTRING,
|
|
168
|
+
data: { prop1: 'foo' },
|
|
169
|
+
sourceContext
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const trackData = tracker.getData(trackedObj.prop1);
|
|
173
|
+
expect(trackData.stack).to.be.instanceOf(Array);
|
|
174
|
+
expect(trackData.stack.length).to.be.greaterThan(0);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
|
|
142
180
|
it('traverses objects and tracks string values', function () {
|
|
143
181
|
simulateRequestScope(() => {
|
|
144
182
|
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
@@ -199,14 +199,16 @@ testMethod('assess event-factory', function () {
|
|
|
199
199
|
});
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
['SOME', 'SINK', 'NONE'].forEach((option) => {
|
|
203
|
+
it(`returns an event without stacktrace generator function when stacktraces option is not set to "ALL" and set to ${option}`, function () {
|
|
204
|
+
core.config.assess.stacktraces = option;
|
|
205
|
+
core.scopes.sources.run(validStore, function () {
|
|
206
|
+
const result = createPropagationEvent(validData);
|
|
206
207
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
expect(result).to.be.like(validResult);
|
|
209
|
+
expect(result.time).not.to.be.undefined;
|
|
210
|
+
expect(result.stack).to.deep.equal([]);
|
|
211
|
+
});
|
|
210
212
|
});
|
|
211
213
|
});
|
|
212
214
|
});
|
|
@@ -296,14 +298,17 @@ testMethod('assess event-factory', function () {
|
|
|
296
298
|
});
|
|
297
299
|
});
|
|
298
300
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
['ALL', 'SOME', 'SINK'].forEach((option) => {
|
|
302
|
+
it(`returns an event with stacktrace generator function when stacktraces option is not set to "NONE" and set to ${option}`, function () {
|
|
303
|
+
core.config.assess.stacktraces = option;
|
|
304
|
+
core.scopes.sources.run(validStore, function () {
|
|
305
|
+
const result = createSinkEvent(validData);
|
|
303
306
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
+
expect(result).to.be.like(validResult);
|
|
308
|
+
expect(result.time).not.to.be.undefined;
|
|
309
|
+
expect(result.stack).to.be.instanceOf(Array);
|
|
310
|
+
expect(result.stack.length).to.be.greaterThan(0);
|
|
311
|
+
});
|
|
307
312
|
});
|
|
308
313
|
});
|
|
309
314
|
|
package/lib/index.d.ts
CHANGED
|
@@ -12,17 +12,22 @@
|
|
|
12
12
|
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
Rule,
|
|
18
|
-
SessionConfigurationRule,
|
|
19
|
-
} from '@contrast/common';
|
|
15
|
+
import { Rule, SessionConfigurationRule } from '@contrast/common';
|
|
16
|
+
import { Config } from '@contrast/config';
|
|
20
17
|
import { Core as _Core } from '@contrast/core';
|
|
18
|
+
import { Deadzones } from '@contrast/deadzones';
|
|
19
|
+
import { DepHooks } from '@contrast/dep-hooks';
|
|
20
|
+
import { Logger } from '@contrast/logger';
|
|
21
|
+
import { Patcher } from '@contrast/patcher';
|
|
22
|
+
import { ReporterBus } from '@contrast/reporter';
|
|
23
|
+
import { Rewriter } from '@contrast/rewriter';
|
|
24
|
+
import { Scopes } from '@contrast/scopes';
|
|
25
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
21
26
|
|
|
22
27
|
export interface Core extends _Core {
|
|
23
28
|
config: Config;
|
|
24
29
|
logger: Logger;
|
|
25
|
-
depHooks:
|
|
30
|
+
depHooks: DepHooks;
|
|
26
31
|
patcher: Patcher
|
|
27
32
|
rewriter: Rewriter;
|
|
28
33
|
scopes: Scopes;
|
|
@@ -197,14 +197,7 @@ module.exports = function(core) {
|
|
|
197
197
|
const vulnerabilityMetadata = checkCspSources(cspHeaders);
|
|
198
198
|
|
|
199
199
|
if (vulnerabilityMetadata.insecure) {
|
|
200
|
-
// We do this because TS API will expect these keys (although it is a typo)
|
|
201
|
-
// When they fix the API we can remove this
|
|
202
|
-
vulnerabilityMetadata.refererSecure = vulnerabilityMetadata.referrerSecure;
|
|
203
|
-
vulnerabilityMetadata.refererValue = vulnerabilityMetadata.referrerValue;
|
|
204
|
-
|
|
205
200
|
delete vulnerabilityMetadata.insecure;
|
|
206
|
-
delete vulnerabilityMetadata.referrerSecure;
|
|
207
|
-
delete vulnerabilityMetadata.referrerValue;
|
|
208
201
|
|
|
209
202
|
reportFindings(sourceContext, {
|
|
210
203
|
ruleId: ResponseScanningRule.CSP_HEADER_INSECURE,
|
|
@@ -251,8 +251,6 @@ describe('assess response scanning handlers', function () {
|
|
|
251
251
|
data: JSON.stringify({
|
|
252
252
|
defaultSrcSecure: true,
|
|
253
253
|
defaultSrcValue: '"self"',
|
|
254
|
-
baseUriSecure: false,
|
|
255
|
-
baseUriValue: '',
|
|
256
254
|
childSrcSecure: true,
|
|
257
255
|
childSrcValue: '',
|
|
258
256
|
connectSrcSecure: true,
|
|
@@ -267,16 +265,12 @@ describe('assess response scanning handlers', function () {
|
|
|
267
265
|
scriptSrcValue: '',
|
|
268
266
|
styleSrcSecure: true,
|
|
269
267
|
styleSrcValue: '',
|
|
268
|
+
baseUriSecure: false,
|
|
269
|
+
baseUriValue: '',
|
|
270
270
|
formActionSecure: true,
|
|
271
271
|
formActionValue: '"foobar"',
|
|
272
272
|
frameAncestorsSecure: true,
|
|
273
273
|
frameAncestorsValue: '"foobar"',
|
|
274
|
-
pluginTypesSecure: true,
|
|
275
|
-
pluginTypesValue: '"foobar"',
|
|
276
|
-
reflectedXssSecure: true,
|
|
277
|
-
reflectedXssValue: '',
|
|
278
|
-
refererSecure: true,
|
|
279
|
-
refererValue: ''
|
|
280
274
|
})
|
|
281
275
|
}
|
|
282
276
|
});
|
|
@@ -261,30 +261,8 @@ function isSourceSecure(sources) {
|
|
|
261
261
|
);
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
/**
|
|
265
|
-
* Evaluator for reflected-xss directive. Checks if the value is not
|
|
266
|
-
* equal to 1
|
|
267
|
-
* Note: If empty it is secure
|
|
268
|
-
* @param {Array} sources sources for a given csp directive
|
|
269
|
-
* @return {Boolean} whether a directive is secure
|
|
270
|
-
*/
|
|
271
|
-
function xssCheck(sources) {
|
|
272
|
-
return sources.every((source) => parseInt(source) === 1);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Evaluator for referrer directive. Checks if value is not *
|
|
277
|
-
* or contains unsafe-url
|
|
278
|
-
* @param {Array} sources sources for a given csp directive
|
|
279
|
-
* @return {Boolean} whether a directive is secure
|
|
280
|
-
*/
|
|
281
|
-
function referrerCheck(sources) {
|
|
282
|
-
return sources.every((source) => !/unsafe-url|\*/.test(source));
|
|
283
|
-
}
|
|
284
|
-
|
|
285
264
|
const KNOWN_DIRECTIVES = [
|
|
286
265
|
{ name: 'default-src', camelCasedName: 'defaultSrc', evaluator: isSourceSecure },
|
|
287
|
-
{ name: 'base-uri', camelCasedName: 'baseUri', evaluator: isSourceSecure },
|
|
288
266
|
{ name: 'child-src', camelCasedName: 'childSrc' },
|
|
289
267
|
{ name: 'connect-src', camelCasedName: 'connectSrc' },
|
|
290
268
|
{ name: 'frame-src', camelCasedName: 'frameSrc' },
|
|
@@ -292,11 +270,9 @@ const KNOWN_DIRECTIVES = [
|
|
|
292
270
|
{ name: 'object-src', camelCasedName: 'objectSrc' },
|
|
293
271
|
{ name: 'script-src', camelCasedName: 'scriptSrc' },
|
|
294
272
|
{ name: 'style-src', camelCasedName: 'styleSrc' },
|
|
273
|
+
{ name: 'base-uri', camelCasedName: 'baseUri', evaluator: isSourceSecure },
|
|
295
274
|
{ name: 'form-action', camelCasedName: 'formAction', evaluator: isSourceSecure },
|
|
296
275
|
{ name: 'frame-ancestors', camelCasedName: 'frameAncestors', evaluator: isSourceSecure },
|
|
297
|
-
{ name: 'plugin-types', camelCasedName: 'pluginTypes', evaluator: isSourceSecure },
|
|
298
|
-
{ name: 'reflected-xss', camelCasedName: 'reflectedXss', evaluator: xssCheck },
|
|
299
|
-
{ name: 'referrer', evaluator: referrerCheck }
|
|
300
276
|
];
|
|
301
277
|
|
|
302
278
|
/**
|
|
@@ -298,12 +298,6 @@ describe('assess response scanning handlers utils', function() {
|
|
|
298
298
|
mediaSrcValue: '',
|
|
299
299
|
objectSrcSecure: false,
|
|
300
300
|
objectSrcValue: '',
|
|
301
|
-
pluginTypesSecure: false,
|
|
302
|
-
pluginTypesValue: '',
|
|
303
|
-
referrerSecure: true,
|
|
304
|
-
referrerValue: '',
|
|
305
|
-
reflectedXssSecure: true,
|
|
306
|
-
reflectedXssValue: '',
|
|
307
301
|
scriptSrcSecure: false,
|
|
308
302
|
scriptSrcValue: '',
|
|
309
303
|
styleSrcSecure: false,
|
|
@@ -366,9 +360,6 @@ describe('assess response scanning handlers utils', function() {
|
|
|
366
360
|
policy: 'referrer *; reflected-xss 1;',
|
|
367
361
|
expectedResult: {
|
|
368
362
|
...defaultValues,
|
|
369
|
-
reflectedXssValue: '1',
|
|
370
|
-
referrerSecure: false,
|
|
371
|
-
referrerValue: '*',
|
|
372
363
|
insecure: true
|
|
373
364
|
}
|
|
374
365
|
},
|
|
@@ -376,8 +367,6 @@ describe('assess response scanning handlers utils', function() {
|
|
|
376
367
|
policy: 'referrer unsafe-url',
|
|
377
368
|
expectedResult: {
|
|
378
369
|
...defaultValues,
|
|
379
|
-
referrerSecure: false,
|
|
380
|
-
referrerValue: 'unsafe-url',
|
|
381
370
|
insecure: true
|
|
382
371
|
}
|
|
383
372
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.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,15 +17,16 @@
|
|
|
17
17
|
"test": "../scripts/test.sh"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@contrast/common": "1.
|
|
21
|
-
"@contrast/config": "1.
|
|
22
|
-
"@contrast/core": "1.
|
|
23
|
-
"@contrast/dep-hooks": "1.
|
|
24
|
-
"@contrast/distringuish": "^5.
|
|
25
|
-
"@contrast/instrumentation": "1.
|
|
26
|
-
"@contrast/logger": "1.
|
|
27
|
-
"@contrast/patcher": "1.
|
|
28
|
-
"@contrast/rewriter": "1.
|
|
29
|
-
"@contrast/scopes": "1.
|
|
20
|
+
"@contrast/common": "1.25.0",
|
|
21
|
+
"@contrast/config": "1.33.0",
|
|
22
|
+
"@contrast/core": "1.37.0",
|
|
23
|
+
"@contrast/dep-hooks": "1.5.0",
|
|
24
|
+
"@contrast/distringuish": "^5.1.0",
|
|
25
|
+
"@contrast/instrumentation": "1.15.0",
|
|
26
|
+
"@contrast/logger": "1.10.0",
|
|
27
|
+
"@contrast/patcher": "1.9.0",
|
|
28
|
+
"@contrast/rewriter": "1.13.0",
|
|
29
|
+
"@contrast/scopes": "1.6.0",
|
|
30
|
+
"semver": "^7.6.0"
|
|
30
31
|
}
|
|
31
32
|
}
|