@contrast/agent-bundle 5.45.1 → 5.47.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 (189) hide show
  1. package/README.md +1 -1
  2. package/node_modules/@contrast/agent/README.md +1 -1
  3. package/node_modules/@contrast/agent/package.json +12 -12
  4. package/node_modules/@contrast/agentify/package.json +15 -15
  5. package/node_modules/@contrast/architecture-components/package.json +5 -5
  6. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/common.js +1 -1
  7. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/handlers.js +24 -11
  8. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/index.js +6 -4
  9. package/node_modules/@contrast/assess/lib/configuration-analysis/install/apollo-server.js +92 -0
  10. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/install/express-session.js +2 -2
  11. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/install/fastify-cookie.js +2 -2
  12. package/node_modules/@contrast/assess/lib/configuration-analysis/install/graphql-yoga.js +90 -0
  13. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/install/hapi.js +2 -2
  14. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/install/koa.js +3 -3
  15. package/node_modules/@contrast/assess/lib/dataflow/propagation/install/string/substring.js +1 -1
  16. package/node_modules/@contrast/assess/lib/dataflow/sources/handler.js +30 -26
  17. package/node_modules/@contrast/assess/lib/dataflow/sources/index.js +2 -0
  18. package/node_modules/@contrast/assess/lib/dataflow/sources/install/fastify-websocket.js +63 -0
  19. package/node_modules/@contrast/assess/lib/dataflow/sources/install/http.js +42 -38
  20. package/node_modules/@contrast/assess/lib/dataflow/sources/install/koa/index.js +1 -1
  21. package/node_modules/@contrast/assess/lib/dataflow/sources/install/koa/koa-bodyparsers.js +76 -48
  22. package/node_modules/@contrast/assess/lib/dataflow/sources/install/koa/koa-multer.js +1 -1
  23. package/node_modules/@contrast/assess/lib/dataflow/sources/install/koa/koa-routers.js +2 -2
  24. package/node_modules/@contrast/assess/lib/dataflow/sources/install/koa/{koa2.js → koa.js} +3 -3
  25. package/node_modules/@contrast/assess/lib/dataflow/sources/install/socket.io.js +80 -0
  26. package/node_modules/@contrast/assess/lib/get-source-context.js +10 -21
  27. package/node_modules/@contrast/assess/lib/index.d.ts +4 -3
  28. package/node_modules/@contrast/assess/lib/index.js +2 -2
  29. package/node_modules/@contrast/assess/lib/make-source-context.js +5 -10
  30. package/node_modules/@contrast/assess/lib/policy.js +400 -0
  31. package/node_modules/@contrast/assess/lib/response-scanning/handlers/index.js +10 -14
  32. package/node_modules/@contrast/assess/package.json +12 -12
  33. package/node_modules/@contrast/common/lib/constants.d.ts +12 -4
  34. package/node_modules/@contrast/common/lib/constants.js +16 -7
  35. package/node_modules/@contrast/common/lib/types.d.ts +5 -1
  36. package/node_modules/@contrast/common/package.json +1 -1
  37. package/node_modules/@contrast/config/lib/common.js +1 -0
  38. package/node_modules/@contrast/config/lib/options.js +14 -0
  39. package/node_modules/@contrast/config/package.json +3 -3
  40. package/node_modules/@contrast/core/package.json +5 -5
  41. package/node_modules/@contrast/deadzones/package.json +5 -5
  42. package/node_modules/@contrast/dep-hooks/lib/package-finder.d.ts +2 -2
  43. package/node_modules/@contrast/dep-hooks/lib/package-finder.js +3 -2
  44. package/node_modules/@contrast/dep-hooks/package.json +4 -4
  45. package/node_modules/@contrast/esm-hooks/README.md +2 -2
  46. package/node_modules/@contrast/esm-hooks/package.json +6 -6
  47. package/node_modules/@contrast/instrumentation/package.json +5 -5
  48. package/node_modules/@contrast/library-analysis/lib/install/library-reporting/dep.json +149 -149
  49. package/node_modules/@contrast/library-analysis/lib/install/library-reporting/index.js +2 -11
  50. package/node_modules/@contrast/library-analysis/lib/install/library-reporting/utils.js +2 -0
  51. package/node_modules/@contrast/library-analysis/lib/install/library-usage/index.js +3 -1
  52. package/node_modules/@contrast/library-analysis/lib/util.js +0 -2
  53. package/node_modules/@contrast/library-analysis/package.json +4 -4
  54. package/node_modules/@contrast/logger/package.json +3 -3
  55. package/node_modules/@contrast/metrics/package.json +6 -6
  56. package/node_modules/@contrast/patcher/package.json +2 -2
  57. package/node_modules/@contrast/protect/lib/error-handlers/index.js +1 -1
  58. package/node_modules/@contrast/protect/lib/error-handlers/install/{koa2.js → koa.js} +4 -4
  59. package/node_modules/@contrast/protect/lib/index.d.ts +1 -1
  60. package/node_modules/@contrast/protect/lib/input-analysis/handlers.js +1 -12
  61. package/node_modules/@contrast/protect/lib/input-analysis/index.js +2 -3
  62. package/node_modules/@contrast/protect/lib/input-analysis/install/koa-bodyparsers.js +92 -0
  63. package/node_modules/@contrast/protect/lib/input-analysis/install/{koa2.js → koa.js} +5 -5
  64. package/node_modules/@contrast/protect/package.json +11 -11
  65. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/application-activity/translations.js +6 -10
  66. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/routes-observed.js +4 -0
  67. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/traces/index.d.ts +1 -1
  68. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/traces/index.js +1 -1
  69. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/traces/translations.d.ts +1 -1
  70. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/traces/translations.js +22 -9
  71. package/node_modules/@contrast/reporter/lib/reporters/file.js +1 -1
  72. package/node_modules/@contrast/reporter/package.json +6 -6
  73. package/node_modules/@contrast/rewriter/package.json +5 -5
  74. package/node_modules/@contrast/route-coverage/lib/index.d.ts +0 -2
  75. package/node_modules/@contrast/route-coverage/lib/index.js +10 -1
  76. package/node_modules/@contrast/route-coverage/lib/install/express/express5.js +16 -1
  77. package/node_modules/@contrast/route-coverage/lib/install/fastify.js +25 -15
  78. package/node_modules/@contrast/route-coverage/lib/install/graphql.js +6 -1
  79. package/node_modules/@contrast/route-coverage/lib/install/koa.js +1 -1
  80. package/node_modules/@contrast/route-coverage/lib/install/socket.io.js +127 -0
  81. package/node_modules/@contrast/route-coverage/package.json +8 -8
  82. package/node_modules/@contrast/scopes/package.json +5 -5
  83. package/node_modules/@contrast/sec-obs/package.json +9 -9
  84. package/node_modules/@contrast/sources/lib/index.js +65 -22
  85. package/node_modules/@contrast/sources/lib/index.test.js +78 -33
  86. package/node_modules/@contrast/sources/lib/source-info.js +1 -10
  87. package/node_modules/@contrast/sources/package.json +3 -3
  88. package/node_modules/@contrast/telemetry/package.json +5 -5
  89. package/node_modules/@types/node/README.md +1 -1
  90. package/node_modules/@types/node/assert/strict.d.ts +105 -2
  91. package/node_modules/@types/node/assert.d.ts +154 -95
  92. package/node_modules/@types/node/buffer.buffer.d.ts +9 -0
  93. package/node_modules/@types/node/buffer.d.ts +8 -4
  94. package/node_modules/@types/node/child_process.d.ts +65 -42
  95. package/node_modules/@types/node/cluster.d.ts +4 -5
  96. package/node_modules/@types/node/crypto.d.ts +1173 -322
  97. package/node_modules/@types/node/dgram.d.ts +9 -8
  98. package/node_modules/@types/node/diagnostics_channel.d.ts +0 -2
  99. package/node_modules/@types/node/dns.d.ts +1 -1
  100. package/node_modules/@types/node/events.d.ts +80 -34
  101. package/node_modules/@types/node/fs/promises.d.ts +39 -21
  102. package/node_modules/@types/node/fs.d.ts +328 -87
  103. package/node_modules/@types/node/globals.d.ts +2 -0
  104. package/node_modules/@types/node/globals.typedarray.d.ts +19 -0
  105. package/node_modules/@types/node/http.d.ts +94 -30
  106. package/node_modules/@types/node/http2.d.ts +178 -52
  107. package/node_modules/@types/node/https.d.ts +91 -62
  108. package/node_modules/@types/node/index.d.ts +2 -0
  109. package/node_modules/@types/node/inspector.d.ts +24 -0
  110. package/node_modules/@types/node/inspector.generated.d.ts +181 -0
  111. package/node_modules/@types/node/net.d.ts +12 -11
  112. package/node_modules/@types/node/os.d.ts +14 -3
  113. package/node_modules/@types/node/package.json +3 -3
  114. package/node_modules/@types/node/perf_hooks.d.ts +6 -8
  115. package/node_modules/@types/node/process.d.ts +12 -23
  116. package/node_modules/@types/node/readline/promises.d.ts +1 -1
  117. package/node_modules/@types/node/sea.d.ts +9 -0
  118. package/node_modules/@types/node/sqlite.d.ts +119 -10
  119. package/node_modules/@types/node/stream/consumers.d.ts +2 -2
  120. package/node_modules/@types/node/stream/web.d.ts +6 -55
  121. package/node_modules/@types/node/stream.d.ts +38 -23
  122. package/node_modules/@types/node/string_decoder.d.ts +2 -2
  123. package/node_modules/@types/node/test.d.ts +31 -26
  124. package/node_modules/@types/node/tls.d.ts +90 -66
  125. package/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +10 -2
  126. package/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +16 -0
  127. package/node_modules/@types/node/ts5.6/index.d.ts +2 -0
  128. package/node_modules/@types/node/ts5.7/index.d.ts +2 -0
  129. package/node_modules/@types/node/url.d.ts +8 -3
  130. package/node_modules/@types/node/util.d.ts +17 -3
  131. package/node_modules/@types/node/v8.d.ts +38 -5
  132. package/node_modules/@types/node/vm.d.ts +169 -88
  133. package/node_modules/@types/node/wasi.d.ts +1 -1
  134. package/node_modules/@types/node/web-globals/crypto.d.ts +32 -0
  135. package/node_modules/@types/node/web-globals/events.d.ts +3 -0
  136. package/node_modules/@types/node/web-globals/streams.d.ts +22 -0
  137. package/node_modules/@types/node/worker_threads.d.ts +109 -48
  138. package/node_modules/@types/node/zlib.d.ts +31 -24
  139. package/node_modules/axios/CHANGELOG.md +403 -357
  140. package/node_modules/axios/README.md +80 -49
  141. package/node_modules/axios/dist/axios.js +121 -46
  142. package/node_modules/axios/dist/axios.js.map +1 -1
  143. package/node_modules/axios/dist/axios.min.js +2 -2
  144. package/node_modules/axios/dist/axios.min.js.map +1 -1
  145. package/node_modules/axios/dist/browser/axios.cjs +126 -57
  146. package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
  147. package/node_modules/axios/dist/esm/axios.js +126 -57
  148. package/node_modules/axios/dist/esm/axios.js.map +1 -1
  149. package/node_modules/axios/dist/esm/axios.min.js +2 -2
  150. package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
  151. package/node_modules/axios/dist/node/axios.cjs +346 -97
  152. package/node_modules/axios/dist/node/axios.cjs.map +1 -1
  153. package/node_modules/axios/index.d.cts +4 -0
  154. package/node_modules/axios/index.d.ts +4 -0
  155. package/node_modules/axios/lib/adapters/adapters.js +85 -40
  156. package/node_modules/axios/lib/adapters/fetch.js +1 -1
  157. package/node_modules/axios/lib/adapters/http.js +220 -42
  158. package/node_modules/axios/lib/core/InterceptorManager.js +1 -1
  159. package/node_modules/axios/lib/core/mergeConfig.js +4 -4
  160. package/node_modules/axios/lib/env/data.js +1 -1
  161. package/node_modules/axios/lib/helpers/HttpStatusCode.js +6 -0
  162. package/node_modules/axios/lib/helpers/bind.js +7 -0
  163. package/node_modules/axios/lib/helpers/cookies.js +24 -13
  164. package/node_modules/axios/package.json +9 -4
  165. package/node_modules/detect-libc/lib/filesystem.js +1 -1
  166. package/node_modules/detect-libc/package.json +3 -1
  167. package/node_modules/node-abi/abi_registry.json +12 -2
  168. package/node_modules/node-abi/package.json +3 -6
  169. package/node_modules/semver/classes/range.js +1 -0
  170. package/node_modules/semver/classes/semver.js +19 -5
  171. package/node_modules/semver/internal/identifiers.js +4 -0
  172. package/node_modules/semver/package.json +3 -3
  173. package/node_modules/undici-types/agent.d.ts +1 -4
  174. package/node_modules/undici-types/client.d.ts +0 -2
  175. package/node_modules/undici-types/diagnostics-channel.d.ts +0 -1
  176. package/node_modules/undici-types/dispatcher.d.ts +0 -6
  177. package/node_modules/undici-types/errors.d.ts +5 -15
  178. package/node_modules/undici-types/eventsource.d.ts +6 -1
  179. package/node_modules/undici-types/h2c-client.d.ts +0 -2
  180. package/node_modules/undici-types/index.d.ts +6 -1
  181. package/node_modules/undici-types/interceptors.d.ts +5 -0
  182. package/node_modules/undici-types/mock-interceptor.d.ts +0 -1
  183. package/node_modules/undici-types/package.json +1 -1
  184. package/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  185. package/node_modules/undici-types/webidl.d.ts +82 -21
  186. package/package.json +3 -3
  187. package/node_modules/@contrast/assess/lib/get-policy.js +0 -336
  188. package/node_modules/@contrast/protect/lib/input-analysis/install/koa-body5.js +0 -63
  189. package/node_modules/@contrast/protect/lib/input-analysis/install/koa-bodyparser4.js +0 -64
