@contrast/assess 1.34.0 → 1.36.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.
Files changed (171) hide show
  1. package/lib/crypto-analysis/install/crypto.js +1 -1
  2. package/lib/dataflow/propagation/install/JSON/parse-fn.js +1 -1
  3. package/lib/dataflow/propagation/install/JSON/parse.js +3 -2
  4. package/lib/dataflow/propagation/install/JSON/parse.test.js +2 -2
  5. package/lib/dataflow/propagation/install/JSON/stringify.js +11 -10
  6. package/lib/dataflow/propagation/install/JSON/stringify.test.js +3 -3
  7. package/lib/dataflow/propagation/install/array-prototype-join.js +4 -3
  8. package/lib/dataflow/propagation/install/array-prototype-join.test.js +3 -3
  9. package/lib/dataflow/propagation/install/buffer.js +2 -3
  10. package/lib/dataflow/propagation/install/contrast-methods/tag.test.js +2 -2
  11. package/lib/dataflow/propagation/install/decode-uri-component.js +5 -8
  12. package/lib/dataflow/propagation/install/decode-uri-component.test.js +1 -1
  13. package/lib/dataflow/propagation/install/ejs/escape-xml.js +6 -9
  14. package/lib/dataflow/propagation/install/ejs/escape-xml.test.js +2 -2
  15. package/lib/dataflow/propagation/install/ejs/template.js +2 -2
  16. package/lib/dataflow/propagation/install/encode-uri.js +4 -6
  17. package/lib/dataflow/propagation/install/encode-uri.test.js +2 -2
  18. package/lib/dataflow/propagation/install/escape-html.js +5 -8
  19. package/lib/dataflow/propagation/install/escape-html.test.js +3 -3
  20. package/lib/dataflow/propagation/install/escape.js +5 -8
  21. package/lib/dataflow/propagation/install/escape.test.js +2 -2
  22. package/lib/dataflow/propagation/install/fastify-send.js +3 -5
  23. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +6 -9
  24. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.test.js +1 -1
  25. package/lib/dataflow/propagation/install/joi/boolean.js +50 -52
  26. package/lib/dataflow/propagation/install/joi/expression.js +3 -10
  27. package/lib/dataflow/propagation/install/joi/index.js +98 -101
  28. package/lib/dataflow/propagation/install/joi/keys.js +10 -5
  29. package/lib/dataflow/propagation/install/joi/number.js +50 -52
  30. package/lib/dataflow/propagation/install/joi/string-schema.js +9 -14
  31. package/lib/dataflow/propagation/install/joi/utils.js +7 -4
  32. package/lib/dataflow/propagation/install/joi/values.js +5 -7
  33. package/lib/dataflow/propagation/install/mongoose/schema-map.js +5 -4
  34. package/lib/dataflow/propagation/install/mongoose/schema-map.test.js +4 -4
  35. package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +5 -4
  36. package/lib/dataflow/propagation/install/mongoose/schema-mixed.test.js +4 -5
  37. package/lib/dataflow/propagation/install/mongoose/schema-string.js +3 -4
  38. package/lib/dataflow/propagation/install/mustache-escape.js +5 -8
  39. package/lib/dataflow/propagation/install/mustache-escape.test.js +2 -2
  40. package/lib/dataflow/propagation/install/mysql-connection-escape.js +5 -8
  41. package/lib/dataflow/propagation/install/mysql-connection-escape.test.js +2 -2
  42. package/lib/dataflow/propagation/install/parse-int.js +3 -3
  43. package/lib/dataflow/propagation/install/path/basename.js +7 -12
  44. package/lib/dataflow/propagation/install/path/basename.test.js +2 -2
  45. package/lib/dataflow/propagation/install/path/common.js +2 -2
  46. package/lib/dataflow/propagation/install/path/dirname.js +5 -10
  47. package/lib/dataflow/propagation/install/path/dirname.test.js +2 -2
  48. package/lib/dataflow/propagation/install/path/extname.js +6 -11
  49. package/lib/dataflow/propagation/install/path/extname.test.js +2 -2
  50. package/lib/dataflow/propagation/install/path/format.js +7 -13
  51. package/lib/dataflow/propagation/install/path/format.test.js +2 -2
  52. package/lib/dataflow/propagation/install/path/join-and-resolve.js +7 -12
  53. package/lib/dataflow/propagation/install/path/join-and-resolve.test.js +2 -2
  54. package/lib/dataflow/propagation/install/path/normalize.js +4 -11
  55. package/lib/dataflow/propagation/install/path/normalize.test.js +2 -2
  56. package/lib/dataflow/propagation/install/path/parse.js +3 -8
  57. package/lib/dataflow/propagation/install/path/parse.test.js +2 -2
  58. package/lib/dataflow/propagation/install/path/relative.js +5 -11
  59. package/lib/dataflow/propagation/install/path/relative.test.js +2 -2
  60. package/lib/dataflow/propagation/install/path/toNamespacedPath.js +5 -11
  61. package/lib/dataflow/propagation/install/path/toNamespacedPath.test.js +2 -2
  62. package/lib/dataflow/propagation/install/pug/index.js +8 -3
  63. package/lib/dataflow/propagation/install/pug-runtime-escape.js +5 -8
  64. package/lib/dataflow/propagation/install/pug-runtime-escape.test.js +1 -1
  65. package/lib/dataflow/propagation/install/querystring/escape.js +3 -3
  66. package/lib/dataflow/propagation/install/querystring/parse.js +7 -11
  67. package/lib/dataflow/propagation/install/querystring/stringify.js +3 -3
  68. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +4 -3
  69. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.test.js +5 -3
  70. package/lib/dataflow/propagation/install/send.js +5 -10
  71. package/lib/dataflow/propagation/install/sequelize/query-generator.js +3 -4
  72. package/lib/dataflow/propagation/install/sequelize/sql-string.js +8 -12
  73. package/lib/dataflow/propagation/install/sequelize/sql-string.test.js +2 -13
  74. package/lib/dataflow/propagation/install/sql-template-strings.js +3 -5
  75. package/lib/dataflow/propagation/install/sql-template-strings.test.js +2 -2
  76. package/lib/dataflow/propagation/install/string/concat.js +2 -1
  77. package/lib/dataflow/propagation/install/string/concat.test.js +15 -2
  78. package/lib/dataflow/propagation/install/string/format-methods.js +4 -2
  79. package/lib/dataflow/propagation/install/string/format-methods.test.js +15 -2
  80. package/lib/dataflow/propagation/install/string/html-methods.js +1 -1
  81. package/lib/dataflow/propagation/install/string/html-methods.test.js +15 -2
  82. package/lib/dataflow/propagation/install/string/index.js +2 -2
  83. package/lib/dataflow/propagation/install/string/match-all.js +2 -1
  84. package/lib/dataflow/propagation/install/string/match-all.test.js +13 -0
  85. package/lib/dataflow/propagation/install/string/match.js +11 -10
  86. package/lib/dataflow/propagation/install/string/match.test.js +13 -0
  87. package/lib/dataflow/propagation/install/string/replace.js +15 -9
  88. package/lib/dataflow/propagation/install/string/replace.test.js +13 -0
  89. package/lib/dataflow/propagation/install/string/slice.js +2 -1
  90. package/lib/dataflow/propagation/install/string/slice.test.js +13 -0
  91. package/lib/dataflow/propagation/install/string/split.js +2 -1
  92. package/lib/dataflow/propagation/install/string/split.test.js +13 -0
  93. package/lib/dataflow/propagation/install/string/substring.js +2 -1
  94. package/lib/dataflow/propagation/install/string/substring.test.js +13 -0
  95. package/lib/dataflow/propagation/install/string/trim.js +4 -1
  96. package/lib/dataflow/propagation/install/string/trim.test.js +13 -0
  97. package/lib/dataflow/propagation/install/unescape.js +5 -8
  98. package/lib/dataflow/propagation/install/unescape.test.js +2 -2
  99. package/lib/dataflow/propagation/install/url/domain-parsers.js +4 -5
  100. package/lib/dataflow/propagation/install/url/domain-parsers.test.js +2 -2
  101. package/lib/dataflow/propagation/install/url/parse.js +3 -2
  102. package/lib/dataflow/propagation/install/url/parse.test.js +2 -2
  103. package/lib/dataflow/propagation/install/url/searchParams.js +5 -5
  104. package/lib/dataflow/propagation/install/url/searchParams.test.js +2 -2
  105. package/lib/dataflow/propagation/install/url/url.js +6 -3
  106. package/lib/dataflow/propagation/install/url/url.test.js +2 -2
  107. package/lib/dataflow/propagation/install/util-format.js +7 -6
  108. package/lib/dataflow/propagation/install/util-format.test.js +2 -2
  109. package/lib/dataflow/propagation/install/validator/hooks.js +7 -2
  110. package/lib/dataflow/sinks/install/child-process.js +1 -1
  111. package/lib/dataflow/sinks/install/child-process.test.js +1 -1
  112. package/lib/dataflow/sinks/install/fs.js +1 -1
  113. package/lib/dataflow/sinks/install/fs.test.js +1 -1
  114. package/lib/dataflow/sinks/install/function.js +1 -1
  115. package/lib/dataflow/sinks/install/http/request.js +2 -1
  116. package/lib/dataflow/sinks/install/http/request.test.js +1 -1
  117. package/lib/dataflow/sinks/install/http/server-response.test.js +3 -5
  118. package/lib/dataflow/sinks/install/restify.js +1 -1
  119. package/lib/dataflow/sinks/install/vm.js +4 -2
  120. package/lib/dataflow/sinks/install/vm.test.js +1 -1
  121. package/lib/dataflow/sources/handler.js +6 -3
  122. package/lib/dataflow/sources/handler.test.js +38 -0
  123. package/lib/dataflow/sources/install/body-parser1.test.js +4 -4
  124. package/lib/dataflow/sources/install/busboy.js +8 -3
  125. package/lib/dataflow/sources/install/busboy.test.js +2 -2
  126. package/lib/dataflow/sources/install/cookie-parser1.test.js +2 -2
  127. package/lib/dataflow/sources/install/express/params.js +14 -11
  128. package/lib/dataflow/sources/install/express/params.test.js +5 -7
  129. package/lib/dataflow/sources/install/express/parsedUrl.js +3 -2
  130. package/lib/dataflow/sources/install/fastify/fastify.js +7 -6
  131. package/lib/dataflow/sources/install/fastify/fastify.test.js +2 -2
  132. package/lib/dataflow/sources/install/formidable1.js +7 -6
  133. package/lib/dataflow/sources/install/formidable1.test.js +2 -2
  134. package/lib/dataflow/sources/install/hapi/hapi.js +8 -10
  135. package/lib/dataflow/sources/install/hapi/hapi.test.js +0 -1
  136. package/lib/dataflow/sources/install/http.js +20 -16
  137. package/lib/dataflow/sources/install/http.test.js +28 -34
  138. package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +7 -7
  139. package/lib/dataflow/sources/install/koa/koa-bodyparsers.test.js +3 -4
  140. package/lib/dataflow/sources/install/koa/koa-multer.js +8 -4
  141. package/lib/dataflow/sources/install/koa/koa-routers.js +7 -6
  142. package/lib/dataflow/sources/install/koa/koa-routers.test.js +2 -2
  143. package/lib/dataflow/sources/install/koa/koa2.js +7 -3
  144. package/lib/dataflow/sources/install/koa/koa2.test.js +1 -1
  145. package/lib/dataflow/sources/install/multer1.js +6 -2
  146. package/lib/dataflow/sources/install/qs6.js +1 -1
  147. package/lib/dataflow/sources/install/querystring.js +1 -1
  148. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.js +1 -4
  149. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.test.js +6 -8
  150. package/lib/dataflow/sources/install/restify/jsonBodyParser.js +0 -1
  151. package/lib/dataflow/sources/install/restify/jsonBodyParser.test.js +4 -8
  152. package/lib/dataflow/sources/install/restify/router.test.js +2 -2
  153. package/lib/dataflow/tag-utils.js +1 -1
  154. package/lib/dataflow/tracker.js +1 -1
  155. package/lib/dataflow/utils/is-safe-content-type.js +3 -2
  156. package/lib/event-factory.js +4 -4
  157. package/lib/event-factory.test.js +19 -14
  158. package/lib/get-policy.js +2 -2
  159. package/lib/index.d.ts +11 -6
  160. package/lib/index.js +18 -7
  161. package/lib/index.test.js +4 -0
  162. package/lib/make-source-context.js +37 -28
  163. package/lib/make-source-context.test.js +7 -7
  164. package/lib/response-scanning/handlers/index.js +7 -5
  165. package/lib/response-scanning/handlers/utils.js +11 -8
  166. package/lib/response-scanning/install/http.js +1 -1
  167. package/lib/sampler.js +136 -0
  168. package/lib/sampler.test.js +296 -0
  169. package/lib/session-configuration/install/express-session.js +1 -1
  170. package/lib/session-configuration/install/fastify-cookie.js +1 -1
  171. package/package.json +10 -10
