@contrast/assess 1.41.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 (162) 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/join-and-resolve.js +3 -4
  53. package/lib/dataflow/propagation/install/path/normalize.js +4 -5
  54. package/lib/dataflow/propagation/install/path/parse.js +3 -4
  55. package/lib/dataflow/propagation/install/path/relative.js +4 -5
  56. package/lib/dataflow/propagation/install/path/toNamespacedPath.js +3 -4
  57. package/lib/dataflow/propagation/install/pug/index.js +3 -4
  58. package/lib/dataflow/propagation/install/pug-runtime-escape.js +3 -4
  59. package/lib/dataflow/propagation/install/querystring/escape.js +3 -4
  60. package/lib/dataflow/propagation/install/querystring/escape.test.js +1 -1
  61. package/lib/dataflow/propagation/install/querystring/parse.js +3 -4
  62. package/lib/dataflow/propagation/install/querystring/parse.test.js +1 -1
  63. package/lib/dataflow/propagation/install/querystring/stringify.js +3 -4
  64. package/lib/dataflow/propagation/install/querystring/stringify.test.js +1 -1
  65. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +2 -3
  66. package/lib/dataflow/propagation/install/send.js +3 -3
  67. package/lib/dataflow/propagation/install/sequelize/query-generator.js +3 -3
  68. package/lib/dataflow/propagation/install/sequelize/query-generator.test.js +2 -1
  69. package/lib/dataflow/propagation/install/sequelize/sql-string.js +5 -5
  70. package/lib/dataflow/propagation/install/sql-template-strings.js +3 -3
  71. package/lib/dataflow/propagation/install/string/concat.js +2 -3
  72. package/lib/dataflow/propagation/install/string/format-methods.js +2 -3
  73. package/lib/dataflow/propagation/install/string/html-methods.js +3 -4
  74. package/lib/dataflow/propagation/install/string/match-all.js +2 -3
  75. package/lib/dataflow/propagation/install/string/match.js +2 -3
  76. package/lib/dataflow/propagation/install/string/replace.js +2 -3
  77. package/lib/dataflow/propagation/install/string/slice.js +2 -3
  78. package/lib/dataflow/propagation/install/string/split.js +2 -3
  79. package/lib/dataflow/propagation/install/string/substring.js +2 -3
  80. package/lib/dataflow/propagation/install/string/trim.js +2 -3
  81. package/lib/dataflow/propagation/install/unescape.js +2 -3
  82. package/lib/dataflow/propagation/install/url/domain-parsers.js +3 -4
  83. package/lib/dataflow/propagation/install/url/parse.js +3 -4
  84. package/lib/dataflow/propagation/install/url/parse.test.js +2 -2
  85. package/lib/dataflow/propagation/install/url/searchParams.js +3 -4
  86. package/lib/dataflow/propagation/install/url/url.js +3 -4
  87. package/lib/dataflow/propagation/install/util-format.js +3 -4
  88. package/lib/dataflow/propagation/install/validator/hooks.js +9 -9
  89. package/lib/dataflow/sinks/install/child-process.js +5 -6
  90. package/lib/dataflow/sinks/install/eval.js +2 -3
  91. package/lib/dataflow/sinks/install/express/reflected-xss.js +2 -3
  92. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +2 -3
  93. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +3 -4
  94. package/lib/dataflow/sinks/install/fs.js +4 -5
  95. package/lib/dataflow/sinks/install/fs.test.js +2 -2
  96. package/lib/dataflow/sinks/install/function.js +2 -3
  97. package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.js +3 -4
  98. package/lib/dataflow/sinks/install/http/request.js +3 -4
  99. package/lib/dataflow/sinks/install/http/request.test.js +2 -2
  100. package/lib/dataflow/sinks/install/http/server-response.js +5 -6
  101. package/lib/dataflow/sinks/install/http/server-response.test.js +3 -3
  102. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +3 -4
  103. package/lib/dataflow/sinks/install/libxmljs.js +4 -5
  104. package/lib/dataflow/sinks/install/libxmljs.test.js +2 -2
  105. package/lib/dataflow/sinks/install/marsdb.js +3 -4
  106. package/lib/dataflow/sinks/install/marsdb.test.js +3 -3
  107. package/lib/dataflow/sinks/install/mongodb.js +3 -4
  108. package/lib/dataflow/sinks/install/mongodb.test.js +2 -6
  109. package/lib/dataflow/sinks/install/mssql.js +4 -5
  110. package/lib/dataflow/sinks/install/mssql.test.js +2 -2
  111. package/lib/dataflow/sinks/install/mysql.js +4 -5
  112. package/lib/dataflow/sinks/install/mysql.test.js +2 -11
  113. package/lib/dataflow/sinks/install/node-serialize.js +3 -4
  114. package/lib/dataflow/sinks/install/node-serialize.test.js +1 -3
  115. package/lib/dataflow/sinks/install/postgres.js +5 -6
  116. package/lib/dataflow/sinks/install/postgres.test.js +3 -9
  117. package/lib/dataflow/sinks/install/restify.js +3 -4
  118. package/lib/dataflow/sinks/install/restify.test.js +3 -5
  119. package/lib/dataflow/sinks/install/sequelize.js +3 -4
  120. package/lib/dataflow/sinks/install/sqlite3.js +3 -4
  121. package/lib/dataflow/sinks/install/vm.js +3 -4
  122. package/lib/dataflow/sources/install/body-parser1.js +2 -3
  123. package/lib/dataflow/sources/install/busboy.js +3 -4
  124. package/lib/dataflow/sources/install/busboy.test.js +2 -2
  125. package/lib/dataflow/sources/install/cookie-parser1.js +2 -3
  126. package/lib/dataflow/sources/install/express/params.js +1 -2
  127. package/lib/dataflow/sources/install/express/parsedUrl.js +1 -2
  128. package/lib/dataflow/sources/install/express/parsedUrl.test.js +9 -8
  129. package/lib/dataflow/sources/install/fastify/fastify.js +2 -3
  130. package/lib/dataflow/sources/install/fastify/fastify.test.js +3 -6
  131. package/lib/dataflow/sources/install/formidable1.js +2 -3
  132. package/lib/dataflow/sources/install/hapi/hapi.js +1 -2
  133. package/lib/dataflow/sources/install/http.js +2 -3
  134. package/lib/dataflow/sources/install/http.test.js +2 -2
  135. package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +3 -5
  136. package/lib/dataflow/sources/install/koa/koa-multer.js +3 -4
  137. package/lib/dataflow/sources/install/koa/koa-multer.test.js +1 -1
  138. package/lib/dataflow/sources/install/koa/koa-routers.js +3 -4
  139. package/lib/dataflow/sources/install/koa/koa2.js +2 -4
  140. package/lib/dataflow/sources/install/multer1.js +2 -3
  141. package/lib/dataflow/sources/install/multer1.test.js +1 -3
  142. package/lib/dataflow/sources/install/qs6.js +2 -3
  143. package/lib/dataflow/sources/install/querystring.js +2 -3
  144. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.js +2 -3
  145. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.test.js +1 -1
  146. package/lib/dataflow/sources/install/restify/jsonBodyParser.js +2 -3
  147. package/lib/dataflow/sources/install/restify/jsonBodyParser.test.js +1 -1
  148. package/lib/dataflow/sources/install/restify/router.js +2 -3
  149. package/lib/dataflow/sources/install/restify/router.test.js +1 -1
  150. package/lib/get-source-context.js +58 -39
  151. package/lib/get-source-context.test.js +103 -78
  152. package/lib/index.d.ts +3 -9
  153. package/lib/response-scanning/install/http.js +3 -3
  154. package/lib/response-scanning/install/http.test.js +2 -2
  155. package/lib/session-configuration/install/express-session.js +1 -1
  156. package/lib/session-configuration/install/express-session.test.js +1 -3
  157. package/lib/session-configuration/install/fastify-cookie.js +1 -1
  158. package/lib/session-configuration/install/fastify-cookie.test.js +1 -3
  159. package/lib/session-configuration/install/koa.js +1 -1
  160. package/lib/session-configuration/install/koa.test.js +1 -1
  161. package/package.json +11 -11
  162. package/lib/constants.js +0 -26