@@ -0,0 +1,400 @@
1
+ /*
2
+ * Copyright: 2025 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 {
19
+ Event,
20
+ ExclusionType,
21
+ InputType,
22
+ Rule,
23
+ ResponseScanningRule,
24
+ ConfigurationRule,
25
+ set,
26
+ primordials: { ArrayPrototypeJoin, RegExpPrototypeTest }
27
+ } = require('@contrast/common');
28
+ const { Core } = require('@contrast/core/lib/ioc/core');
29
+
30
+ const ASSESS_RULES = Object.values({
31
+ ...Rule,
32
+ ...ResponseScanningRule,
33
+ ...ConfigurationRule,
34
+ });
35
+ const BROAD_INPUT_EXCLUSION_TYPES = [
36
+ ExclusionType.BODY,
37
+ ExclusionType.QUERYSTRING
38
+ ];
39
+ const NAMED_INPUT_EXCLUSION_TYPES = [
40
+ ExclusionType.COOKIE,
41
+ ExclusionType.HEADER,
42
+ ExclusionType.PARAMETER
43
+ ];
44
+ const BODY_TYPES = [
45
+ InputType.BODY,
46
+ InputType.JSON_VALUE,
47
+ InputType.JSON_ARRAYED_VALUE,
48
+ InputType.MULTIPART_CONTENT_TYPE,
49
+ InputType.MULTIPART_FIELD_NAME,
50
+ InputType.MULTIPART_NAME,
51
+ InputType.MULTIPART_VALUE,
52
+ ];
53
+ const COMPONENT_NAME = 'assess.policy';
54
+
55
+ class AssessPolicy {
56
+ /**
57
+ * @param {{
58
+ * config: import('@contrast/config').Config,
59
+ * logger: import('@contrast/logger').Logger,
60
+ * messages: import('@contrast/common').Messages,
61
+ * }} core
62
+ */
63
+ constructor(core) {
64
+ Object.defineProperty(this, 'core', { value: core });
65
+
66
+ this.version = Date.now();
67
+ this.disabledRules = new Set(core.config.getEffectiveValue('assess.rules.disabled_rules'));
68
+ this.exclusionMap = new Map([
69
+ [ExclusionType.BODY, []],
70
+ [ExclusionType.COOKIE, []],
71
+ [ExclusionType.HEADER, []],
72
+ [ExclusionType.PARAMETER, []],
73
+ [ExclusionType.QUERYSTRING, []],
74
+ [ExclusionType.URL, []],
75
+ ]);
76
+
77
+ core.messages.on(Event.SERVER_SETTINGS_UPDATE, (msg) => {
78
+ if (!msg.assess && !msg.exclusions) return;
79
+
80
+ this.version = Date.now();
81
+
82
+ if (msg.assess) {
83
+ const enabledRules = new Set();
84
+ this.disabledRules = new Set(core.config.getEffectiveValue('assess.rules.disabled_rules'));
85
+
86
+ for (const ruleId of ASSESS_RULES) {
87
+ const enable = msg.assess[ruleId]?.enable;
88
+ if (enable === false) {
89
+ this.disabledRules.add(ruleId);
90
+ // map to "sub-rules"
91
+ if (ruleId === Rule.NOSQL_INJECTION) this.disabledRules.add(Rule.NOSQL_INJECTION_MONGO);
92
+ } else if (enable === true) {
93
+ enabledRules.add(ruleId);
94
+ if (ruleId === Rule.NOSQL_INJECTION) enabledRules.add(Rule.NOSQL_INJECTION_MONGO);
95
+ }
96
+ }
97
+ this.core.logger.info({
98
+ enabledRules,
99
+ disabledRules: Array.from(this.disabledRules)
100
+ }, 'Assess policy rules updated');
101
+ }
102
+
103
+ if (msg.exclusions) {
104
+ for (const arr of this.exclusionMap.values()) arr.length = 0;
105
+
106
+ const rawDtmList = [
107
+ ...(msg?.exclusions?.input || []),
108
+ ...(msg?.exclusions?.url || []),
109
+ ].filter((exclusion) => exclusion?.modes?.includes?.('assess'));
110
+
111
+ // reset global exclusion state
112
+ for (const type of Object.values(ExclusionType)) {
113
+ this.exclusionMap.get(type).length = 0;
114
+ }
115
+
116
+ if (!rawDtmList.length) return;
117
+
118
+ for (const dtm of rawDtmList) {
119
+ // normalize different dtm types
120
+ dtm.type = dtm.type || 'URL';
121
+ const { type } = dtm;
122
+ const key = ExclusionType[type];
123
+ // defensive code against unanticipated DTM values
124
+ if (key) {
125
+ const Ctor = dtm.type === ExclusionType.URL ? UrlExclusion : InputExclusion;
126
+ this.exclusionMap.get(dtm.type).push(new Ctor(dtm));
127
+ }
128
+ }
129
+
130
+ this.core.logger.info({
131
+ exclusions: Object.fromEntries(this.exclusionMap)
132
+ }, 'Assess exclusions updated (%s total)', rawDtmList.length);
133
+ }
134
+ });
135
+ }
136
+
137
+ getRequestPolicy(sourceInfo) {
138
+ return new RequestPolicy(this.core, sourceInfo);
139
+ }
140
+ }
141
+
142
+ class RequestPolicy {
143
+ /**
144
+ * @param {{
145
+ * config: import('@contrast/config').Config,
146
+ * logger: import('@contrast/logger').Logger,
147
+ * messages: import('@contrast/common').Messages,
148
+ * }} core
149
+ * @param {import('@contrast/common').SourceInfo} sourceInfo
150
+ */
151
+ constructor(core, sourceInfo) {
152
+ Object.defineProperty(this, 'core', { value: core });
153
+ this.sourceInfo = sourceInfo;
154
+ this.init();
155
+ }
156
+
157
+ /**
158
+ * Used to (re)initialize the instance's exclusions, reading from current assess global policy.
159
+ */
160
+ init() {
161
+ const { core, sourceInfo } = this;
162
+ this.allowed = false;
163
+ this.version = core.assess.policy.version;
164
+ this.exclusions = {};
165
+
166
+ if (!core.config.getEffectiveValue('assess.enable')) {
167
+ this.allowed = true;
168
+ return;
169
+ }
170
+
171
+ // Evaluate URL exclusions.
172
+ // If one matches and applies to all rules, we set `allowed: true` which will
173
+ // disable assess for the request (via getSourceContext()). If specific rules are
174
+ // disabled, we remove them from the request policy's set of enabled rules.
175
+ for (const urlExclusion of this.core.assess.policy.exclusionMap.get(ExclusionType.URL)) {
176
+ if (urlExclusion.matchesUriPath(sourceInfo.uriPath)) {
177
+ if (!urlExclusion.rules?.size) {
178
+ core.logger.debug({
179
+ name: urlExclusion.name
180
+ }, 'All Assess rules have been disabled by URL exclusion');
181
+ this.allowed = true;
182
+ // no need to further process exclusions - request will be ignored
183
+ return;
184
+ } else {
185
+ // build as needed
186
+ if (!this.exclusions.disabledRules) this.exclusions.disabledRules = new Set();
187
+
188
+ for (const ruleId of urlExclusion.rules) {
189
+ this.exclusions.disabledRules.add(ruleId);
190
+ }
191
+ core.logger.debug({
192
+ name: urlExclusion.name,
193
+ rules: Array.from(urlExclusion.rules),
194
+ }, 'Assess rules disabled by URL exclusion');
195
+ }
196
+ }
197
+ }
198
+
199
+ // Process input exclusions that apply broadly: BODY, QUERYSTRING
200
+ for (const type of BROAD_INPUT_EXCLUSION_TYPES) {
201
+ for (const exclusion of core.assess.policy.exclusionMap.get(type)) {
202
+ if (exclusion.matchesUriPath(sourceInfo.uriPath)) {
203
+ // build as needed
204
+ if (!this.exclusions[type]) this.exclusions[type] = { track: true, excludedRules: new Set() };
205
+ const inputPolicy = this.exclusions[type];
206
+
207
+ if (exclusion.rules.size) {
208
+ for (const ruleId of exclusion.rules) {
209
+ inputPolicy.excludedRules.add(ruleId);
210
+ }
211
+ } else {
212
+ inputPolicy.track = false;
213
+ inputPolicy.excludedRules.clear();
214
+ break;
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ for (const type of NAMED_INPUT_EXCLUSION_TYPES) {
221
+ for (const exclusion of core.assess.policy.exclusionMap.get(type)) {
222
+ if (exclusion.matchesUriPath(sourceInfo.uriPath)) {
223
+ if (!this.exclusions[type]) this.exclusions[type] = [];
224
+ this.exclusions[type].push(exclusion);
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Given input type and optional field name will give instructions on how
232
+ * to track based on global policy and various exclusions that may apply.
233
+ * @param {} inputType
234
+ * @param {} fieldName
235
+ * @returns {boolean|Set<string>} false - do not track
236
+ * true - track
237
+ * Set - track but add tags to exclude these rules
238
+ */
239
+ getInputPolicy(inputType, fieldName) {
240
+ if (this.version < this.core.assess.policy.version) this.init();
241
+ if (this.allowed) return false; // don't track - request ignored
242
+
243
+ let inputRuleExclusions;
244
+ let excludedRuleIds;
245
+
246
+ if (inputType === InputType.HEADER) {
247
+ inputRuleExclusions = this.exclusions[ExclusionType.HEADER];
248
+ } else if (inputType === InputType.QUERYSTRING) {
249
+ if (this.exclusions[ExclusionType.QUERYSTRING]?.track === false) {
250
+ return false;
251
+ } else {
252
+ if (this.exclusions[ExclusionType.QUERYSTRING]?.excludedRules)
253
+ excludedRuleIds = new Set(this.exclusions[ExclusionType.QUERYSTRING]?.excludedRules);
254
+ inputRuleExclusions = this.exclusions[ExclusionType.PARAMETER];
255
+ }
256
+ } else if (inputType === InputType.URL_PARAMETER) {
257
+ inputRuleExclusions = this.exclusions[ExclusionType.PARAMETER];
258
+ } else if ([
259
+ InputType.COOKIE_NAME,
260
+ InputType.COOKIE_VALUE
261
+ ].includes(inputType)) {
262
+ inputRuleExclusions = this.exclusions[ExclusionType.COOKIE];
263
+ } else if (BODY_TYPES.includes(inputType)) {
264
+ if (this.exclusions[ExclusionType.BODY]?.track === false) {
265
+ return false;
266
+ } else {
267
+ inputRuleExclusions = this.exclusions[ExclusionType.PARAMETER];
268
+ }
269
+ }
270
+
271
+ if (inputRuleExclusions) {
272
+ for (const exclusion of inputRuleExclusions) {
273
+ if (exclusion.matchesInputName(fieldName)) {
274
+ // disables some rules
275
+ if (exclusion.rules.size) {
276
+ for (const ruleId of exclusion.rules) {
277
+ if (!excludedRuleIds) excludedRuleIds = new Set();
278
+ excludedRuleIds.add(ruleId);
279
+ }
280
+ } else {
281
+ return false; // don't track - all rules disabled
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ if (this.exclusions.disabledRules || excludedRuleIds) {
288
+ // only URL Exclusions disabled these rules
289
+ if (!excludedRuleIds) return this.exclusions.disabledRules;
290
+ // only Input Exclusion disabled these
291
+ if (!this.exclusions.disabledRules) return excludedRuleIds;
292
+ // merge since URL Exclusions and Input Exclusions have disabled rules
293
+ return new Set([...this.exclusions.disabledRules, ...excludedRuleIds]);
294
+ }
295
+
296
+ return true;
297
+ }
298
+
299
+ isRuleEnabled(ruleId) {
300
+ if (this.version < this.core.assess.policy.version) this.init();
301
+
302
+ if (this.allowed) return false;
303
+
304
+ return (
305
+ !this.exclusions.disabledRules?.has?.(ruleId) &&
306
+ !this.core.assess.policy.disabledRules?.has?.(ruleId)
307
+ );
308
+ }
309
+ }
310
+
311
+ /**
312
+ * @typedef InputPolicy
313
+ * @property {boolean} track
314
+ * @property {Set<Rule>} excludedRules
315
+ */
316
+
317
+ class UrlExclusion {
318
+ constructor(dtm) {
319
+ this._urlRegex = null;
320
+ this._urls = new Set();
321
+ this.name = dtm.name;
322
+ this.type = ExclusionType[dtm.type];
323
+ this.rules = new Set(dtm.assess_rules);
324
+
325
+ if (dtm.urls.length) {
326
+ const regexSegments = [];
327
+ for (const url of dtm.urls) {
328
+ if (shouldBeRegExp(url)) {
329
+ regexSegments.push(url);
330
+ } else {
331
+ this._urls.add(url);
332
+ }
333
+ }
334
+ if (regexSegments.length) {
335
+ this._urlRegex = new RegExp(`^${ArrayPrototypeJoin.call(regexSegments, '|')}$`);
336
+ }
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Checks whether the current URI path matches any of the exclusion's URL values.
342
+ * Exclusions that don't match for the current request will not be enabled. The
343
+ * interpretation of the DTM is that if its urls list is empty, then that means
344
+ * it should match all requestss (can be the case for input exclusions).
345
+ * @param {string} uriPath uri to check
346
+ * @returns {boolean}
347
+ */
348
+ matchesUriPath(uriPath) {
349
+ return (!this._urlRegex && !this._urls.size) ||
350
+ this._urls.has(uriPath) ||
351
+ !!this._urlRegex?.test?.(uriPath);
352
+ }
353
+ }
354
+
355
+ class InputExclusion extends UrlExclusion {
356
+ constructor(dtm) {
357
+ super(dtm);
358
+ this._inputNameRegex = null;
359
+ this._inputName = null;
360
+
361
+ // dtm.name value is null for BODY and QUERYSTRING types
362
+ if (dtm.name) {
363
+ if (shouldBeRegExp(dtm.name)) {
364
+ this._inputNameRegex = new RegExp(`^${dtm.name}$`);
365
+ } else {
366
+ this._inputName = dtm.name;
367
+ }
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Checks if the provided name matches the value from the exclusion dtm.
373
+ * @param {string} name field name being evaluated
374
+ * @returns {boolean}
375
+ */
376
+ matchesInputName(name) {
377
+ // BODY and QUERYSTRING always match since they apply broadly
378
+ if (!this._inputName && !this._inputNameRegex) return true;
379
+ return this._inputNameRegex ? RegExpPrototypeTest.call(this._inputNameRegex, name) : this._inputName === name;
380
+ }
381
+ }
382
+
383
+ function shouldBeRegExp(str) {
384
+ return str.indexOf('*') > 0 ||
385
+ str.indexOf('.') > 0 ||
386
+ str.indexOf('+') > 0 ||
387
+ str.indexOf('?') > 0 ||
388
+ str.indexOf('\\') > 0;
389
+ }
390
+
391
+ module.exports = Core.makeComponent({
392
+ name: COMPONENT_NAME,
393
+ factory(core) {
394
+ const policy = new AssessPolicy(core);
395
+ set(core, COMPONENT_NAME, policy);
396
+ return policy;
397
+ },
398
+ });
399
+ module.exports.AssessPolicy = AssessPolicy;
400
+ module.exports.RequestPolicy = RequestPolicy;
@@ -60,7 +60,7 @@ module.exports = function(core) {
60
60
 
61
61
  responseScanning.handleAutoCompleteMissing = function(sourceContext, resHeaders, resBody) {
62
62
  if (
63
- !isEnabled(AUTOCOMPLETE_MISSING, sourceContext) ||
63
+ !sourceContext.policy?.isRuleEnabled(AUTOCOMPLETE_MISSING) ||
64
64
  !isHtmlContent(resHeaders)
65
65
  ) {
66
66
  return;
@@ -91,7 +91,7 @@ module.exports = function(core) {
91
91
 
92
92
  // de-dupe; this will be re-emitted for parseableBody handlers anyway
93
93
  if (
94
- !isEnabled(CACHE_CONTROLS_MISSING, sourceContext) ||
94
+ !sourceContext.policy?.isRuleEnabled(CACHE_CONTROLS_MISSING) ||
95
95
  (isParseableResponse(resHeaders) && !resBody)
96
96
  ) {
97
97
  return;
@@ -139,7 +139,7 @@ module.exports = function(core) {
139
139
  };
140
140
 
141
141
  responseScanning.handleClickJackingControlsMissing = function(sourceContext, resHeaders) {
142
- if (!isEnabled(CLICKJACKING_CONTROL_MISSING, sourceContext)) return;
142
+ if (!sourceContext.policy?.isRuleEnabled(CLICKJACKING_CONTROL_MISSING)) return;
143
143
 
144
144
  // look for x-frame-options headers with deny or sameorigin
145
145
  const xFrameHeaders = resHeaders['x-frame-options'];
@@ -158,7 +158,7 @@ module.exports = function(core) {
158
158
  };
159
159
 
160
160
  responseScanning.handleParameterPollution = function(sourceContext, resBody) {
161
- if (!isEnabled(PARAMETER_POLLUTION, sourceContext)) return;
161
+ if (!sourceContext.policy?.isRuleEnabled(PARAMETER_POLLUTION)) return;
162
162
 
163
163
  // look for form tag with missing action attribute.
164
164
  // ex: <form method="post">..
@@ -189,12 +189,12 @@ module.exports = function(core) {
189
189
  const cspHeaders = getCspHeaders(resHeaders);
190
190
 
191
191
  // Don't report if not set; this report belongs to 'csp-header-missing'
192
- if (!cspHeaders && isEnabled(CSP_HEADER_MISSING, sourceContext)) {
192
+ if (!cspHeaders && sourceContext.policy?.isRuleEnabled(CSP_HEADER_MISSING)) {
193
193
  reportFindings(sourceContext, { ruleId: ResponseScanningRule.CSP_HEADER_MISSING });
194
194
  return;
195
195
  }
196
196
 
197
- if (!isEnabled(CSP_HEADER_INSECURE, sourceContext)) return;
197
+ if (!sourceContext.policy?.isRuleEnabled(CSP_HEADER_INSECURE)) return;
198
198
 
199
199
  const vulnerabilityMetadata = checkCspSources(cspHeaders);
200
200
 
@@ -209,7 +209,7 @@ module.exports = function(core) {
209
209
  };
210
210
 
211
211
  responseScanning.handleHstsHeaderMissing = function(sourceContext, resHeaders) {
212
- if (!isEnabled(HSTS_HEADER_MISSING, sourceContext)) return;
212
+ if (!sourceContext?.policy?.isRuleEnabled(HSTS_HEADER_MISSING)) return;
213
213
 
214
214
  let header = resHeaders['strict-transport-security'];
215
215
  let maxAge;
@@ -241,7 +241,7 @@ module.exports = function(core) {
241
241
  };
242
242
 
243
243
  responseScanning.handleXContentTypeHeaderMissing = function(sourceContext, resHeaders) {
244
- if (!isEnabled(XCONTENTTYPE_HEADER_MISSING, sourceContext)) return;
244
+ if (!sourceContext.policy?.isRuleEnabled(XCONTENTTYPE_HEADER_MISSING)) return;
245
245
 
246
246
  const headerName = 'x-content-type-options';
247
247
  let header = resHeaders[headerName];
@@ -262,7 +262,7 @@ module.exports = function(core) {
262
262
  };
263
263
 
264
264
  responseScanning.handleXPoweredByHeader = function(sourceContext, resHeaders) {
265
- if (!isEnabled(X_POWERED_BY_HEADER, sourceContext)) return;
265
+ if (!sourceContext.policy?.isRuleEnabled(X_POWERED_BY_HEADER)) return;
266
266
 
267
267
  const headerName = 'x-powered-by';
268
268
  let header = resHeaders[headerName];
@@ -280,7 +280,7 @@ module.exports = function(core) {
280
280
  };
281
281
 
282
282
  responseScanning.handleXxsProtectionHeaderDisabled = function(sourceContext, responseHeaders) {
283
- if (!isEnabled(XXSPROTECTION_HEADER_DISABLED, sourceContext)) return;
283
+ if (!sourceContext?.policy?.isRuleEnabled(XXSPROTECTION_HEADER_DISABLED)) return;
284
284
 
285
285
  const header = responseHeaders['x-xss-protection'];
286
286
 
@@ -294,9 +294,5 @@ module.exports = function(core) {
294
294
  }
295
295
  };
296
296
 
297
- function isEnabled(ruleId, sourceContext) {
298
- return !!sourceContext?.policy?.enabledRules?.has?.(ruleId);
299
- }
300
-
301
297
  return responseScanning;
302
298
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.63.0",
3
+ "version": "1.65.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)",
@@ -20,18 +20,18 @@
20
20
  "test": "bash ../scripts/test.sh"
21
21
  },
22
22
  "dependencies": {
23
- "@contrast/common": "1.37.0",
24
- "@contrast/config": "1.52.1",
25
- "@contrast/core": "1.57.1",
26
- "@contrast/dep-hooks": "1.26.1",
23
+ "@contrast/common": "1.38.0",
24
+ "@contrast/config": "1.54.0",
25
+ "@contrast/core": "1.59.0",
26
+ "@contrast/dep-hooks": "1.28.0",
27
27
  "@contrast/distringuish": "^6.0.2",
28
- "@contrast/instrumentation": "1.36.1",
29
- "@contrast/logger": "1.30.1",
30
- "@contrast/patcher": "1.29.1",
31
- "@contrast/rewriter": "1.34.0",
32
- "@contrast/route-coverage": "1.49.1",
33
- "@contrast/scopes": "1.27.1",
34
- "@contrast/sources": "1.3.1",
28
+ "@contrast/instrumentation": "1.38.0",
29
+ "@contrast/logger": "1.32.0",
30
+ "@contrast/patcher": "1.31.0",
31
+ "@contrast/rewriter": "1.36.0",
32
+ "@contrast/route-coverage": "1.51.0",
33
+ "@contrast/scopes": "1.29.0",
34
+ "@contrast/sources": "1.5.0",
35
35
  "semver": "^7.6.0"
36
36
  }
37
37
  }
@@ -6,7 +6,7 @@ export declare enum Event {
6
6
  ASSESS_DATAFLOW_FINDING = "assess-dataflow-findings",
7
7
  ASSESS_DATAFLOW_SAFE_POSITIVE = "assess-dataflow-safe-positive",
8
8
  ASSESS_RESPONSE_SCANNING_FINDING = "assess-response-scanning-findings",
9
- ASSESS_SESSION_CONFIGURATION_FINDING = "assess-session-configuration-findings",
9
+ ASSESS_CONFIGURATION_FINDING = "assess-configuration-findings",
10
10
  ASSESS_CRYPTO_ANALYSIS_FINDING = "assess-crypto-analysis-finding",
11
11
  LIBRARY = "library",
12
12
  LIBRARY_USAGE = "library-usage",
@@ -60,9 +60,10 @@ export declare enum ResponseScanningRule {
60
60
  XCONTENTTYPE_HEADER_MISSING = "xcontenttype-header-missing",
61
61
  XXSPROTECTION_HEADER_DISABLED = "xxssprotection-header-disabled"
62
62
  }
63
- export declare enum SessionConfigurationRule {
63
+ export declare enum ConfigurationRule {
64
64
  HTTPONLY = "httponly",
65
- SECURE_FLAG_MISSING = "secure-flag-missing"
65
+ SECURE_FLAG_MISSING = "secure-flag-missing",
66
+ GRAPHQL_INTROSPECTION = "graphql-introspection"
66
67
  }
67
68
  export declare enum InputType {
68
69
  UNDEFINED_TYPE = "UNDEFINED_TYPE",
@@ -86,7 +87,8 @@ export declare enum InputType {
86
87
  METHOD = "METHOD",
87
88
  REQUEST = "REQUEST",
88
89
  URL_PARAMETER = "URL_PARAMETER",
89
- UNKNOWN = "UNKNOWN"
90
+ UNKNOWN = "UNKNOWN",
91
+ WEBSOCKET = "WEBSOCKET"
90
92
  }
91
93
  export declare enum ExclusionType {
92
94
  BODY = "BODY",
@@ -96,6 +98,12 @@ export declare enum ExclusionType {
96
98
  QUERYSTRING = "QUERYSTRING",
97
99
  URL = "URL"
98
100
  }
101
+ export declare enum RouteType {
102
+ HTTP = "HTTP",
103
+ MESSAGE_BROKER = "MESSAGE_BROKER",
104
+ MIDDLEWARE = "MIDDLEWARE",
105
+ RPC = "RPC"
106
+ }
99
107
  export declare enum DataflowTag {
100
108
  XML_ENCODED = "XML_ENCODED",
101
109
  XML_DECODED = "XML_DECODED",
@@ -14,7 +14,7 @@
14
14
  * way not consistent with the End User License Agreement.
15
15
  */
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.URI_REGEXES = exports.symbols = exports.agentLibIDListTypes = exports.FS_METHODS = exports.BLOCKING_MODES = exports.ServerEnvironment = exports.DataflowTag = exports.ExclusionType = exports.InputType = exports.SessionConfigurationRule = exports.ResponseScanningRule = exports.Rule = exports.ProtectRuleMode = exports.Event = void 0;
17
+ exports.URI_REGEXES = exports.symbols = exports.agentLibIDListTypes = exports.FS_METHODS = exports.BLOCKING_MODES = exports.ServerEnvironment = exports.DataflowTag = exports.RouteType = exports.ExclusionType = exports.InputType = exports.ConfigurationRule = exports.ResponseScanningRule = exports.Rule = exports.ProtectRuleMode = exports.Event = void 0;
18
18
  var Event;
19
19
  (function (Event) {
20
20
  // lifecycle
@@ -26,7 +26,7 @@ var Event;
26
26
  Event["ASSESS_DATAFLOW_FINDING"] = "assess-dataflow-findings";
27
27
  Event["ASSESS_DATAFLOW_SAFE_POSITIVE"] = "assess-dataflow-safe-positive";
28
28
  Event["ASSESS_RESPONSE_SCANNING_FINDING"] = "assess-response-scanning-findings";
29
- Event["ASSESS_SESSION_CONFIGURATION_FINDING"] = "assess-session-configuration-findings";
29
+ Event["ASSESS_CONFIGURATION_FINDING"] = "assess-configuration-findings";
30
30
  Event["ASSESS_CRYPTO_ANALYSIS_FINDING"] = "assess-crypto-analysis-finding";
31
31
  Event["LIBRARY"] = "library";
32
32
  Event["LIBRARY_USAGE"] = "library-usage";
@@ -85,11 +85,12 @@ var ResponseScanningRule;
85
85
  ResponseScanningRule["XCONTENTTYPE_HEADER_MISSING"] = "xcontenttype-header-missing";
86
86
  ResponseScanningRule["XXSPROTECTION_HEADER_DISABLED"] = "xxssprotection-header-disabled";
87
87
  })(ResponseScanningRule || (exports.ResponseScanningRule = ResponseScanningRule = {}));
88
- var SessionConfigurationRule;
89
- (function (SessionConfigurationRule) {
90
- SessionConfigurationRule["HTTPONLY"] = "httponly";
91
- SessionConfigurationRule["SECURE_FLAG_MISSING"] = "secure-flag-missing";
92
- })(SessionConfigurationRule || (exports.SessionConfigurationRule = SessionConfigurationRule = {}));
88
+ var ConfigurationRule;
89
+ (function (ConfigurationRule) {
90
+ ConfigurationRule["HTTPONLY"] = "httponly";
91
+ ConfigurationRule["SECURE_FLAG_MISSING"] = "secure-flag-missing";
92
+ ConfigurationRule["GRAPHQL_INTROSPECTION"] = "graphql-introspection";
93
+ })(ConfigurationRule || (exports.ConfigurationRule = ConfigurationRule = {}));
93
94
  var InputType;
94
95
  (function (InputType) {
95
96
  InputType["UNDEFINED_TYPE"] = "UNDEFINED_TYPE";
@@ -114,6 +115,7 @@ var InputType;
114
115
  InputType["REQUEST"] = "REQUEST";
115
116
  InputType["URL_PARAMETER"] = "URL_PARAMETER";
116
117
  InputType["UNKNOWN"] = "UNKNOWN";
118
+ InputType["WEBSOCKET"] = "WEBSOCKET";
117
119
  })(InputType || (exports.InputType = InputType = {}));
118
120
  var ExclusionType;
119
121
  (function (ExclusionType) {
@@ -124,6 +126,13 @@ var ExclusionType;
124
126
  ExclusionType["QUERYSTRING"] = "QUERYSTRING";
125
127
  ExclusionType["URL"] = "URL";
126
128
  })(ExclusionType || (exports.ExclusionType = ExclusionType = {}));
129
+ var RouteType;
130
+ (function (RouteType) {
131
+ RouteType["HTTP"] = "HTTP";
132
+ RouteType["MESSAGE_BROKER"] = "MESSAGE_BROKER";
133
+ RouteType["MIDDLEWARE"] = "MIDDLEWARE";
134
+ RouteType["RPC"] = "RPC";
135
+ })(RouteType || (exports.RouteType = RouteType = {}));
127
136
  var DataflowTag;
128
137
  (function (DataflowTag) {
129
138
  DataflowTag["XML_ENCODED"] = "XML_ENCODED";
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import { ServerResponse } from 'node:http';
3
- import { Event, ProtectRuleMode, Rule } from './constants';
3
+ import { Event, ProtectRuleMode, RouteType, Rule } from './constants';
4
4
  export interface Installable {
5
5
  install(...args: any[]): void | Promise<void>;
6
6
  uninstall?(): void | Promise<void>;
@@ -335,6 +335,10 @@ export interface RouteInfo {
335
335
  * @example "get"
336
336
  */
337
337
  method?: string;
338
+ /**
339
+ * The type of route that is being reported. Default should be RouteType.HTTP.
340
+ */
341
+ type: RouteType;
338
342
  /**
339
343
  * URL for a route.
340
344
  * @example "prefix/route/path"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/common",
3
- "version": "1.37.0",
3
+ "version": "1.38.0",
4
4
  "description": "Shared constants and utilities for all Contrast Agent modules",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -106,6 +106,7 @@ const mappings = {
106
106
  }
107
107
  },
108
108
  'assess.probabilistic_sampling.window_ms': (remoteData) => remoteData.assess?.sampling?.window_ms,
109
+ 'assess.report_middleware_routes': (remoteData) => remoteData.assess?.report_middleware_routes,
109
110
  'assess.stacktraces': (remoteData) => remoteData.assess?.report_stacktraces,
110
111
  'agent.logger.level': coerceLowerCase('logger.level'),
111
112
  'agent.logger.path': (remoteData) => remoteData.logger?.path,