@contrast/assess 1.40.0 → 1.42.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 (167) hide show
  1. package/lib/crypto-analysis/install/crypto.js +4 -5
  2. package/lib/crypto-analysis/install/crypto.test.js +1 -1
  3. package/lib/crypto-analysis/install/math.js +2 -4
  4. package/lib/dataflow/propagation/install/JSON/parse.js +2 -3
  5. package/lib/dataflow/propagation/install/JSON/stringify.js +3 -4
  6. package/lib/dataflow/propagation/install/array-prototype-join.js +2 -3
  7. package/lib/dataflow/propagation/install/buffer.js +3 -4
  8. package/lib/dataflow/propagation/install/contrast-methods/add.js +2 -3
  9. package/lib/dataflow/propagation/install/contrast-methods/number.js +2 -3
  10. package/lib/dataflow/propagation/install/contrast-methods/string.js +2 -3
  11. package/lib/dataflow/propagation/install/contrast-methods/tag.js +2 -3
  12. package/lib/dataflow/propagation/install/decode-uri-component.js +2 -3
  13. package/lib/dataflow/propagation/install/ejs/escape-xml.js +3 -4
  14. package/lib/dataflow/propagation/install/ejs/template.js +3 -4
  15. package/lib/dataflow/propagation/install/ejs/template.test.js +1 -1
  16. package/lib/dataflow/propagation/install/encode-uri.js +2 -3
  17. package/lib/dataflow/propagation/install/escape-html.js +3 -4
  18. package/lib/dataflow/propagation/install/escape.js +2 -3
  19. package/lib/dataflow/propagation/install/fastify-send.js +3 -3
  20. package/lib/dataflow/propagation/install/fastify-send.test.js +1 -3
  21. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +3 -4
  22. package/lib/dataflow/propagation/install/isnumeric-0.js +1 -1
  23. package/lib/dataflow/propagation/install/joi/any.js +1 -1
  24. package/lib/dataflow/propagation/install/joi/any.test.js +1 -1
  25. package/lib/dataflow/propagation/install/joi/array.test.js +5 -5
  26. package/lib/dataflow/propagation/install/joi/boolean.js +3 -3
  27. package/lib/dataflow/propagation/install/joi/boolean.test.js +1 -1
  28. package/lib/dataflow/propagation/install/joi/expression.js +3 -3
  29. package/lib/dataflow/propagation/install/joi/expression.test.js +1 -1
  30. package/lib/dataflow/propagation/install/joi/index.js +3 -3
  31. package/lib/dataflow/propagation/install/joi/keys.js +3 -3
  32. package/lib/dataflow/propagation/install/joi/number.js +3 -3
  33. package/lib/dataflow/propagation/install/joi/number.test.js +1 -1
  34. package/lib/dataflow/propagation/install/joi/object.js +1 -1
  35. package/lib/dataflow/propagation/install/joi/object.test.js +1 -1
  36. package/lib/dataflow/propagation/install/joi/ref.test.js +4 -4
  37. package/lib/dataflow/propagation/install/joi/string-schema.js +4 -4
  38. package/lib/dataflow/propagation/install/joi/string-schema.test.js +4 -4
  39. package/lib/dataflow/propagation/install/joi/values.js +3 -3
  40. package/lib/dataflow/propagation/install/mongoose/schema-map.js +4 -4
  41. package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +4 -4
  42. package/lib/dataflow/propagation/install/mongoose/schema-string.js +4 -4
  43. package/lib/dataflow/propagation/install/mustache-escape.js +3 -4
  44. package/lib/dataflow/propagation/install/mustache-escape.test.js +1 -1
  45. package/lib/dataflow/propagation/install/mysql-connection-escape.js +22 -14
  46. package/lib/dataflow/propagation/install/mysql-connection-escape.test.js +1 -1
  47. package/lib/dataflow/propagation/install/parse-int.js +2 -3
  48. package/lib/dataflow/propagation/install/path/basename.js +3 -4
  49. package/lib/dataflow/propagation/install/path/dirname.js +3 -4
  50. package/lib/dataflow/propagation/install/path/extname.js +3 -4
  51. package/lib/dataflow/propagation/install/path/format.js +3 -4
  52. package/lib/dataflow/propagation/install/path/index.test.js +1 -1
  53. package/lib/dataflow/propagation/install/path/join-and-resolve.js +3 -4
  54. package/lib/dataflow/propagation/install/path/normalize.js +4 -5
  55. package/lib/dataflow/propagation/install/path/parse.js +3 -4
  56. package/lib/dataflow/propagation/install/path/relative.js +4 -5
  57. package/lib/dataflow/propagation/install/path/toNamespacedPath.js +3 -4
  58. package/lib/dataflow/propagation/install/pug/index.js +3 -4
  59. package/lib/dataflow/propagation/install/pug-runtime-escape.js +3 -4
  60. package/lib/dataflow/propagation/install/querystring/escape.js +3 -4
  61. package/lib/dataflow/propagation/install/querystring/escape.test.js +1 -1
  62. package/lib/dataflow/propagation/install/querystring/parse.js +3 -4
  63. package/lib/dataflow/propagation/install/querystring/parse.test.js +1 -1
  64. package/lib/dataflow/propagation/install/querystring/stringify.js +3 -4
  65. package/lib/dataflow/propagation/install/querystring/stringify.test.js +1 -1
  66. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +2 -3
  67. package/lib/dataflow/propagation/install/send.js +3 -3
  68. package/lib/dataflow/propagation/install/sequelize/query-generator.js +3 -3
  69. package/lib/dataflow/propagation/install/sequelize/query-generator.test.js +2 -1
  70. package/lib/dataflow/propagation/install/sequelize/sql-string.js +5 -5
  71. package/lib/dataflow/propagation/install/sql-template-strings.js +3 -3
  72. package/lib/dataflow/propagation/install/string/concat.js +2 -3
  73. package/lib/dataflow/propagation/install/string/format-methods.js +2 -3
  74. package/lib/dataflow/propagation/install/string/html-methods.js +3 -4
  75. package/lib/dataflow/propagation/install/string/match-all.js +2 -3
  76. package/lib/dataflow/propagation/install/string/match.js +2 -3
  77. package/lib/dataflow/propagation/install/string/replace.js +2 -3
  78. package/lib/dataflow/propagation/install/string/slice.js +2 -3
  79. package/lib/dataflow/propagation/install/string/split.js +2 -3
  80. package/lib/dataflow/propagation/install/string/substring.js +2 -3
  81. package/lib/dataflow/propagation/install/string/trim.js +2 -3
  82. package/lib/dataflow/propagation/install/unescape.js +2 -3
  83. package/lib/dataflow/propagation/install/url/domain-parsers.js +3 -4
  84. package/lib/dataflow/propagation/install/url/parse.js +3 -4
  85. package/lib/dataflow/propagation/install/url/parse.test.js +2 -2
  86. package/lib/dataflow/propagation/install/url/searchParams.js +3 -4
  87. package/lib/dataflow/propagation/install/url/url.js +3 -4
  88. package/lib/dataflow/propagation/install/util-format.js +3 -4
  89. package/lib/dataflow/propagation/install/validator/hooks.js +9 -9
  90. package/lib/dataflow/sinks/install/child-process.js +5 -6
  91. package/lib/dataflow/sinks/install/eval.js +2 -3
  92. package/lib/dataflow/sinks/install/express/reflected-xss.js +3 -4
  93. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +3 -4
  94. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +3 -4
  95. package/lib/dataflow/sinks/install/fs.js +4 -5
  96. package/lib/dataflow/sinks/install/fs.test.js +2 -2
  97. package/lib/dataflow/sinks/install/function.js +2 -3
  98. package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.js +3 -4
  99. package/lib/dataflow/sinks/install/http/request.js +3 -4
  100. package/lib/dataflow/sinks/install/http/request.test.js +2 -2
  101. package/lib/dataflow/sinks/install/http/server-response.js +5 -6
  102. package/lib/dataflow/sinks/install/http/server-response.test.js +3 -3
  103. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +3 -4
  104. package/lib/dataflow/sinks/install/libxmljs.js +4 -5
  105. package/lib/dataflow/sinks/install/libxmljs.test.js +2 -2
  106. package/lib/dataflow/sinks/install/marsdb.js +3 -4
  107. package/lib/dataflow/sinks/install/marsdb.test.js +3 -3
  108. package/lib/dataflow/sinks/install/mongodb.js +3 -4
  109. package/lib/dataflow/sinks/install/mongodb.test.js +2 -6
  110. package/lib/dataflow/sinks/install/mssql.js +4 -5
  111. package/lib/dataflow/sinks/install/mssql.test.js +2 -2
  112. package/lib/dataflow/sinks/install/mysql.js +4 -5
  113. package/lib/dataflow/sinks/install/mysql.test.js +2 -11
  114. package/lib/dataflow/sinks/install/node-serialize.js +3 -4
  115. package/lib/dataflow/sinks/install/node-serialize.test.js +1 -3
  116. package/lib/dataflow/sinks/install/postgres.js +5 -6
  117. package/lib/dataflow/sinks/install/postgres.test.js +3 -9
  118. package/lib/dataflow/sinks/install/restify.js +3 -4
  119. package/lib/dataflow/sinks/install/restify.test.js +3 -5
  120. package/lib/dataflow/sinks/install/sequelize.js +3 -4
  121. package/lib/dataflow/sinks/install/sqlite3.js +3 -4
  122. package/lib/dataflow/sinks/install/vm.js +3 -4
  123. package/lib/dataflow/sources/install/body-parser1.js +2 -4
  124. package/lib/dataflow/sources/install/body-parser1.test.js +4 -8
  125. package/lib/dataflow/sources/install/busboy.js +3 -4
  126. package/lib/dataflow/sources/install/busboy.test.js +2 -2
  127. package/lib/dataflow/sources/install/cookie-parser1.js +2 -4
  128. package/lib/dataflow/sources/install/cookie-parser1.test.js +2 -4
  129. package/lib/dataflow/sources/install/express/params.js +56 -38
  130. package/lib/dataflow/sources/install/express/params.test.js +80 -73
  131. package/lib/dataflow/sources/install/express/parsedUrl.js +45 -29
  132. package/lib/dataflow/sources/install/express/parsedUrl.test.js +71 -29
  133. package/lib/dataflow/sources/install/fastify/fastify.js +2 -3
  134. package/lib/dataflow/sources/install/fastify/fastify.test.js +3 -6
  135. package/lib/dataflow/sources/install/formidable1.js +2 -3
  136. package/lib/dataflow/sources/install/hapi/hapi.js +1 -2
  137. package/lib/dataflow/sources/install/http.js +2 -3
  138. package/lib/dataflow/sources/install/http.test.js +2 -2
  139. package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +3 -5
  140. package/lib/dataflow/sources/install/koa/koa-multer.js +3 -4
  141. package/lib/dataflow/sources/install/koa/koa-multer.test.js +1 -1
  142. package/lib/dataflow/sources/install/koa/koa-routers.js +3 -4
  143. package/lib/dataflow/sources/install/koa/koa2.js +2 -4
  144. package/lib/dataflow/sources/install/multer1.js +2 -3
  145. package/lib/dataflow/sources/install/multer1.test.js +1 -3
  146. package/lib/dataflow/sources/install/qs6.js +2 -4
  147. package/lib/dataflow/sources/install/querystring.js +2 -3
  148. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.js +2 -3
  149. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.test.js +1 -1
  150. package/lib/dataflow/sources/install/restify/jsonBodyParser.js +2 -3
  151. package/lib/dataflow/sources/install/restify/jsonBodyParser.test.js +1 -1
  152. package/lib/dataflow/sources/install/restify/router.js +2 -4
  153. package/lib/dataflow/sources/install/restify/router.test.js +4 -6
  154. package/lib/get-source-context.js +77 -37
  155. package/lib/get-source-context.test.js +106 -53
  156. package/lib/index.d.ts +3 -9
  157. package/lib/response-scanning/install/http.js +3 -3
  158. package/lib/response-scanning/install/http.test.js +2 -2
  159. package/lib/session-configuration/install/express-session.js +1 -1
  160. package/lib/session-configuration/install/express-session.test.js +1 -3
  161. package/lib/session-configuration/install/fastify-cookie.js +1 -1
  162. package/lib/session-configuration/install/fastify-cookie.test.js +1 -3
  163. package/lib/session-configuration/install/koa.js +1 -1
  164. package/lib/session-configuration/install/koa.test.js +1 -1
  165. package/package.json +11 -11
  166. package/lib/constants.js +0 -26
  167. package/lib/dataflow/sinks/install/fs-original.js +0 -170
