@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.
@@ -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
- for (const method of ['createCipher', 'createCipheriv']) {
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
- 'createCipher',
98
- 'createCipheriv'
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,
@@ -74,6 +74,9 @@ describe('assess dataflow propagation Buffer', function () {
74
74
  source: 'P0',
75
75
  target: 'R',
76
76
  tags: { UNTRUSTED: [0, 7] },
77
+ name: 'global.Buffer.from',
78
+ moduleName: 'Buffer',
79
+ methodName: 'from',
77
80
  };
78
81
 
79
82
  [
@@ -49,7 +49,7 @@ module.exports = function(core) {
49
49
  name,
50
50
  moduleName: 'String',
51
51
  methodName: 'prototype.matchAll',
52
- context: `'${objInfo.value}'.matcAll(${arg})`,
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
- it('returns an event without stacktrace generator function when stacktraces option is not set to "ALL"', function () {
203
- core.config.assess.stacktraces = 'SOME';
204
- core.scopes.sources.run(validStore, function () {
205
- const result = createPropagationEvent(validData);
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
- expect(result).to.be.like(validResult);
208
- expect(result.time).not.to.be.undefined;
209
- expect(result.stack).to.deep.equal([]);
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
- it('returns an event with stacktrace generator function when stacktraces option is not set to "NONE"', function () {
300
- core.config.assess.stacktraces = 'ALL';
301
- core.scopes.sources.run(validStore, function () {
302
- const result = createSinkEvent(validData);
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
- expect(result).to.be.like(validResult);
305
- expect(result.time).not.to.be.undefined;
306
- expect(result.stack).to.be.instanceOf(Array);
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 { IncomingMessage, ServerResponse } from 'node:http';
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: RequireHook;
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.33.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.24.0",
21
- "@contrast/config": "1.31.0",
22
- "@contrast/core": "1.35.1",
23
- "@contrast/dep-hooks": "1.3.4",
24
- "@contrast/distringuish": "^5.0.0",
25
- "@contrast/instrumentation": "1.13.1",
26
- "@contrast/logger": "1.8.5",
27
- "@contrast/patcher": "1.7.5",
28
- "@contrast/rewriter": "1.11.1",
29
- "@contrast/scopes": "1.4.2"
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
  }