@contrast/assess 1.39.0 → 1.40.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.
@@ -50,11 +50,15 @@ module.exports = function(core) {
50
50
  // default policy to `null` until it is set later below. this will cause
51
51
  // all instrumentation to short-circuit, see `./get-source-context.js`.
52
52
  policy: null,
53
- reqData: { uriPath, queries },
53
+ reqData: {
54
+ method: req.method,
55
+ uriPath,
56
+ queries,
57
+ },
54
58
  };
55
59
 
56
60
  // check whether sampling allows processing
57
- ctx.sampleInfo = assess.sampler?.getSampleInfo?.(sourceData) ?? null;
61
+ ctx.sampleInfo = assess.sampler?.getSampleInfo(sourceData) ?? null;
58
62
  if (ctx.sampleInfo?.canSample === false) return ctx;
59
63
 
60
64
  // set policy - can be returned as `null` if request is url-excluded.
@@ -65,7 +69,6 @@ module.exports = function(core) {
65
69
  ctx.reqData.headers = { ...req.headers }; // copy to avoid storing tracked values
66
70
  ctx.reqData.ip = req.socket.remoteAddress;
67
71
  ctx.reqData.httpVersion = req.httpVersion;
68
- ctx.reqData.method = req.method;
69
72
  if (ctx.reqData.headers['content-type'])
70
73
  ctx.reqData.contentType = StringPrototypeToLowerCase.call(ctx.reqData.headers['content-type']);
71
74
 
@@ -0,0 +1,156 @@
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 SamplingStrategies = {
19
+ AssessTurnedOff: 1,
20
+ Probabilistic: 2,
21
+ };
22
+
23
+ class RouteAnalysisMonitor {
24
+ constructor(core) {
25
+ this._core = core;
26
+ // default behavior is to sample
27
+ this._defaultAnalysisInfo = { paused: false };
28
+ // cache keys come from discovered route `normalizedUrl`s, so size is controlled
29
+ this._normalCache = new Map();
30
+ this._ttl = core.config.assess.probabilistic_sampling.route_monitor.ttl_ms;
31
+ }
32
+
33
+ /**
34
+ * @param {object} reqData
35
+ * @param {string} reqData.uriPath
36
+ * @returns {AnalysisInfo}
37
+ */
38
+ getAnalysisInfo({ method, uriPath }) {
39
+ const normalizedUrl = this._core.routeCoverage.uriPathToNormalizedUrl(uriPath);
40
+ const now = Date.now();
41
+
42
+ if (normalizedUrl) {
43
+ const key = `${method}:${normalizedUrl}`;
44
+ let routeMeta = this._normalCache.get(key);
45
+
46
+ // not in cache, not paused
47
+ if (!routeMeta) {
48
+ routeMeta = {
49
+ pauseEnd: now + this._ttl,
50
+ normalizedUrl,
51
+ };
52
+ this._normalCache.set(key, routeMeta);
53
+
54
+ return { paused: false, ...routeMeta };
55
+ }
56
+
57
+ // unpause if pauseEnd expired
58
+ if (routeMeta.pauseEnd < now) {
59
+ // update so route lookup will be paused next time
60
+ routeMeta.pauseEnd = now + this._ttl;
61
+
62
+ return { paused: false, ...routeMeta };
63
+ }
64
+
65
+ // was in cache and still paused
66
+ return { paused: true, ...routeMeta };
67
+ } else {
68
+ // todo - handle "dynamic" routes
69
+ }
70
+
71
+ return this._defaultAnalysisInfo;
72
+ }
73
+ }
74
+
75
+ class BaseSampler {
76
+ constructor(strategy, opts) {
77
+ // save strategy and opts on instance so they can be checked before re-initializing
78
+ this.strategy = strategy;
79
+ this.opts = opts;
80
+ }
81
+ }
82
+
83
+ // Allows Assess to turn off at runtime e.g. disabled via TeamServer DTM
84
+ class AssessTurnedOffSampler extends BaseSampler {
85
+ constructor() {
86
+ super(SamplingStrategies.AssessTurnedOff);
87
+ this._sampleInfo = Object.seal({ canSample: false });
88
+ }
89
+
90
+ getSampleInfo() {
91
+ return this._sampleInfo;
92
+ }
93
+ }
94
+
95
+ class ProbabilisticSampler extends BaseSampler {
96
+ constructor(opts) {
97
+ super(SamplingStrategies.Probabilistic, opts);
98
+ }
99
+
100
+ getSampleInfo(sourceInfo) {
101
+ const { base_probability } = this.opts;
102
+ const { reqData } = sourceInfo.store.assess;
103
+
104
+ // base caclulation
105
+ const rand = Math.random();
106
+ const canSample = rand < base_probability;
107
+ const sampleInfo = { canSample, base_probability, rand };
108
+
109
+ // check route monitoring before sampling
110
+ if (canSample) {
111
+ const routeInfo = this.routeMonitor?.getAnalysisInfo(reqData);
112
+
113
+ if (routeInfo) {
114
+ // don't sample if analysis is paused
115
+ routeInfo.paused && (sampleInfo.canSample = false);
116
+
117
+ // append any additional metadata to sample info
118
+ routeInfo.pauseEnd && (sampleInfo.pauseEnd = routeInfo.pauseEnd);
119
+ routeInfo.normalizedUrl && (sampleInfo.normalizedUrl = routeInfo.normalizedUrl);
120
+ }
121
+ }
122
+
123
+ return sampleInfo;
124
+ }
125
+ }
126
+
127
+ class SamplerBuilder {
128
+ constructor(core) {
129
+ this.builders = new Map([
130
+ //
131
+ [SamplingStrategies.AssessTurnedOff, () => new AssessTurnedOffSampler()],
132
+ //
133
+ [SamplingStrategies.Probabilistic, (opts) => {
134
+ const sampler = new ProbabilisticSampler(opts);
135
+
136
+ if (opts?.route_monitor?.enable)
137
+ sampler.routeMonitor = new RouteAnalysisMonitor(core);
138
+
139
+ return sampler;
140
+ }],
141
+ ]);
142
+ }
143
+
144
+ build(strategy, opts) {
145
+ return this.builders.get(strategy)(opts);
146
+ }
147
+ }
148
+
149
+ module.exports = {
150
+ RouteAnalysisMonitor,
151
+ BaseSampler,
152
+ AssessTurnedOffSampler,
153
+ ProbabilisticSampler,
154
+ SamplerBuilder,
155
+ SamplingStrategies,
156
+ };
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const { devNull } = require('node:os');
4
+ const { expect } = require('chai');
5
+ const { EventEmitter } = require('events');
6
+ const mocks = require('@contrast/test/mocks');
7
+ const { RouteAnalysisMonitor } = require('./common');
8
+ const frameworkData = require('@contrast/test/data/framework-routing-data')();
9
+
10
+ describe('assess sampler classes', function() {
11
+ describe('RouteAnalysisMonitor', function() {
12
+ it('getAnalysisInfo returns null when no discovery data was registered', function() {
13
+ const monitor = new RouteAnalysisMonitor(initMockCore());
14
+ [
15
+ ['/user/1', '/user/2', '/user/3', '/user/4'],
16
+ ['/user/1/cart', '/user/2/cart', '/user/3/cart', '/user/4/cart'],
17
+ ['/products/all', '/products/all'],
18
+ ['/products/1', '/products/2', '/products/3', '/products/4'],
19
+ ].forEach((uriPaths) => {
20
+ for (const uriPath of uriPaths) {
21
+ expect(monitor.getAnalysisInfo({ uriPath })).to.deep.equal({ paused: false });
22
+ }
23
+ });
24
+ });
25
+
26
+ for (const [framework, testData] of Object.entries(frameworkData)) {
27
+ describe(`${framework} framework route monitoring`, function() {
28
+ let core, monitor;
29
+
30
+ before(function() {
31
+ core = initMockCore();
32
+ core.config.setValue('assess.probabilistic_sampling.route_monitor.ttl_ms', 500, 'CONFIGURATION_FILE');
33
+ monitor = new RouteAnalysisMonitor(core);
34
+ testData.forEach((d) => {
35
+ core.routeCoverage._normalizedUrlMapper.handleDiscover(d.routeInfo);
36
+ });
37
+ });
38
+
39
+ testData.forEach(({ paths, routeInfo, skip, hasMapping }) => {
40
+ it(`${skip ? 'does not monitor' : 'monitors'} for normalizedUrl '${routeInfo.normalizedUrl}'`, async function() {
41
+ const groups = { paused: [], notPaused: [] };
42
+ let calls = 0;
43
+
44
+ // iterate at least twice if paths array has 1 element
45
+ [...paths, ...paths].forEach((uriPath, i) => {
46
+ const result = monitor.getAnalysisInfo({ uriPath, method: routeInfo.method });
47
+ calls++;
48
+ // update bucket
49
+ groups[result.paused ? 'paused' : 'notPaused'].push({ index: i, uriPath, ...result });
50
+ });
51
+
52
+ if (hasMapping !== false) {
53
+ // first call was unpaused
54
+ expect(groups.notPaused).to.have.lengthOf(1);
55
+ expect(groups.notPaused[0]).to.have.property('index', 0);
56
+ // all other calls were paused
57
+ expect(groups.paused).to.have.lengthOf(calls - 1);
58
+ for (const result of groups.paused) {
59
+ expect(result.index).to.be.greaterThan(0);
60
+ }
61
+ } else {
62
+ expect(groups.paused).to.have.lengthOf(0);
63
+ }
64
+ });
65
+ });
66
+ });
67
+ }
68
+ });
69
+ });
70
+
71
+ function initMockCore() {
72
+ const { CONTRAST_CONFIG_PATH } = process.env;
73
+ process.env.CONTRAST_CONFIG_PATH = devNull;
74
+
75
+ let core;
76
+
77
+ try {
78
+ core = {
79
+ // sampler needs this namespace to exist
80
+ assess: {},
81
+ // mocks
82
+ messages: new EventEmitter(),
83
+ logger: mocks.logger(),
84
+ };
85
+ // use actual config so we can get dynamic effective values with TS message
86
+ // updates (mock doesn't). we can also test new effective config mappings.
87
+ require('@contrast/config')(core);
88
+ require('@contrast/route-coverage')(core);
89
+ core.config.setValue('assess.enable', true, 'CONTRAST_UI');
90
+ } catch (err) {
91
+ process.env.CONTRAST_CONFIG_PATH = CONTRAST_CONFIG_PATH;
92
+
93
+ console.dir(err);
94
+ throw err;
95
+ }
96
+
97
+ // reset to orig value
98
+ process.env.CONTRAST_CONFIG_PATH = CONTRAST_CONFIG_PATH;
99
+
100
+ return core;
101
+ }
@@ -16,6 +16,8 @@
16
16
  'use strict';
17
17
 
18
18
  const { Event } = require('@contrast/common');
19
+ const { ConfigSource } = require('@contrast/config');
20
+ const { SamplerBuilder, SamplingStrategies } = require('./common');
19
21
 
20
22
  /**
21
23
  * @param {{
@@ -34,29 +36,40 @@ module.exports = function assess(core) {
34
36
  messages,
35
37
  } = core;
36
38
 
39
+ const samplerBuilder = new SamplerBuilder(core);
40
+
37
41
  /**
38
42
  * Initializes the Assess sampler only if there's a need, otherwise sets it to null.
39
43
  * Clients will call the instance methods optionally: assess.sampler?.getSampleInfo().
40
44
  * Determines sampler strategy and options based on effective config values.
41
45
  */
42
46
  function initSampler() {
47
+ const isProd = config.getEffectiveValue('server.environment') === 'PRODUCTION';
48
+ const enableAssess = config.getEffectiveValue('assess.enable');
43
49
  const baseProbability = config.getEffectiveValue('assess.probabilistic_sampling.base_probability');
50
+ const {
51
+ value: enableSampling,
52
+ source: samplingConfigSource
53
+ } = config._effectiveMap.get('assess.probabilistic_sampling.enable');
44
54
 
45
55
  let strategy;
46
56
  let opts;
47
57
 
48
- if (!config.getEffectiveValue('assess.enable') || baseProbability === 0) {
49
- // if Assess was disabled by TS use disabled sampler
50
- strategy = 'disabled';
58
+ // determine strategy and options
59
+ if (!enableAssess || baseProbability === 0) {
60
+ // if Assess was disabled by TS turn assess off i.e. sample 0% of requests
61
+ strategy = SamplingStrategies.AssessTurnedOff;
51
62
  } else if (baseProbability < 1) {
63
+ // in PRODUCTION environments turn on sampling unless explicitly disabled
52
64
  if (
53
- config.getEffectiveValue('assess.probabilistic_sampling.enable') ||
54
- config.getEffectiveValue('server.environment') === 'PRODUCTION'
65
+ enableSampling ||
66
+ (isProd && samplingConfigSource === ConfigSource.DEFAULT_VALUE)
55
67
  ) {
56
68
  // strategy and opts can be more dynamic in the future
57
- strategy = 'probabilistic';
69
+ strategy = SamplingStrategies.Probabilistic;
58
70
  opts = {
59
- base_probability: baseProbability
71
+ base_probability: baseProbability,
72
+ route_monitor: config.assess.probabilistic_sampling.route_monitor,
60
73
  };
61
74
  }
62
75
  }
@@ -68,7 +81,7 @@ module.exports = function assess(core) {
68
81
  assess.sampler?.opts?.base_probability !== opts?.base_probability
69
82
  ) {
70
83
  logger.info({ strategy, opts }, 'updating assess sampler');
71
- assess.sampler = SamplerFactory[strategy](opts);
84
+ assess.sampler = samplerBuilder.build(strategy, opts);
72
85
  }
73
86
  } else {
74
87
  if (assess.sampler) logger.info('assess sampling disabled');
@@ -79,58 +92,11 @@ module.exports = function assess(core) {
79
92
 
80
93
  // initialize a first time
81
94
  initSampler();
82
- // and re-init again upon settings updates. we don't use the settings message argument, since all
83
- // effective sampling configs will have have been updated by @contrast/config (it registers
84
- // listeners first).
95
+
96
+ // and re-init again upon settings updates. we don't use the settings
97
+ // message argument, since all effective sampling configs will have
98
+ // been updated by @contrast/config (it registers listeners first).
85
99
  messages.on(Event.SERVER_SETTINGS_UPDATE, initSampler);
86
100
 
87
101
  return core.assess.sampler;
88
102
  };
89
-
90
- class BaseSampler {
91
- constructor(strategy, opts) {
92
- // save strategy and opts on instance so they can be checked before re-initializing
93
- this.strategy = strategy;
94
- this.opts = opts;
95
- }
96
- }
97
-
98
- class DisabledSampler extends BaseSampler {
99
- constructor() {
100
- super('disabled');
101
- this._sampleInfo = Object.seal({ canSample: false });
102
- }
103
-
104
- getSampleInfo() {
105
- return this._sampleInfo;
106
- }
107
- }
108
-
109
- class ProbabilisticSampler extends BaseSampler {
110
- constructor(opts) {
111
- super('probabilistic', opts);
112
- }
113
-
114
- getSampleInfo() {
115
- const { base_probability } = this.opts;
116
- const rand = Math.random();
117
- const canSample = rand < base_probability;
118
- return { canSample, base_probability, rand };
119
- }
120
- }
121
-
122
- class SamplerFactory {
123
- /**
124
- * Used when Assess is disabled by TS; always returns value instructing not to sample
125
- */
126
- static disabled() {
127
- return new DisabledSampler();
128
- }
129
-
130
- /**
131
- * Each request has fixed chance e.g. 0.05.
132
- */
133
- static probabilistic(opts = {}) {
134
- return new ProbabilisticSampler(opts);
135
- }
136
- }
@@ -6,26 +6,29 @@ const { Event } = require('@contrast/common');
6
6
  const mocks = require('@contrast/test/mocks');
7
7
  const { devNull } = require('node:os');
8
8
 
9
+ const initSampler = require('.');
10
+ const { SamplingStrategies: { AssessTurnedOff, Probabilistic } } = require('./common');
11
+
9
12
  const TRIALS = 1000;
10
13
  const ZSCORE = 3.891; // 99.99% confidence
11
14
 
12
15
  describe('assess sampler', function() {
13
16
  it('sampling is disabled by default and assess.sampler is null', function() {
14
17
  const core = initMockCore();
15
- require('./sampler')(core);
18
+ initSampler(core);
16
19
  expect(core.assess.sampler).to.be.null;
17
20
  });
18
21
 
19
- it('samples at default rate of 0.01', function() {
22
+ it('samples at default rate of 0.10', function() {
20
23
  const core = initMockCore();
21
24
  core.config.setValue('assess.enable', true, 'CONTRAST_UI');
22
25
  core.config.setValue('assess.probabilistic_sampling.enable', true, 'CONTRAST_UI');
23
26
  // initialize after configs are set
24
- const sampler = require('./sampler')(core);
27
+ const sampler = initSampler(core);
25
28
 
26
29
  // verify configs set in before setup
27
- expect(sampler).to.have.property('strategy', 'probabilistic');
28
- expect(sampler.opts).to.have.property('base_probability', 0.01);
30
+ expect(sampler).to.have.property('strategy', Probabilistic);
31
+ expect(sampler.opts).to.have.property('base_probability', 0.10);
29
32
  expect(sampler).to.have.property('getSampleInfo').to.be.a('function');
30
33
  // test behavior
31
34
  testProbabilisticSampler(sampler);
@@ -36,10 +39,10 @@ describe('assess sampler', function() {
36
39
  core.config.setValue('assess.probabilistic_sampling.enable', true, 'CONTRAST_UI');
37
40
  core.config.setValue('assess.probabilistic_sampling.base_probability', 0.25, 'CONTRAST_UI');
38
41
  // initialize after configs are set
39
- const sampler = require('./sampler')(core);
42
+ const sampler = initSampler(core);
40
43
 
41
44
  // verify configs
42
- expect(sampler).to.have.property('strategy', 'probabilistic');
45
+ expect(sampler).to.have.property('strategy', Probabilistic);
43
46
  expect(sampler.opts).to.have.property('base_probability', 0.25);
44
47
  expect(sampler).to.have.property('getSampleInfo').to.be.a('function');
45
48
  // test behavior
@@ -51,12 +54,12 @@ describe('assess sampler', function() {
51
54
  core.config.setValue('assess.probabilistic_sampling.enable', true, 'CONTRAST_UI');
52
55
  core.config.setValue('assess.probabilistic_sampling.base_probability', 0.25, 'CONTRAST_UI');
53
56
  // initialize after configs are set
54
- require('./sampler')(core);
57
+ initSampler(core);
55
58
  // don't destructure to just { sampler } since the value of assess.sampler changes on update.
56
59
  const { assess } = core;
57
60
 
58
61
  // verify configs
59
- expect(assess.sampler).to.have.property('strategy', 'probabilistic');
62
+ expect(assess.sampler).to.have.property('strategy', Probabilistic);
60
63
  expect(assess.sampler.opts).to.have.property('base_probability', 0.25);
61
64
  expect(assess.sampler).to.have.property('getSampleInfo').to.be.a('function');
62
65
  // test behavior
@@ -81,12 +84,12 @@ describe('assess sampler', function() {
81
84
  expect(core.logger.info.getCalls().map(c => c.args)).to.deep.equal([
82
85
  // when initialized
83
86
  [
84
- { strategy: 'probabilistic', opts: { base_probability: 0.25 } },
87
+ { strategy: Probabilistic, opts: { base_probability: 0.25, route_monitor: { enable: true, ttl_ms: 1800000 } } },
85
88
  'updating assess sampler'
86
89
  ],
87
90
  // update 1
88
91
  [
89
- { strategy: 'probabilistic', opts: { base_probability: 0.5 } },
92
+ { strategy: Probabilistic, opts: { base_probability: 0.5, route_monitor: { enable: true, ttl_ms: 1800000 } } },
90
93
  'updating assess sampler'
91
94
  ]
92
95
  ]);
@@ -98,12 +101,12 @@ describe('assess sampler', function() {
98
101
  core.config.setValue('assess.probabilistic_sampling.enable', true, 'CONTRAST_UI');
99
102
  core.config.setValue('assess.probabilistic_sampling.base_probability', 0.50, 'CONTRAST_UI');
100
103
  // initializes after configs are set
101
- require('./sampler')(core);
104
+ initSampler(core);
102
105
  // don't destructure to just { sampler } since the value of assess.sampler changes on update.
103
106
  const { assess } = core;
104
107
 
105
108
  // verify configs set in before setup
106
- expect(assess.sampler).to.have.property('strategy', 'probabilistic');
109
+ expect(assess.sampler).to.have.property('strategy', Probabilistic);
107
110
  expect(assess.sampler.opts).to.have.property('base_probability', 0.50);
108
111
  expect(assess.sampler).to.have.property('getSampleInfo').to.be.a('function');
109
112
  // test behavior
@@ -115,7 +118,7 @@ describe('assess sampler', function() {
115
118
  });
116
119
 
117
120
  // verify updated sampling strategy
118
- expect(assess.sampler.strategy).to.equal('disabled');
121
+ expect(assess.sampler.strategy).to.equal(AssessTurnedOff);
119
122
  // test behavior
120
123
  testDisabledSampler(assess.sampler);
121
124
 
@@ -123,12 +126,12 @@ describe('assess sampler', function() {
123
126
  expect(core.logger.info.getCalls().map(c => c.args)).to.deep.equal([
124
127
  // when initialized
125
128
  [
126
- { strategy: 'probabilistic', opts: { base_probability: 0.5 } },
129
+ { strategy: Probabilistic, opts: { base_probability: 0.5, route_monitor: { enable: true, ttl_ms: 1800000 } } },
127
130
  'updating assess sampler'
128
131
  ],
129
132
  // update 1
130
133
  [
131
- { strategy: 'disabled', opts: undefined },
134
+ { strategy: AssessTurnedOff, opts: undefined },
132
135
  'updating assess sampler'
133
136
  ]
134
137
  ]);
@@ -142,11 +145,13 @@ describe('assess sampler', function() {
142
145
  process.env.CONTRAST_CONFIG_PATH = cfgPath;
143
146
 
144
147
  core.config.setValue('assess.enable', true, 'CONTRAST_UI');
145
- core.config.setValue('assess.probabilistic_sampling.enable', false, 'DEFAULT_VALUE');
148
+ // core.config.setValue('assess.probabilistic_sampling.enable', true, 'DEFAULT_VALUE');
146
149
  core.config.setValue('assess.probabilistic_sampling.base_probability', 0.50, 'CONTRAST_UI');
150
+
147
151
  // initializes after configs are set
148
- require('./sampler')(core);
149
- // don't destructure to just { sampler } since the value of assess.sampler changes on update.
152
+ initSampler(core);
153
+
154
+ // don't destructure to just { sampler } since the value of assess.sampler changes on update
150
155
  const { assess } = core;
151
156
 
152
157
  // disabled by config
@@ -176,7 +181,7 @@ describe('assess sampler', function() {
176
181
  });
177
182
 
178
183
  // verify updated sampling strategy
179
- expect(assess.sampler.strategy).to.equal('disabled');
184
+ expect(assess.sampler.strategy).to.equal(AssessTurnedOff);
180
185
  // test behavior
181
186
  testDisabledSampler(assess.sampler);
182
187
 
@@ -200,19 +205,19 @@ describe('assess sampler', function() {
200
205
  expect(core.logger.info.getCalls().map(c => c.args)).to.deep.equal([
201
206
  // update 1: env=PRODUCTION enables sampling
202
207
  [
203
- { strategy: 'probabilistic', opts: { base_probability: 0.5 } },
208
+ { strategy: Probabilistic, opts: { base_probability: 0.5, route_monitor: { enable: true, ttl_ms: 1800000 } } },
204
209
  'updating assess sampler'
205
210
  ],
206
211
  // update 2: env = DEVELOPMENT disables sampling
207
212
  ['assess sampling disabled'],
208
213
  // update 3: assess.enable=false sets disabled sampling strategy
209
214
  [
210
- { strategy: 'disabled', opts: undefined },
215
+ { strategy: AssessTurnedOff, opts: undefined },
211
216
  'updating assess sampler'
212
217
  ],
213
218
  // update 5: assess.enable=true and request_frequency=50 re-enables assess and new sampling probability
214
219
  [
215
- { strategy: 'probabilistic', opts: { base_probability: 0.02 } },
220
+ { strategy: Probabilistic, opts: { base_probability: 0.02, route_monitor: { enable: true, ttl_ms: 1800000 } } },
216
221
  'updating assess sampler'
217
222
  ]
218
223
  ]);
@@ -236,6 +241,8 @@ function initMockCore() {
236
241
  // use actual config so we can get dynamic effective values with TS message
237
242
  // updates (mock doesn't). we can also test new effective config mappings.
238
243
  require('@contrast/config')(core);
244
+ require('@contrast/route-coverage')(core);
245
+
239
246
  core.config.setValue('assess.enable', true, 'CONTRAST_UI');
240
247
  } catch (err) {
241
248
  process.env.CONTRAST_CONFIG_PATH = CONTRAST_CONFIG_PATH;
@@ -274,15 +281,20 @@ function getStats(p) {
274
281
 
275
282
  function testProbabilisticSampler(sampler) {
276
283
  const { opts } = sampler;
284
+ const store = {
285
+ assess: {
286
+ reqData: { uriPath: '/foo' }
287
+ }
288
+ };
277
289
 
278
290
  // run trials and count how many times sampler says okay to analyze
279
291
  let observedSampleCount = 0;
280
292
  for (let n = 0; n < TRIALS; n++) {
281
- const info = sampler.getSampleInfo();
293
+ const info = sampler.getSampleInfo({ store });
282
294
  if (info.canSample) observedSampleCount++;
283
295
  }
284
296
 
285
- const stats = getStats(opts.base_probability, observedSampleCount);
297
+ const stats = getStats(opts.base_probability);
286
298
  const { confidenceInterval: [low, high] } = stats;
287
299
 
288
300
  // for debugging/visibility
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.39.0",
3
+ "version": "1.40.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)",
@@ -18,15 +18,16 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@contrast/common": "1.26.0",
21
- "@contrast/config": "1.35.0",
22
- "@contrast/core": "1.40.0",
23
- "@contrast/dep-hooks": "1.8.0",
21
+ "@contrast/config": "1.36.0",
22
+ "@contrast/core": "1.41.0",
23
+ "@contrast/dep-hooks": "1.9.0",
24
24
  "@contrast/distringuish": "^5.1.0",
25
- "@contrast/instrumentation": "1.18.0",
26
- "@contrast/logger": "1.13.0",
27
- "@contrast/patcher": "1.12.0",
28
- "@contrast/rewriter": "1.16.0",
29
- "@contrast/scopes": "1.9.0",
25
+ "@contrast/instrumentation": "1.19.0",
26
+ "@contrast/logger": "1.14.0",
27
+ "@contrast/patcher": "1.13.0",
28
+ "@contrast/rewriter": "1.17.0",
29
+ "@contrast/route-coverage": "1.30.0",
30
+ "@contrast/scopes": "1.10.0",
30
31
  "semver": "^7.6.0"
31
32
  }
32
33
  }