@@ -16,7 +16,6 @@
16
16
  'use strict';
17
17
 
18
18
  const { InputType: { URL_PARAMETER } } = require('@contrast/common');
19
- const { InstrumentationType: { SOURCE } } = require('../../../../constants');
20
19
  const { patchType } = require('../../common');
21
20
 
22
21
  module.exports = function init(core) {
@@ -33,16 +32,15 @@ module.exports = function init(core) {
33
32
  return core.assess.dataflow.sources.restifyInstrumentation.router = {
34
33
  install() {
35
34
  depHooks.resolve(
36
- { name: 'restify', file: 'lib/router.js', version: '>=8' },
35
+ { name: 'restify', file: 'lib/router.js', version: '>=8 <12' },
37
36
  (Router) => {
38
37
  patcher.patch(Router.prototype, 'lookup', {
39
38
  name: 'restify.Router.prototype.lookup',
40
39
  patchType,
41
40
  post({ args: [req], hooked, orig, name, funcKey }) {
42
- const sourceContext = getSourceContext(SOURCE);
41
+ const sourceContext = getSourceContext();
43
42
 
44
43
  if (!sourceContext) {
45
- logger.error({ funcKey }, 'unable to handle source. Missing `sourceContext`');
46
44
  return;
47
45
  }
48
46
 
@@ -17,7 +17,7 @@ describe('assess dataflow sources restify router', function () {
17
17
  req = {};
18
18
 
19
19
  core.depHooks.resolve
20
- .withArgs({ name: 'restify', file: 'lib/router.js', version: '>=8' })
20
+ .withArgs({ name: 'restify', file: 'lib/router.js', version: '>=8 <12' })
21
21
  .yields(Router);
22
22
 
23
23
  init(core).install();
@@ -27,11 +27,9 @@ describe('assess dataflow sources restify router', function () {
27
27
  simulateRequestScope(() => {
28
28
  Router.prototype.lookup(req);
29
29
 
30
- expect(handle).not.to.have.been.called;
31
- expect(core.logger.error).to.have.been.calledWith(
32
- { funcKey: 'assess-dataflow-source:restify.Router.prototype.lookup' },
33
- 'unable to handle source. Missing `sourceContext`'
34
- );
30
+ expect(handle).not.called;
31
+ expect(core.logger.trace.callCount).greaterThan(0);
32
+ expect(core.logger.trace.lastCall.args[0]).includes('Assess intentionally disabled');
35
33
  }, { assess: { policy: null } });
36
34
  });
37
35
 
@@ -12,16 +12,17 @@
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
-
15
+ // @ts-check
16
16
  'use strict';
17
17
 
18
- const { InstrumentationType } = require('./constants');
18
+ /** @typedef {import('@contrast/assess').SourceContext} SourceContext */
19
19
 
20
20
  /**
21
21
  * @param {{
22
- * assess: import('@contrast/assess').Assess,
23
- * config: import('@contrast/config').Config,
24
- * scopes: import('@contrast/scopes').Scopes,
22
+ * readonly config: import('@contrast/config').Config;
23
+ * readonly logger: import('@contrast/logger').Logger;
24
+ * readonly scopes: import('@contrast/scopes').Scopes;
25
+ * assess: import('@contrast/assess').Assess;
25
26
  * }} core
26
27
  */
27
28
  module.exports = function(core) {
@@ -32,42 +33,81 @@ module.exports = function(core) {
32
33
  } = core;
33
34
 
34
35
  /**
35
- * @param {import('./constants.js').InstrumentationType} type
36
- * @returns {import('@contrast/assess').SourceContext|null} the assess store
36
+ * Used by all propagator instrumentation to make sure that:
37
+ * - the assess store is available
38
+ * - assess is enabled for this request
39
+ * - instrumentation is not locked
40
+ *
41
+ * Any code that does not use this function and directly accesses the assess
42
+ * source context will recurse until the stack is blown if the following
43
+ * checks are not correctly made.
44
+ * @returns {SourceContext | null}
37
45
  */
38
- return core.assess.getSourceContext = function getSourceContext(type, ...rest) {
46
+ core.assess.getPropagatorContext = function getPropagatorContext() {
47
+ if (instrumentation.isLocked()) return null;
48
+
49
+ // the following logging used to be done by the caller, but has been moved
50
+ // here as opposed to overloading `ctx.policy` with a special value so the
51
+ // caller could determine whether no source context was available or the
52
+ // request is being intentionally excluded. A negative of this is that the
53
+ // function name is not available to be included in the log.
54
+ const ctx = sources.getStore()?.assess;
55
+ if (!ctx) return null;
56
+
57
+ // there is a context, but if policy is null then assess is intentionally
58
+ // disabled (i.e., url exclusion or the request is not sampled).
59
+ if (!ctx.policy) {
60
+ core.logger.trace('Assess intentionally disabled for this request');
61
+ return null;
62
+ }
63
+
64
+ if (ctx.propagationEventsCount > config.assess.max_propagation_events) return null;
65
+
66
+ return ctx;
67
+ };
68
+
69
+ /**
70
+ * @param {import('@contrast/common').Rule} ruleId
71
+ * @returns {SourceContext | null}
72
+ */
73
+ core.assess.getSinkContext = function getSinkContext(ruleId) {
74
+ if (instrumentation.isLocked()) return null;
75
+
39
76
  const ctx = sources.getStore()?.assess;
40
- // <unsafe>
41
- // This method is expected to be called by all Assess instrumentation components prior to doing work.
42
- // Until we check policy, and whether instrumentation is locked, any instrumentation that is called before
43
- // the </unsafe> section below will result in infinite recursion.
44
- //
45
- // E.g. Uncommenting any line below will cause a stack overflow:
46
- // 'asdf'.concat()
47
- // console.log() // even though this is deadzoned, we haven't checked whether instrumentation is locked yet
48
- //
49
- // policy will not exist if assess is altogether disabled for the active request e.g. url exclusion
50
- if (!ctx?.policy || instrumentation.isLocked()) return null;
51
- // </unsafe> but still be careful
52
-
53
- switch (type) {
54
- case InstrumentationType.PROPAGATOR: {
55
- if (ctx.propagationEventsCount > config.assess.max_propagation_events) return null;
56
- break;
57
- }
58
- case InstrumentationType.SOURCE: {
59
- if (ctx.sourceEventsCount > config.assess.max_context_source_events) return null;
60
- break;
61
- }
62
-
63
- case InstrumentationType.RULE: {
64
- const [ruleId] = rest;
65
- if (!ruleId) break;
66
- if (!ctx.policy?.enabledRules?.has?.(ruleId) || ruleScopes.isLocked(ruleId)) return null;
67
- break;
68
- }
77
+ if (!ctx) return null;
78
+
79
+ if (!ctx.policy) {
80
+ core.logger.trace('Assess intentionally disabled for this request');
81
+ return null;
69
82
  }
70
83
 
84
+ if (ruleId && !ctx.policy?.enabledRules?.has?.(ruleId) || ruleScopes.isLocked(ruleId)) return null;
85
+
71
86
  return ctx;
72
87
  };
88
+
89
+ /** @returns {SourceContext | null} */
90
+ core.assess.getSourceContext = function getSourceContext() {
91
+ if (instrumentation.isLocked()) return null;
92
+
93
+ const ctx = sources.getStore()?.assess;
94
+ if (!ctx) {
95
+ // because this is a real error, and we don't have the function name
96
+ // that the caller previously logged, we generate a stack trace to
97
+ // capture that information.
98
+ const err = new Error('No source context found');
99
+ core.logger.warn({ err }, 'assess running outside of request scope');
100
+ return null;
101
+ }
102
+
103
+ if (!ctx.policy) {
104
+ core.logger.trace('Assess intentionally disabled for this request');
105
+ return null;
106
+ }
107
+
108
+ if (ctx.sourceEventsCount > config.assess.max_context_source_events) return null;
109
+
110
+ return ctx;
111
+ };
112
+
73
113
  };
@@ -3,9 +3,29 @@
3
3
  const { expect } = require('chai');
4
4
  const { Event } = require('@contrast/common');
5
5
  const { initAssessFixture } = require('@contrast/test/fixtures');
6
- const {
7
- InstrumentationType: { SOURCE, PROPAGATOR, RULE }
8
- } = require('./constants');
6
+ const sinon = require('sinon');
7
+
8
+ function execStoreAssertions(assessStore) {
9
+ expect(assessStore).to.be.an('object').and.deep.include({
10
+ reqData: {
11
+ ip: '127.0.0.1',
12
+ httpVersion: '1.1',
13
+ method: 'get',
14
+ headers: {
15
+ 'content-type': 'text/html',
16
+ language: 'en',
17
+ referer: 'http://fake.url.foo',
18
+ },
19
+ uriPath: '/index',
20
+ queries: '_id=123',
21
+ contentType: 'text/html'
22
+ },
23
+ responseData: {},
24
+ sourceEventsCount: 0,
25
+ propagationEventsCount: 0,
26
+ });
27
+ expect(assessStore.policy.enabledRules).to.have.length.greaterThan(5);
28
+ }
9
29
 
10
30
  describe('assess getSourceContext', function () {
11
31
  let core, simulateRequestScope;
@@ -14,86 +34,74 @@ describe('assess getSourceContext', function () {
14
34
  ({ core, simulateRequestScope } = initAssessFixture());
15
35
  });
16
36
 
17
- function execStoreAssertions(assessStore) {
18
- expect(assessStore).to.be.an('object').and.deep.include({
19
- reqData: {
20
- ip: '127.0.0.1',
21
- httpVersion: '1.1',
22
- method: 'get',
23
- headers: {
24
- 'content-type': 'text/html',
25
- language: 'en',
26
- referer: 'http://fake.url.foo',
27
- },
28
- uriPath: '/index',
29
- queries: '_id=123',
30
- contentType: 'text/html'
31
- },
32
- responseData: {},
33
- sourceEventsCount: 0,
34
- propagationEventsCount: 0,
37
+ describe('getPropagatorContext()', function() {
38
+ it('returns null when instrumentation is locked', function () {
39
+ sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
40
+
41
+ simulateRequestScope(() => {
42
+ expect(core.assess.getPropagatorContext()).to.be.null;
43
+ });
35
44
  });
36
- expect(assessStore.policy.enabledRules).to.have.length.greaterThan(5);
37
- }
38
45
 
39
- it('returns `null` when not in request scope', function() {
40
- expect(core.assess.getSourceContext()).to.be.null;
41
- });
46
+ it('returns null when not in request scope', function() {
47
+ expect(core.assess.getPropagatorContext()).to.be.null;
48
+ });
42
49
 
43
- describe('getSourceContext()', function() {
44
- it('returns assess store when in request scope', function() {
50
+ it('returns null when assess is disabled by policy', function() {
45
51
  simulateRequestScope(() => {
46
- execStoreAssertions(core.assess.getSourceContext());
47
- });
52
+ expect(core.assess.getPropagatorContext()).to.be.null;
53
+ expect(core.logger.trace).to.have.been.calledWith('Assess intentionally disabled for this request');
54
+ }, { assess: { policy: undefined } });
48
55
  });
49
- });
50
56
 
51
- describe('.getSourceContext(SOURCE?)', function() {
52
- it('returns assess store when max source event threshold is not met', function() {
57
+ it('returns assess store when max propagation event threshold is not met', function() {
53
58
  simulateRequestScope(() => {
54
- execStoreAssertions(core.assess.getSourceContext(SOURCE));
59
+ execStoreAssertions(core.assess.getPropagatorContext());
55
60
  });
56
61
  });
57
62
 
58
- it('returns `null` when max source event threshold is exceeded', function() {
63
+ it('returns null when max propagation event threshold is exceeded', function() {
64
+ core.config.setValue('assess.max_propagation_events', 10);
65
+
59
66
  simulateRequestScope(() => {
60
- core.config.setValue('assess.max_context_source_events', 10);
61
- core.scopes.sources.getStore().assess.sourceEventsCount = 11;
62
- expect(core.assess.getSourceContext(SOURCE)).to.be.null;
63
- });
67
+ expect(core.assess.getPropagatorContext()).to.be.null;
68
+ }, { assess: { propagationEventsCount: 11 } });
64
69
  });
65
70
  });
66
71
 
67
- describe('.getSourceContext(PROPAGATOR?)', function() {
68
- it('returns assess store when max propagation event threshold is not met', function() {
72
+ describe('getSinkContext()', function() {
73
+ it('returns null when instrumentation is locked', function () {
74
+ sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
75
+
69
76
  simulateRequestScope(() => {
70
- execStoreAssertions(core.assess.getSourceContext(SOURCE));
77
+ expect(core.assess.getSinkContext()).to.be.null;
71
78
  });
72
79
  });
73
80
 
74
- it('returns `null` when max propagation event threshold is exceeded', function() {
81
+ it('returns null when not in request scope', function() {
82
+ expect(core.assess.getSinkContext()).to.be.null;
83
+ });
84
+
85
+ it('returns null when assess is disabled by policy', function() {
75
86
  simulateRequestScope(() => {
76
- core.config.setValue('assess.max_propagation_events', 10);
77
- core.scopes.sources.getStore().assess.propagationEventsCount = 11;
78
- expect(core.assess.getSourceContext(PROPAGATOR)).to.be.null;
79
- });
87
+ expect(core.assess.getSinkContext()).to.be.null;
88
+ expect(core.logger.trace).to.have.been.calledWith('Assess intentionally disabled for this request');
89
+ }, { assess: { policy: undefined } });
80
90
  });
81
- });
82
91
 
83
- describe('.getSourceContext(SINK?, ruleId?)', function() {
84
92
  it('returns assess store when ruleId is not passed', function() {
85
93
  simulateRequestScope(() => {
86
- execStoreAssertions(core.assess.getSourceContext(RULE));
94
+ execStoreAssertions(core.assess.getSourceContext());
87
95
  });
88
96
  });
89
97
 
90
98
  it('returns assess store when ruleId provided is enabled in the policy', function() {
91
99
  simulateRequestScope(() => {
92
- execStoreAssertions(core.assess.getSourceContext(RULE, 'reflected-xss'));
100
+ execStoreAssertions(core.assess.getSinkContext('reflected-xss'));
93
101
  });
94
102
  });
95
103
 
96
- it('returns `null` when ruleId provided is not enabled in the policy', function() {
104
+ it('returns null when ruleId provided is not enabled in the policy', function() {
97
105
  core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
98
106
  assess: {
99
107
  ['reflected-xss']: { enable: false },
@@ -101,8 +109,53 @@ describe('assess getSourceContext', function () {
101
109
  });
102
110
 
103
111
  simulateRequestScope(() => {
104
- expect(core.assess.getSourceContext(RULE, 'reflected-xss')).to.be.null;
112
+ expect(core.assess.getSinkContext('reflected-xss')).to.be.null;
105
113
  });
106
114
  });
107
115
  });
116
+
117
+ describe('getSourceContext()', function() {
118
+ it('returns null when instrumentation is locked', function () {
119
+ sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
120
+
121
+ simulateRequestScope(() => {
122
+ expect(core.assess.getSourceContext()).to.be.null;
123
+ });
124
+ });
125
+
126
+ it('returns null when not in request scope', function() {
127
+ expect(core.assess.getSourceContext()).to.be.null;
128
+ expect(core.logger.warn).to.have.been.calledWithMatch(
129
+ sinon.match.object,
130
+ 'assess running outside of request scope',
131
+ );
132
+ });
133
+
134
+ it('returns null when assess is disabled by policy', function() {
135
+ simulateRequestScope(() => {
136
+ expect(core.assess.getSourceContext()).to.be.null;
137
+ expect(core.logger.trace).to.have.been.calledWith('Assess intentionally disabled for this request');
138
+ }, { assess: { policy: undefined } });
139
+ });
140
+
141
+ it('returns assess store when in request scope', function() {
142
+ simulateRequestScope(() => {
143
+ execStoreAssertions(core.assess.getSourceContext());
144
+ });
145
+ });
146
+
147
+ it('returns assess store when max source event threshold is not met', function() {
148
+ simulateRequestScope(() => {
149
+ execStoreAssertions(core.assess.getSourceContext());
150
+ });
151
+ });
152
+
153
+ it('returns null when max source event threshold is exceeded', function() {
154
+ core.config.setValue('assess.max_context_source_events', 10);
155
+
156
+ simulateRequestScope(() => {
157
+ expect(core.assess.getSourceContext()).to.be.null;
158
+ }, { assess: { sourceEventsCount: 11 } });
159
+ });
160
+ });
108
161
  });
package/lib/index.d.ts CHANGED
@@ -37,12 +37,6 @@ export interface Core extends _Core {
37
37
  metrics: any;
38
38
  }
39
39
 
40
- export enum InstrumentationType {
41
- SOURCE = 'source',
42
- PROPAGATOR = 'propagator',
43
- RULE = 'rule',
44
- }
45
-
46
40
  export interface SourceContext {
47
41
  reqData: object,
48
42
  responseData: {
@@ -74,14 +68,14 @@ export interface RuleState {
74
68
 
75
69
  export interface Assess {
76
70
  getPolicy(): Policy,
77
- getSourceContext(instrType?: InstrumentationType, opts?: any): SourceContext,
71
+ getPropagatorContext(): SourceContext | null,
72
+ getSinkContext(ruleId: Rule): SourceContext | null,
73
+ getSourceContext(): SourceContext | null,
78
74
  makeSourceContext(req: IncomingMessage, res: ServerResponse): SourceContext,
79
75
  ruleScopes: RuleScopes,
80
76
  ruleState: RuleState,
81
77
  }
82
78
 
83
- export function getSourceContext(instrType?: InstrumentationType, ops?: any): SourceContext;
84
-
85
79
  declare function init(core: Core): Assess;
86
80
 
87
81
  export = init;
@@ -89,7 +89,7 @@ module.exports = function(core) {
89
89
  }
90
90
 
91
91
  http.install = function() {
92
- depHooks.resolve({ name: 'http' }, (http) => {
92
+ depHooks.resolve({ name: 'http', version: '*' }, (http) => {
93
93
  {
94
94
  const name = 'http.ServerResponse.prototype.write';
95
95
  patcher.patch(http.ServerResponse.prototype, 'write', {
@@ -116,7 +116,7 @@ module.exports = function(core) {
116
116
  }
117
117
  });
118
118
 
119
- depHooks.resolve({ name: 'http2' }, (http2) => {
119
+ depHooks.resolve({ name: 'http2', version: '*' }, (http2) => {
120
120
  // todo: NODE-3467
121
121
  {
122
122
  const name = 'http2.Http2ServerResponse.prototype.write';
@@ -147,7 +147,7 @@ module.exports = function(core) {
147
147
  }
148
148
  });
149
149
 
150
- depHooks.resolve({ name: 'spdy', file: 'lib/spdy/response.js' }, (response) => {
150
+ depHooks.resolve({ name: 'spdy', version: '<5', file: 'lib/spdy/response.js' }, (response) => {
151
151
  patcher.patch(response, 'end', {
152
152
  name: 'spdy.response.end',
153
153
  patchType: 'test',
@@ -95,10 +95,10 @@ describe('assess response scanning sinks http', function () {
95
95
 
96
96
  http = require('./http')(core);
97
97
  core.depHooks.resolve
98
- .withArgs({ name: 'http' })
98
+ .withArgs(sinon.match({ name: 'http' }))
99
99
  .yields(httpModule);
100
100
  core.depHooks.resolve
101
- .withArgs({ name: 'http2' })
101
+ .withArgs(sinon.match({ name: 'http2' }))
102
102
  .yields(http2Module);
103
103
 
104
104
  http.install();
@@ -41,7 +41,7 @@ module.exports = function (core) {
41
41
  const expressSession = core.assess.sessionConfiguration.expressSession = {};
42
42
 
43
43
  expressSession.install = function () {
44
- return depHooks.resolve({ name: 'express-session' }, (session) => {
44
+ return depHooks.resolve({ name: 'express-session', version: '<2' }, (session) => {
45
45
  // Return the hooked function as the export.
46
46
  const hooked = patcher.patch(session, {
47
47
  name: 'express.hookedSessionConstructor',
@@ -16,9 +16,7 @@ describe('assess session-configuration http', function () {
16
16
 
17
17
  beforeEach(function () {
18
18
  ({ core, simulateRequestScope } = initAssessFixture());
19
- core.depHooks.resolve
20
- .withArgs({ name: 'express-session' })
21
- .yields(ExpressSession);
19
+ core.depHooks.resolve.yields(ExpressSession);
22
20
 
23
21
  sinon.spy(core.assess.sessionConfiguration, 'handleHttpOnly');
24
22
  sinon.spy(core.assess.sessionConfiguration, 'handleSecure');
@@ -40,7 +40,7 @@ module.exports = function (core) {
40
40
 
41
41
  return core.assess.sessionConfiguration.fastifyCookie = {
42
42
  install () {
43
- depHooks.resolve({ name: '@fastify/cookie' }, (_export) => {
43
+ depHooks.resolve({ name: '@fastify/cookie', version: '<12' }, (_export) => {
44
44
  const patched = patcher.patch(_export, {
45
45
  name: 'express.hookedSessionConstructor',
46
46
  patchType,
@@ -26,9 +26,7 @@ describe('assess session-configuration @fastify/cookie', function () {
26
26
  addHook: sinon.stub().yields({}, reply),
27
27
  };
28
28
 
29
- core.depHooks.resolve
30
- .withArgs({ name: '@fastify/cookie' })
31
- .yields(mockExport);
29
+ core.depHooks.resolve.yields(mockExport);
32
30
 
33
31
  sinon.stub(core.assess.sessionConfiguration, 'reportFindings');
34
32
  core.assess.sessionConfiguration.fastifyCookie.install();
@@ -39,7 +39,7 @@ module.exports = function (core) {
39
39
 
40
40
  return core.assess.sessionConfiguration.koa = {
41
41
  install () {
42
- depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
42
+ depHooks.resolve({ name: 'koa', version: '>=2.3.0 <3' }, (Koa) => {
43
43
  patcher.patch(Koa.prototype, 'use', {
44
44
  name: 'Koa.Application',
45
45
  patchType,
@@ -30,7 +30,7 @@ describe('assess sessionConfiguration Koa', function () {
30
30
  ctxMock.cookies = { set: sinon.stub() };
31
31
 
32
32
  core.depHooks.resolve
33
- .withArgs({ name: 'koa', version: '>=2.3.0' })
33
+ .withArgs({ name: 'koa', version: '>=2.3.0 <3' })
34
34
  .yields(koaMock);
35
35
 
36
36
  sinon.stub(core.assess.sessionConfiguration, 'reportFindings');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.40.0",
3
+ "version": "1.42.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,17 +17,17 @@
17
17
  "test": "../scripts/test.sh"
18
18
  },
19
19
  "dependencies": {
20
- "@contrast/common": "1.26.0",
21
- "@contrast/config": "1.36.0",
22
- "@contrast/core": "1.41.0",
23
- "@contrast/dep-hooks": "1.9.0",
20
+ "@contrast/common": "1.27.0",
21
+ "@contrast/config": "1.37.0",
22
+ "@contrast/core": "1.42.0",
23
+ "@contrast/dep-hooks": "1.11.0",
24
24
  "@contrast/distringuish": "^5.1.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",
25
+ "@contrast/instrumentation": "1.21.0",
26
+ "@contrast/logger": "1.15.0",
27
+ "@contrast/patcher": "1.14.0",
28
+ "@contrast/rewriter": "1.18.0",
29
+ "@contrast/route-coverage": "1.32.0",
30
+ "@contrast/scopes": "1.12.0",
31
31
  "semver": "^7.6.0"
32
32
  }
33
33
  }
package/lib/constants.js DELETED
@@ -1,26 +0,0 @@
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 InstrumentationType = {
19
- SOURCE: 'SOURCE',
20
- PROPAGATOR: 'PROPAGATOR',
21
- RULE: 'RULE',
22
- };
23
-
24
- module.exports = {
25
- InstrumentationType,
26
- };