@@ -16,7 +16,6 @@
16
16
  'use strict';
17
17
 
18
18
  const { InputType: { BODY } } = 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,7 +32,7 @@ module.exports = function init(core) {
33
32
  return core.assess.dataflow.sources.restifyInstrumentation.fieldedTextBodyParser = {
34
33
  install() {
35
34
  depHooks.resolve(
36
- { name: 'restify', file: 'lib/plugins/fieldedTextBodyParser.js', version: '>=8' },
35
+ { name: 'restify', file: 'lib/plugins/fieldedTextBodyParser.js', version: '>=8 <12' },
37
36
  (fieldedTextBodyParser) => patcher.patch(fieldedTextBodyParser, {
38
37
  name: 'restify.plugins.fieldedTextBodyParser',
39
38
  patchType,
@@ -44,7 +43,7 @@ module.exports = function init(core) {
44
43
  pre(data) {
45
44
  const { args: [req, , next], name, funcKey } = data;
46
45
  data.args[2] = function contrastNext(...args) {
47
- const sourceContext = getSourceContext(SOURCE);
46
+ const sourceContext = getSourceContext();
48
47
 
49
48
  if (!sourceContext) return next(...args);
50
49
 
@@ -20,7 +20,7 @@ describe('assess dataflow sources restify fieldedTextBodyParser', function () {
20
20
  next = sinon.stub();
21
21
 
22
22
  core.depHooks.resolve
23
- .withArgs({ name: 'restify', file: 'lib/plugins/fieldedTextBodyParser.js', version: '>=8' })
23
+ .withArgs({ name: 'restify', file: 'lib/plugins/fieldedTextBodyParser.js', version: '>=8 <12' })
24
24
  .callsFake((_, cb) => {
25
25
  fieldedTextBodyParserStub = cb(fieldedTextBodyParserStub);
26
26
  });
@@ -16,7 +16,6 @@
16
16
  'use strict';
17
17
 
18
18
  const { InputType: { JSON_VALUE } } = 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,7 +32,7 @@ module.exports = function init(core) {
33
32
  return core.assess.dataflow.sources.restifyInstrumentation.jsonBodyParser = {
34
33
  install() {
35
34
  depHooks.resolve(
36
- { name: 'restify', file: 'lib/plugins/jsonBodyParser.js', version: '>=8' },
35
+ { name: 'restify', file: 'lib/plugins/jsonBodyParser.js', version: '>=8 <12' },
37
36
  (jsonBodyParser) => patcher.patch(jsonBodyParser, {
38
37
  name: 'restify.plugins.jsonBodyParser',
39
38
  patchType,
@@ -48,7 +47,7 @@ module.exports = function init(core) {
48
47
  const { args: [req, , next], name, funcKey } = data;
49
48
 
50
49
  data.args[2] = function contrastNext(...args) {
51
- const sourceContext = getSourceContext(SOURCE);
50
+ const sourceContext = getSourceContext();
52
51
 
53
52
  if (!sourceContext) {
54
53
  return next(...args);
@@ -23,7 +23,7 @@ describe('assess dataflow sources restify jsonBodyParser', function () {
23
23
  next = sinon.stub();
24
24
 
25
25
  core.depHooks.resolve
26
- .withArgs({ name: 'restify', file: 'lib/plugins/jsonBodyParser.js', version: '>=8' })
26
+ .withArgs({ name: 'restify', file: 'lib/plugins/jsonBodyParser.js', version: '>=8 <12' })
27
27
  .callsFake((_, cb) => {
28
28
  jsonBodyParserStub = cb(jsonBodyParserStub);
29
29
  });
@@ -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,13 +32,13 @@ 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
44
  return;
@@ -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();
@@ -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,63 +33,81 @@ module.exports = function(core) {
32
33
  } = core;
33
34
 
34
35
  /**
35
- * getSourceContext must be used by all instrumentation to make sure that the
36
- * - assess store is available
36
+ * Used by all propagator instrumentation to make sure that:
37
+ * - the assess store is available
37
38
  * - assess is enabled for this request
38
39
  * - instrumentation is not locked
39
40
  *
40
41
  * Any code that does not use this function and directly accesses the assess
41
42
  * source context will recurse until the stack is blown if the following
42
43
  * checks are not correctly made.
43
- *
44
- * @param {import('./constants.js').InstrumentationType} type
45
- * @returns {import('@contrast/assess').SourceContext|null} the assess store
44
+ * @returns {SourceContext | null}
46
45
  */
47
- return core.assess.getSourceContext = function getSourceContext(type, ...rest) {
48
- const ctx = sources.getStore()?.assess;
46
+ core.assess.getPropagatorContext = function getPropagatorContext() {
47
+ if (instrumentation.isLocked()) return null;
49
48
 
50
49
  // the following logging used to be done by the caller, but has been moved
51
50
  // here as opposed to overloading `ctx.policy` with a special value so the
52
51
  // caller could determine whether no source context was available or the
53
52
  // request is being intentionally excluded. A negative of this is that the
54
53
  // function name is not available to be included in the log.
55
- if (!ctx) {
56
- if (type === InstrumentationType.SOURCE) {
57
- // because this is a real error, and we don't have the function name
58
- // that the caller previously logged, we generate a stack trace to
59
- // capture that information.
60
- const err = new Error('No source context found');
61
- core.logger.warn({ err }, 'assess running outside of request scope');
62
- }
63
- return null;
64
- }
54
+ const ctx = sources.getStore()?.assess;
55
+ if (!ctx) return null;
56
+
65
57
  // there is a context, but if policy is null then assess is intentionally
66
58
  // disabled (i.e., url exclusion or the request is not sampled).
67
59
  if (!ctx.policy) {
68
60
  core.logger.trace('Assess intentionally disabled for this request');
69
61
  return null;
70
- } else if (instrumentation.isLocked()) {
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
+
76
+ const ctx = sources.getStore()?.assess;
77
+ if (!ctx) return null;
78
+
79
+ if (!ctx.policy) {
80
+ core.logger.trace('Assess intentionally disabled for this request');
81
+ return null;
82
+ }
83
+
84
+ if (ruleId && !ctx.policy?.enabledRules?.has?.(ruleId) || ruleScopes.isLocked(ruleId)) return null;
85
+
86
+ return ctx;
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');
71
100
  return null;
72
101
  }
73
102
 
74
- switch (type) {
75
- case InstrumentationType.PROPAGATOR: {
76
- if (ctx.propagationEventsCount > config.assess.max_propagation_events) return null;
77
- break;
78
- }
79
- case InstrumentationType.SOURCE: {
80
- if (ctx.sourceEventsCount > config.assess.max_context_source_events) return null;
81
- break;
82
- }
83
-
84
- case InstrumentationType.RULE: {
85
- const [ruleId] = rest;
86
- if (!ruleId) break;
87
- if (!ctx.policy?.enabledRules?.has?.(ruleId) || ruleScopes.isLocked(ruleId)) return null;
88
- break;
89
- }
103
+ if (!ctx.policy) {
104
+ core.logger.trace('Assess intentionally disabled for this request');
105
+ return null;
90
106
  }
91
107
 
108
+ if (ctx.sourceEventsCount > config.assess.max_context_source_events) return null;
109
+
92
110
  return ctx;
93
111
  };
112
+
94
113
  };
@@ -4,9 +4,28 @@ const { expect } = require('chai');
4
4
  const { Event } = require('@contrast/common');
5
5
  const { initAssessFixture } = require('@contrast/test/fixtures');
6
6
  const sinon = require('sinon');
7
- const {
8
- InstrumentationType: { SOURCE, PROPAGATOR, RULE }
9
- } = require('./constants');
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
+ }
10
29
 
11
30
  describe('assess getSourceContext', function () {
12
31
  let core, simulateRequestScope;
@@ -15,113 +34,74 @@ describe('assess getSourceContext', function () {
15
34
  ({ core, simulateRequestScope } = initAssessFixture());
16
35
  });
17
36
 
18
- function execStoreAssertions(assessStore) {
19
- expect(assessStore).to.be.an('object').and.deep.include({
20
- reqData: {
21
- ip: '127.0.0.1',
22
- httpVersion: '1.1',
23
- method: 'get',
24
- headers: {
25
- 'content-type': 'text/html',
26
- language: 'en',
27
- referer: 'http://fake.url.foo',
28
- },
29
- uriPath: '/index',
30
- queries: '_id=123',
31
- contentType: 'text/html'
32
- },
33
- responseData: {},
34
- sourceEventsCount: 0,
35
- propagationEventsCount: 0,
36
- });
37
- expect(assessStore.policy.enabledRules).to.have.length.greaterThan(5);
38
- }
39
-
40
- it('return null when not in request scope', function() {
41
- expect(core.assess.getSourceContext()).to.be.null;
42
- expect(core.logger.warn.callCount).equal(0);
43
- });
37
+ describe('getPropagatorContext()', function() {
38
+ it('returns null when instrumentation is locked', function () {
39
+ sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
44
40
 
45
- it('return null and log when getSourceContext(SOURCE) not in request scope', function() {
46
- expect(core.assess.getSourceContext(SOURCE)).to.be.null;
47
- expect(core.logger.warn.callCount).equal(1);
48
- expect(core.logger.warn.args[0][1]).to.include('outside of request scope');
49
- });
50
-
51
- it('return null and log when intentionally disabled (policy === null)', function() {
52
- simulateRequestScope(() => {
53
- core.scopes.sources.getStore().assess.policy = null;
54
- expect(core.assess.getSourceContext()).null;
55
- // lots of code writes trace logs
56
- const last = core.logger.trace.lastCall;
57
- expect(last.args[0]).to.include('Assess intentionally disabled');
41
+ simulateRequestScope(() => {
42
+ expect(core.assess.getPropagatorContext()).to.be.null;
43
+ });
58
44
  });
59
- });
60
45
 
61
- it('return null and do not log when instrumentation is locked', function() {
62
- simulateRequestScope(() => {
63
- core.scopes.instrumentation.isLocked = sinon.stub().returns(true);
64
- expect(core.assess.getSourceContext()).null;
65
- if (core.logger.trace.called) {
66
- expect(core.logger.trace.lastCall.args[0]).not.include('Assess intentionally disabled');
67
- }
46
+ it('returns null when not in request scope', function() {
47
+ expect(core.assess.getPropagatorContext()).to.be.null;
68
48
  });
69
- });
70
49
 
71
- describe('getSourceContext()', function() {
72
- it('returns assess store when in request scope', function() {
50
+ it('returns null when assess is disabled by policy', function() {
73
51
  simulateRequestScope(() => {
74
- execStoreAssertions(core.assess.getSourceContext());
75
- });
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 } });
76
55
  });
77
- });
78
56
 
79
- describe('.getSourceContext(SOURCE)', function() {
80
- 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() {
81
58
  simulateRequestScope(() => {
82
- execStoreAssertions(core.assess.getSourceContext(SOURCE));
59
+ execStoreAssertions(core.assess.getPropagatorContext());
83
60
  });
84
61
  });
85
62
 
86
- 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
+
87
66
  simulateRequestScope(() => {
88
- core.config.setValue('assess.max_context_source_events', 10);
89
- core.scopes.sources.getStore().assess.sourceEventsCount = 11;
90
- expect(core.assess.getSourceContext(SOURCE)).to.be.null;
91
- });
67
+ expect(core.assess.getPropagatorContext()).to.be.null;
68
+ }, { assess: { propagationEventsCount: 11 } });
92
69
  });
93
70
  });
94
71
 
95
- describe('.getSourceContext(PROPAGATOR)', function() {
96
- 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
+
97
76
  simulateRequestScope(() => {
98
- execStoreAssertions(core.assess.getSourceContext(PROPAGATOR));
77
+ expect(core.assess.getSinkContext()).to.be.null;
99
78
  });
100
79
  });
101
80
 
102
- 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() {
103
86
  simulateRequestScope(() => {
104
- core.config.setValue('assess.max_propagation_events', 10);
105
- core.scopes.sources.getStore().assess.propagationEventsCount = 11;
106
- expect(core.assess.getSourceContext(PROPAGATOR)).to.be.null;
107
- });
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 } });
108
90
  });
109
- });
110
91
 
111
- describe('.getSourceContext(RULE, ruleId?)', function() {
112
92
  it('returns assess store when ruleId is not passed', function() {
113
93
  simulateRequestScope(() => {
114
- execStoreAssertions(core.assess.getSourceContext(RULE));
94
+ execStoreAssertions(core.assess.getSourceContext());
115
95
  });
116
96
  });
117
97
 
118
98
  it('returns assess store when ruleId provided is enabled in the policy', function() {
119
99
  simulateRequestScope(() => {
120
- execStoreAssertions(core.assess.getSourceContext(RULE, 'reflected-xss'));
100
+ execStoreAssertions(core.assess.getSinkContext('reflected-xss'));
121
101
  });
122
102
  });
123
103
 
124
- 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() {
125
105
  core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
126
106
  assess: {
127
107
  ['reflected-xss']: { enable: false },
@@ -129,8 +109,53 @@ describe('assess getSourceContext', function () {
129
109
  });
130
110
 
131
111
  simulateRequestScope(() => {
132
- expect(core.assess.getSourceContext(RULE, 'reflected-xss')).to.be.null;
112
+ expect(core.assess.getSinkContext('reflected-xss')).to.be.null;
133
113
  });
134
114
  });
135
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
+ });
136
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.41.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.1",
23
- "@contrast/dep-hooks": "1.10.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.20.0",
26
- "@contrast/logger": "1.14.0",
27
- "@contrast/patcher": "1.13.0",
28
- "@contrast/rewriter": "1.17.1",
29
- "@contrast/route-coverage": "1.31.0",
30
- "@contrast/scopes": "1.11.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
  }