package/lib/sampler.js ADDED
@@ -0,0 +1,136 @@
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 { Event } = require('@contrast/common');
19
+
20
+ /**
21
+ * @param {{
22
+ * assess: import('.').Assess,
23
+ * config: import('@contrast/config').Config,
24
+ * logger: import('@contrast/logger').Logger,
25
+ * messages: import('@contrast/common').Messages,
26
+ * }} core
27
+ * @returns {import('@contrast/common').Installable}
28
+ */
29
+ module.exports = function assess(core) {
30
+ const {
31
+ assess,
32
+ config,
33
+ logger,
34
+ messages,
35
+ } = core;
36
+
37
+ /**
38
+ * Initializes the Assess sampler only if there's a need, otherwise sets it to null.
39
+ * Clients will call the instance methods optionally: assess.sampler?.getSampleInfo().
40
+ * Determines sampler strategy and options based on effective config values.
41
+ */
42
+ function initSampler() {
43
+ const baseProbability = config.getEffectiveValue('assess.probabilistic_sampling.base_probability');
44
+
45
+ let strategy;
46
+ let opts;
47
+
48
+ if (!config.getEffectiveValue('assess.enable') || baseProbability === 0) {
49
+ // if Assess was disabled by TS use disabled sampler
50
+ strategy = 'disabled';
51
+ } else if (baseProbability < 1) {
52
+ if (
53
+ config.getEffectiveValue('assess.probabilistic_sampling.enable') ||
54
+ config.getEffectiveValue('server.environment') === 'PRODUCTION'
55
+ ) {
56
+ // strategy and opts can be more dynamic in the future
57
+ strategy = 'probabilistic';
58
+ opts = {
59
+ base_probability: baseProbability
60
+ };
61
+ }
62
+ }
63
+
64
+ if (strategy) {
65
+ // only re-init if the strategy and options are different than current sampler
66
+ if (
67
+ assess.sampler?.strategy !== strategy ||
68
+ assess.sampler?.opts?.base_probability !== opts?.base_probability
69
+ ) {
70
+ logger.info({ strategy, opts }, 'updating assess sampler');
71
+ assess.sampler = SamplerFactory[strategy](opts);
72
+ }
73
+ } else {
74
+ // disable sampling by setting sampler to null
75
+ assess.sampler = null;
76
+ logger.info('assess sampling disabled');
77
+ }
78
+ }
79
+
80
+ // initialize a first time
81
+ 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).
85
+ messages.on(Event.SERVER_SETTINGS_UPDATE, initSampler);
86
+
87
+ return core.assess.sampler;
88
+ };
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
+ }
@@ -0,0 +1,296 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('events');
4
+ const { expect } = require('chai');
5
+ const { Event } = require('@contrast/common');
6
+ const mocks = require('@contrast/test/mocks');
7
+
8
+ const TRIALS = 1000;
9
+ const ZSCORE = 3.891; // 99.99% confidence
10
+
11
+ describe('assess sampler', function() {
12
+ it('sampling is disabled by default and assess.sampler is null', function() {
13
+ const core = initMockCore();
14
+ require('./sampler')(core);
15
+ expect(core.assess.sampler).to.be.null;
16
+ });
17
+
18
+ it('samples at default rate of 0.01', function() {
19
+ const core = initMockCore();
20
+ core.config.setValue('assess.enable', true, 'CONTRAST_UI');
21
+ core.config.setValue('assess.probabilistic_sampling.enable', true, 'CONTRAST_UI');
22
+ // initialize after configs are set
23
+ const sampler = require('./sampler')(core);
24
+
25
+ // verify configs set in before setup
26
+ expect(sampler).to.have.property('strategy', 'probabilistic');
27
+ expect(sampler.opts).to.have.property('base_probability', 0.01);
28
+ expect(sampler).to.have.property('getSampleInfo').to.be.a('function');
29
+ // test behavior
30
+ testProbabilisticSampler(sampler);
31
+ });
32
+
33
+ it('samples at p=base_probability', function() {
34
+ const core = initMockCore();
35
+ core.config.setValue('assess.probabilistic_sampling.enable', true, 'CONTRAST_UI');
36
+ core.config.setValue('assess.probabilistic_sampling.base_probability', 0.25, 'CONTRAST_UI');
37
+ // initialize after configs are set
38
+ const sampler = require('./sampler')(core);
39
+
40
+ // verify configs
41
+ expect(sampler).to.have.property('strategy', 'probabilistic');
42
+ expect(sampler.opts).to.have.property('base_probability', 0.25);
43
+ expect(sampler).to.have.property('getSampleInfo').to.be.a('function');
44
+ // test behavior
45
+ testProbabilisticSampler(sampler);
46
+ });
47
+
48
+ it('probability is dynamic and set by TS server setting: assess.sampling.request_frequency', function() {
49
+ const core = initMockCore();
50
+ core.config.setValue('assess.probabilistic_sampling.enable', true, 'CONTRAST_UI');
51
+ core.config.setValue('assess.probabilistic_sampling.base_probability', 0.25, 'CONTRAST_UI');
52
+ // initialize after configs are set
53
+ require('./sampler')(core);
54
+ // don't destructure to just { sampler } since the value of assess.sampler changes on update.
55
+ const { assess } = core;
56
+
57
+ // verify configs
58
+ expect(assess.sampler).to.have.property('strategy', 'probabilistic');
59
+ expect(assess.sampler.opts).to.have.property('base_probability', 0.25);
60
+ expect(assess.sampler).to.have.property('getSampleInfo').to.be.a('function');
61
+ // test behavior
62
+ testProbabilisticSampler(assess.sampler);
63
+
64
+ // update
65
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
66
+ assess: {
67
+ sampling: {
68
+ enable: true,
69
+ request_frequency: 2, // 1 / 2 = 0.50 base_probability
70
+ }
71
+ }
72
+ });
73
+
74
+ // verify sampler was updated
75
+ expect(assess.sampler.opts.base_probability).to.equal(0.50);
76
+ // test behavior
77
+ testProbabilisticSampler(assess.sampler);
78
+
79
+ // test logging throughout init/updates
80
+ expect(core.logger.info.getCalls().map(c => c.args)).to.deep.equal([
81
+ // when initialized
82
+ [
83
+ { strategy: 'probabilistic', opts: { base_probability: 0.25 } },
84
+ 'updating assess sampler'
85
+ ],
86
+ // update 1
87
+ [
88
+ { strategy: 'probabilistic', opts: { base_probability: 0.5 } },
89
+ 'updating assess sampler'
90
+ ]
91
+ ]);
92
+ });
93
+
94
+ it('sampler always returns `canSample: false` if TS `assess.enable` setting is false', function() {
95
+ const core = initMockCore();
96
+ core.config.setValue('assess.enable', true, 'CONTRAST_UI');
97
+ core.config.setValue('assess.probabilistic_sampling.enable', true, 'CONTRAST_UI');
98
+ core.config.setValue('assess.probabilistic_sampling.base_probability', 0.50, 'CONTRAST_UI');
99
+ // initializes after configs are set
100
+ require('./sampler')(core);
101
+ // don't destructure to just { sampler } since the value of assess.sampler changes on update.
102
+ const { assess } = core;
103
+
104
+ // verify configs set in before setup
105
+ expect(assess.sampler).to.have.property('strategy', 'probabilistic');
106
+ expect(assess.sampler.opts).to.have.property('base_probability', 0.50);
107
+ expect(assess.sampler).to.have.property('getSampleInfo').to.be.a('function');
108
+ // test behavior
109
+ testProbabilisticSampler(assess.sampler);
110
+
111
+ // runtime update that says to disable assess
112
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
113
+ assess: { enable: false }
114
+ });
115
+
116
+ // verify updated sampling strategy
117
+ expect(assess.sampler.strategy).to.equal('disabled');
118
+ // test behavior
119
+ testDisabledSampler(assess.sampler);
120
+
121
+ // test logging throughout init/updates
122
+ expect(core.logger.info.getCalls().map(c => c.args)).to.deep.equal([
123
+ // when initialized
124
+ [
125
+ { strategy: 'probabilistic', opts: { base_probability: 0.5 } },
126
+ 'updating assess sampler'
127
+ ],
128
+ // update 1
129
+ [
130
+ { strategy: 'disabled', opts: undefined },
131
+ 'updating assess sampler'
132
+ ]
133
+ ]);
134
+ });
135
+
136
+ it('sampler behavior adjusts to series of TS updates', function() {
137
+ // setup
138
+ const core = initMockCore();
139
+ core.config.setValue('assess.enable', true, 'CONTRAST_UI');
140
+ core.config.setValue('assess.probabilistic_sampling.enable', false, 'DEFAULT_VALUE');
141
+ core.config.setValue('assess.probabilistic_sampling.base_probability', 0.50, 'CONTRAST_UI');
142
+ // initializes after configs are set
143
+ require('./sampler')(core);
144
+ // don't destructure to just { sampler } since the value of assess.sampler changes on update.
145
+ const { assess } = core;
146
+
147
+ // disabled by config
148
+ expect(assess.sampler).to.be.null;
149
+
150
+ // set to PRODUCTION to enable sampling
151
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
152
+ environment: 'PRODUCTION',
153
+ });
154
+
155
+ // verify sampler was updated
156
+ expect(assess.sampler.opts.base_probability).to.equal(0.50);
157
+ // test behavior
158
+ testProbabilisticSampler(assess.sampler);
159
+
160
+ // disable sampling by setting to DEVELOPMENT
161
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
162
+ environment: 'DEVELOPMENT',
163
+ });
164
+
165
+ // since assess.probabilistic_sampling is off and env is no longer PRODUCTION
166
+ expect(assess.sampler).to.be.null;
167
+
168
+ // disable Assess
169
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
170
+ assess: { enable: false },
171
+ });
172
+
173
+ // verify updated sampling strategy
174
+ expect(assess.sampler.strategy).to.equal('disabled');
175
+ // test behavior
176
+ testDisabledSampler(assess.sampler);
177
+
178
+ // re-enable Assess and set new probability via request_frequency
179
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
180
+ assess: {
181
+ enable: true,
182
+ sampling: {
183
+ enable: true,
184
+ request_frequency: 50, //
185
+ }
186
+ },
187
+ });
188
+
189
+ // verify sampler was updated
190
+ expect(assess.sampler.opts.base_probability).to.equal(1 / 50);
191
+ // test behavior
192
+ testProbabilisticSampler(assess.sampler);
193
+
194
+ // test logging throughout init/updates
195
+ expect(core.logger.info.getCalls().map(c => c.args)).to.deep.equal([
196
+ // when initialized
197
+ ['assess sampling disabled'],
198
+ // update 1: env=PRODUCTION enables sampling
199
+ [
200
+ { strategy: 'probabilistic', opts: { base_probability: 0.5 } },
201
+ 'updating assess sampler'
202
+ ],
203
+ // update 2: env = DEVELOPMENT disables sampling
204
+ ['assess sampling disabled'],
205
+ // update 3: assess.enable=false sets disabled sampling strategy
206
+ [
207
+ { strategy: 'disabled', opts: undefined },
208
+ 'updating assess sampler'
209
+ ],
210
+ // update 5: assess.enable=true and request_frequency=50 re-enables assess and new sampling probability
211
+ [
212
+ { strategy: 'probabilistic', opts: { base_probability: 0.02 } },
213
+ 'updating assess sampler'
214
+ ]
215
+ ]);
216
+ });
217
+ });
218
+
219
+ function initMockCore() {
220
+ const { CONTRAST_CONFIG_PATH } = process.env;
221
+ let core;
222
+
223
+ try {
224
+ // ensure default config if devs use this
225
+ process.env.CONTRAST_CONFIG_PATH = '';
226
+ core = {
227
+ // sampler needs this namespace to exist
228
+ assess: {},
229
+ // mocks
230
+ messages: new EventEmitter(),
231
+ logger: mocks.logger(),
232
+ };
233
+ // use actual config so we can get dynamic effective values with TS message
234
+ // updates (mock doesn't). we can also test new effective config mappings.
235
+ require('@contrast/config')(core);
236
+ core.config.setValue('assess.enable', true, 'CONTRAST_UI');
237
+ } catch (err) {
238
+ console.dir(err);
239
+ throw err;
240
+ }
241
+
242
+ // reset to orig value
243
+ if (CONTRAST_CONFIG_PATH) process.env.CONTRAST_CONFIG_PATH = CONTRAST_CONFIG_PATH;
244
+
245
+ return core;
246
+ }
247
+
248
+ // https://en.wikipedia.org/wiki/Binomial_distribution
249
+ function getStats(p) {
250
+ const mean = TRIALS * p;
251
+ const variance = mean * (1 - p);
252
+ const standardDev = Math.sqrt(variance);
253
+ const marginOfError = ZSCORE * standardDev;
254
+ const confidenceInterval = [
255
+ Math.max(0, mean - marginOfError),
256
+ mean + marginOfError
257
+ ];
258
+
259
+ return {
260
+ p,
261
+ mean,
262
+ variance,
263
+ standardDev,
264
+ confidenceInterval,
265
+ trials: TRIALS,
266
+ zscore: ZSCORE,
267
+ };
268
+ }
269
+
270
+ function testProbabilisticSampler(sampler) {
271
+ const { opts } = sampler;
272
+
273
+ // run trials and count how many times sampler says okay to analyze
274
+ let observedSampleCount = 0;
275
+ for (let n = 0; n < TRIALS; n++) {
276
+ const info = sampler.getSampleInfo();
277
+ if (info.canSample) observedSampleCount++;
278
+ }
279
+
280
+ const stats = getStats(opts.base_probability, observedSampleCount);
281
+ const { confidenceInterval: [low, high] } = stats;
282
+
283
+ // for debugging/visibility
284
+ // console.log({ observedSampleCount, ...stats })
285
+ expect(observedSampleCount).to.be.greaterThan(low).and.lessThan(high);
286
+ }
287
+
288
+ function testDisabledSampler(sampler) {
289
+ // run trials and count how many times sampler says okay to analyze
290
+ let observedSampleCount = 0;
291
+ for (let n = 0; n < TRIALS; n++) {
292
+ const info = sampler.getSampleInfo();
293
+ if (info.canSample) observedSampleCount++;
294
+ }
295
+ expect(observedSampleCount).to.equal(0);
296
+ }
@@ -14,7 +14,7 @@
14
14
  */
15
15
  'use strict';
16
16
 
17
- const { StringPrototypeToLowerCase } = require('@contrast/common');
17
+ const { primordials: { StringPrototypeToLowerCase } } = require('@contrast/common');
18
18
  const { patchType } = require('../common');
19
19
 
20
20
  /**
@@ -14,7 +14,7 @@
14
14
  */
15
15
  'use strict';
16
16
 
17
- const { StringPrototypeToLowerCase } = require('@contrast/common');
17
+ const { primordials: { StringPrototypeToLowerCase } } = require('@contrast/common');
18
18
  const { patchType } = require('../common');
19
19
 
20
20
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.34.0",
3
+ "version": "1.36.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,16 +17,16 @@
17
17
  "test": "../scripts/test.sh"
18
18
  },
19
19
  "dependencies": {
20
- "@contrast/common": "1.25.0",
21
- "@contrast/config": "1.32.0",
22
- "@contrast/core": "1.36.0",
23
- "@contrast/dep-hooks": "1.4.0",
20
+ "@contrast/common": "1.26.0",
21
+ "@contrast/config": "1.34.0",
22
+ "@contrast/core": "1.38.0",
23
+ "@contrast/dep-hooks": "1.6.0",
24
24
  "@contrast/distringuish": "^5.1.0",
25
- "@contrast/instrumentation": "1.14.0",
26
- "@contrast/logger": "1.9.0",
27
- "@contrast/patcher": "1.8.0",
28
- "@contrast/rewriter": "1.12.0",
29
- "@contrast/scopes": "1.5.0",
25
+ "@contrast/instrumentation": "1.16.0",
26
+ "@contrast/logger": "1.11.0",
27
+ "@contrast/patcher": "1.10.0",
28
+ "@contrast/rewriter": "1.14.0",
29
+ "@contrast/scopes": "1.7.0",
30
30
  "semver": "^7.6.0"
31
31
  }
32
32
  }