@contrast/agent 4.8.0 → 4.10.1

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 (59) hide show
  1. package/bin/VERSION +1 -1
  2. package/bin/linux/contrast-service +0 -0
  3. package/bin/mac/contrast-service +0 -0
  4. package/bin/windows/contrast-service.exe +0 -0
  5. package/bootstrap.js +12 -2
  6. package/esm.mjs +33 -0
  7. package/lib/assess/index.js +2 -0
  8. package/lib/assess/models/source-event.js +6 -0
  9. package/lib/assess/policy/rules.json +29 -0
  10. package/lib/assess/policy/signatures.json +6 -0
  11. package/lib/assess/propagators/JSON/stringify.js +77 -7
  12. package/lib/assess/propagators/joi/any.js +48 -0
  13. package/lib/assess/propagators/joi/index.js +2 -0
  14. package/lib/assess/propagators/joi/object.js +61 -0
  15. package/lib/assess/propagators/joi/string-base.js +16 -0
  16. package/lib/assess/propagators/mongoose/helpers.js +36 -1
  17. package/lib/assess/propagators/mongoose/index.js +1 -0
  18. package/lib/assess/propagators/mongoose/map.js +19 -31
  19. package/lib/assess/propagators/mongoose/mixed.js +71 -0
  20. package/lib/assess/propagators/mongoose/string.js +8 -0
  21. package/lib/assess/sinks/rethinkdb-nosql-injection.js +142 -0
  22. package/lib/assess/sources/event-handler.js +307 -0
  23. package/lib/assess/sources/index.js +93 -5
  24. package/lib/assess/spdy/index.js +23 -0
  25. package/lib/assess/spdy/sinks/index.js +23 -0
  26. package/lib/assess/spdy/sinks/xss.js +84 -0
  27. package/lib/assess/technologies/index.js +2 -1
  28. package/lib/constants.js +2 -1
  29. package/lib/contrast.js +6 -6
  30. package/lib/core/arch-components/index.js +1 -0
  31. package/lib/core/arch-components/mongodb.js +22 -18
  32. package/lib/core/arch-components/mysql.js +25 -15
  33. package/lib/core/arch-components/postgres.js +40 -12
  34. package/lib/core/arch-components/sqlite3.js +3 -5
  35. package/lib/core/arch-components/util.js +49 -0
  36. package/lib/core/config/options.js +37 -1
  37. package/lib/core/exclusions/exclusion.js +2 -5
  38. package/lib/core/express/index.js +28 -2
  39. package/lib/core/express/utils.js +8 -3
  40. package/lib/core/fastify/index.js +2 -1
  41. package/lib/core/hapi/index.js +2 -1
  42. package/lib/core/koa/index.js +9 -1
  43. package/lib/core/rewrite/callees.js +16 -0
  44. package/lib/core/rewrite/import-declaration.js +71 -0
  45. package/lib/core/rewrite/index.js +9 -7
  46. package/lib/core/rewrite/injections.js +5 -1
  47. package/lib/hooks/frameworks/index.js +2 -0
  48. package/lib/hooks/frameworks/spdy.js +87 -0
  49. package/lib/hooks/http.js +11 -0
  50. package/lib/protect/restify/sources.js +35 -0
  51. package/lib/protect/rules/nosqli/nosql-injection-rule.js +30 -16
  52. package/lib/protect/rules/nosqli/nosql-scanner/index.js +1 -1
  53. package/lib/protect/rules/nosqli/nosql-scanner/rethinkdbscanner.js +26 -0
  54. package/lib/protect/sinks/index.js +2 -0
  55. package/lib/protect/sinks/mongodb.js +1 -3
  56. package/lib/protect/sinks/rethinkdb.js +47 -0
  57. package/lib/reporter/translations/to-protobuf/dtm/trace-event/index.js +4 -4
  58. package/lib/util/source-map.js +3 -3
  59. package/package.json +18 -12
@@ -0,0 +1,71 @@
1
+ /**
2
+ Copyright: 2022 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
+ 'use strict';
16
+
17
+ const tracker = require('../../../tracker');
18
+ const patcher = require('../../../hooks/patcher');
19
+ const requireHook = require('../../../hooks/require');
20
+ const tagRangeUtil = require('../../models/tag-range/util');
21
+ const {
22
+ PATCH_TYPES: { ASSESS_PROPAGATOR }
23
+ } = require('../../../constants');
24
+ const {
25
+ hasUserDefinedValidator,
26
+ tagCustomValidatedValues
27
+ } = require('./helpers');
28
+ const agent = require('../../../agent');
29
+
30
+ const doValidateSyncPatcher = (SchemaMap) => {
31
+ patcher.patch(SchemaMap.prototype, 'doValidateSync', {
32
+ alwaysRun: true,
33
+ name: 'mongoose.mixed.doValidateSync',
34
+ patchType: ASSESS_PROPAGATOR,
35
+ post(data) {
36
+ if (data.result || !hasUserDefinedValidator(data)) return;
37
+
38
+ const input = data.args[0];
39
+ const inputType = typeof input;
40
+
41
+ if (inputType !== 'string' && inputType !== 'object') return;
42
+
43
+ let values;
44
+ if (inputType === 'string') {
45
+ values = [input];
46
+ } else if (Array.isArray(input)) {
47
+ values = input;
48
+ } else if (input instanceof Map) {
49
+ values = input.values();
50
+ } else {
51
+ values = Object.values(input);
52
+ }
53
+
54
+ tagCustomValidatedValues(values, data, tracker, tagRangeUtil);
55
+ }
56
+ });
57
+ };
58
+
59
+ requireHook.resolve(
60
+ { name: 'mongoose', file: 'lib/schema/mixed.js', version: '>=5.0.0' },
61
+ (SchemaMap) => {
62
+ if (
63
+ !agent.config ||
64
+ (agent.config && !agent.config.agent.trust_custom_validators)
65
+ ) {
66
+ return;
67
+ }
68
+
69
+ doValidateSyncPatcher(SchemaMap);
70
+ }
71
+ );
@@ -24,6 +24,7 @@ const {
24
24
  const TagRange = require('../../models/tag-range');
25
25
  const { CallContext, PropagationEvent, Signature } = require('../../models');
26
26
  const { hasUserDefinedValidator } = require('./helpers');
27
+ const agent = require('../../../agent');
27
28
 
28
29
  const enumPatcher = (SchemaString) => {
29
30
  patcher.patch(SchemaString.prototype, 'enum', {
@@ -98,6 +99,13 @@ const doValidateSyncPatcher = (SchemaString) => {
98
99
  requireHook.resolve(
99
100
  { name: 'mongoose', file: 'lib/schema/string.js', version: '>=5.0.0' },
100
101
  (SchemaString) => {
102
+ if (
103
+ !agent.config ||
104
+ (agent.config && !agent.config.agent.trust_custom_validators)
105
+ ) {
106
+ return;
107
+ }
108
+
101
109
  enumPatcher(SchemaString);
102
110
  doValidateSyncPatcher(SchemaString);
103
111
  }
@@ -0,0 +1,142 @@
1
+ /**
2
+ Copyright: 2022 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
+ 'use strict';
16
+
17
+ const patcher = require('../../hooks/patcher');
18
+ const { PATCH_TYPES } = require('../../constants');
19
+ const moduleHook = require('../../hooks/require');
20
+ const { CallContext, Signature } = require('../models');
21
+ const {
22
+ RULES: { NOSQL_INJECTION }
23
+ } = require('../../constants');
24
+
25
+ const filterSignature = new Signature({
26
+ moduleName: 'rethinkdb.RDBVal',
27
+ methodName: 'filter',
28
+ isModule: true
29
+ });
30
+ const matchSignature = new Signature({
31
+ moduleName: 'rethinkdb.RDBVal',
32
+ methodName: 'match',
33
+ isModule: true
34
+ });
35
+ const moduleName = 'rethinkdb';
36
+
37
+ class Handler {
38
+ constructor({ report, isVulnerable, requiredTags }) {
39
+ this._isVulnerable = isVulnerable;
40
+ this.report = report;
41
+ this.requiredTags = requiredTags;
42
+ this.disallowedTags = [
43
+ 'alphanum-space-hyphen',
44
+ 'limited-chars',
45
+ 'custom-validated',
46
+ 'custom-encoded-nosql-injection',
47
+ 'custom-validated-nosql-injection'
48
+ ];
49
+ }
50
+
51
+ isVulnerable(input) {
52
+ const { requiredTags, disallowedTags } = this;
53
+ return this._isVulnerable({
54
+ input,
55
+ ruleId: NOSQL_INJECTION,
56
+ disallowedTags,
57
+ requiredTags,
58
+ searchDepth: 5
59
+ });
60
+ }
61
+
62
+ handle() {
63
+ moduleHook.resolve({ name: moduleName }, (rethinkdb) =>
64
+ this.handleRequire(rethinkdb)
65
+ );
66
+ }
67
+
68
+ patchRethinkDb(rethinkdb) {
69
+ const self = this;
70
+ patcher.patch(rethinkdb, 'table', {
71
+ name: moduleName,
72
+ patchType: PATCH_TYPES.ASSESS_SINK,
73
+ alwaysRun: true,
74
+ post(data) {
75
+ self.patchTableMethod(data.result);
76
+ }
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Patch the table method and through it gain access to the object
82
+ * prototype that holds the vulnerable `match` and `filter` methods
83
+ */
84
+ patchTableMethod(tableObject) {
85
+ const RDBValObject = Object.getPrototypeOf(tableObject.args[0]);
86
+ const TermBaseObject = Object.getPrototypeOf(RDBValObject);
87
+ const self = this;
88
+
89
+ patcher.patch(TermBaseObject, 'match', {
90
+ name: 'TermBase.prototype',
91
+ patchType: PATCH_TYPES.ASSESS_SINK,
92
+ alwaysRun: true,
93
+ post({ args: [str], hooked, obj }) {
94
+ if (self.isVulnerable(str)) {
95
+ self.report({
96
+ ruleId: NOSQL_INJECTION,
97
+ signature: matchSignature,
98
+ input: str,
99
+ ctxt: new CallContext({
100
+ obj,
101
+ args: [str],
102
+ result: str,
103
+ stackOpts: {
104
+ constructorOpt: hooked
105
+ }
106
+ })
107
+ });
108
+ }
109
+ }
110
+ });
111
+ patcher.patch(TermBaseObject, 'filter', {
112
+ name: 'TermBase.prototype',
113
+ patchType: PATCH_TYPES.ASSESS_SINK,
114
+ alwaysRun: true,
115
+ post({ args: [str], hooked, obj }) {
116
+ if (self.isVulnerable(str)) {
117
+ self.report({
118
+ ruleId: NOSQL_INJECTION,
119
+ signature: filterSignature,
120
+ input: str,
121
+ ctxt: new CallContext({
122
+ obj,
123
+ args: [str],
124
+ result: str,
125
+ stackOpts: {
126
+ constructorOpt: hooked
127
+ }
128
+ })
129
+ });
130
+ }
131
+ }
132
+ });
133
+ }
134
+
135
+ handleRequire(rethinkdb) {
136
+ this.patchRethinkDb(rethinkdb);
137
+ return rethinkdb;
138
+ }
139
+ }
140
+
141
+ module.exports = ({ common }) => new Handler(common);
142
+ module.exports.Handler = Handler;
@@ -0,0 +1,307 @@
1
+ /**
2
+ Copyright: 2022 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
+ 'use strict';
16
+
17
+ const logger = require('../../core/logger')('contrast:assess.sources');
18
+ const { PATCH_TYPES } = require('../../constants');
19
+ const { CallContext, SourceEvent } = require('../models');
20
+ const TraceEventSource = require('../../reporter/models/trace-event-source');
21
+ const { AsyncStorage, KEYS } = require('../../core/async-storage');
22
+ const TagRange = require('../models/tag-range');
23
+ const patcher = require('../../hooks/patcher');
24
+ const tracker = require('../../tracker');
25
+ const parseurl = require('parseurl');
26
+
27
+ const SOURCE_EVENT_MAX = 250;
28
+ const trackedObjects = new WeakMap();
29
+
30
+ class SourceEventHandler {
31
+ constructor({
32
+ config = {
33
+ agent: {
34
+ node: {
35
+ array_request_sampling: {
36
+ enable: true,
37
+ threshold: 50,
38
+ interval: 5
39
+ }
40
+ }
41
+ }
42
+ },
43
+ ensureReq = true,
44
+ exclusions,
45
+ stackOpts,
46
+ snapshot,
47
+ signature,
48
+ type = 'UNKNOWN'
49
+ } = {}) {
50
+ this.config = config;
51
+ this.exclusions = exclusions;
52
+ this.type = type;
53
+ this.snapshot = snapshot;
54
+ this.signature = signature;
55
+ this.stackOpts = stackOpts;
56
+ this.ensureReq = ensureReq;
57
+ this.samplingEnabled = config.agent.node.array_request_sampling.enable;
58
+ this.samplingThreshold = config.agent.node.array_request_sampling.threshold;
59
+ this.samplingInterval = this.samplingEnabled
60
+ ? config.agent.node.array_request_sampling.interval
61
+ : 1;
62
+
63
+ this.inputExclusions = [];
64
+ this.urlExclusions = [];
65
+ this.skipEvent = false;
66
+ this.sourceEventCount = 0;
67
+
68
+ this.init();
69
+ }
70
+
71
+ init() {
72
+ // values reused in functions
73
+ this.req = AsyncStorage.get(KEYS.REQ);
74
+
75
+ // NODE-1431: prevents crashing when req is not present in AsyncStorage.
76
+ if (!this.req) {
77
+ if (!this.ensureReq) return;
78
+
79
+ this.skipEvent = true;
80
+ logger.debug(
81
+ 'failed to handle source event for type: %s; request not present in async storage',
82
+ this.type
83
+ );
84
+ }
85
+
86
+ if (!this.exclusions) return;
87
+
88
+ const { pathname } = parseurl(this.req);
89
+
90
+ this.inputExclusions = this.exclusions
91
+ .getInputExclusions('assess')
92
+ .reduce((acc, e) => {
93
+ if (!e.matchesUrl(pathname)) return acc;
94
+
95
+ // `isNamed` is false for exclusion types such as BODY and QUERYSTRING which
96
+ // apply to the entire set of params not only those by a specified name. Also
97
+ // is true if the input name is set to wildcard "*" meaning it should apply to
98
+ // all values. In each case there's no need to wrap if all rules are excluded.
99
+ if (!this.skipEvent && e.appliesToAllAssessRules() && !e.isNamed) {
100
+ logExclusionMessage(e, this.type);
101
+ this.skipEvent = true;
102
+ }
103
+
104
+ acc.push(e);
105
+ return acc;
106
+ }, []);
107
+
108
+ this.urlExclusions = this.exclusions
109
+ .getUrlExclusions('assess')
110
+ .reduce((acc, e) => {
111
+ if (!e.matchesUrl(pathname)) return acc;
112
+
113
+ if (!this.skipEvent && e.appliesToAllAssessRules()) {
114
+ logExclusionMessage(e, this.type);
115
+ this.skipEvent = true;
116
+ }
117
+
118
+ acc.push(e);
119
+ return acc;
120
+ }, []);
121
+ }
122
+
123
+ /**
124
+ *
125
+ * @param {object} param
126
+ * @param {object} param.obj usually the incoming message
127
+ * @param {string} param.prop obj[prop] is containing user-controlled data
128
+ */
129
+ handle({ obj, prop }) {
130
+ this.typeKey = prop;
131
+
132
+ if (this.skipEvent) return;
133
+
134
+ this.traverseAndTrack({ obj, key: prop });
135
+ }
136
+
137
+ // eslint-disable-next-line complexity
138
+ traverseAndTrack({ obj, key, path = [] }) {
139
+ const value = obj[key];
140
+
141
+ if (!value) return;
142
+
143
+ if (this.sourceEventCount > SOURCE_EVENT_MAX) {
144
+ logger.debug('max sources exceeded for %s', this.type);
145
+ return;
146
+ }
147
+
148
+ if (Array.isArray(value)) {
149
+ const limit = Math.min(value.length, this.samplingThreshold);
150
+
151
+ trackedObjects.set(value, { ...this, path, key });
152
+
153
+ for (let i = 0; i < limit; i += this.samplingInterval) {
154
+ this.traverseAndTrack({
155
+ obj: value,
156
+ key: i,
157
+ path: [...path, i]
158
+ });
159
+ }
160
+ } else if (Buffer.isBuffer(value)) {
161
+ this.sourceEventCount++;
162
+ patcher.patch(value, 'toString', {
163
+ name: 'Buffer.toString',
164
+ alwaysRun: true,
165
+ patchType: PATCH_TYPES.MISC,
166
+ post: (wrapCtx) => {
167
+ this.trackStringProp({
168
+ obj: wrapCtx,
169
+ key: 'result',
170
+ path
171
+ });
172
+ }
173
+ });
174
+ } else if (typeof value === 'string' || value instanceof String) {
175
+ this.sourceEventCount++;
176
+ this.trackStringProp({ obj, key, path });
177
+ } else if (typeof value === 'object') {
178
+ trackedObjects.set(value, { ...this, path, key });
179
+
180
+ for (const objKey of Object.keys(value)) {
181
+ this.traverseAndTrack({
182
+ obj: value,
183
+ path: [...path, objKey],
184
+ key: objKey
185
+ });
186
+ }
187
+ }
188
+ }
189
+
190
+ trackStringProp({ obj, key, path, value = obj[key] }) {
191
+ const trackData = tracker.track(value);
192
+ if (!trackData) {
193
+ return;
194
+ }
195
+
196
+ const name = path.join('.');
197
+ const { props, str: result } = trackData;
198
+
199
+ props.tagRanges = this.getTagRanges({ name, result });
200
+ props.event = new SourceEvent({
201
+ context: new CallContext({
202
+ obj: 'IncomingMessage {}',
203
+ args: [`${this.typeKey}.${name}`],
204
+ result,
205
+ stackOpts: this.stackOpts
206
+ }),
207
+ code: `req.${this.typeKey}.${name}`,
208
+ signature: this.signature,
209
+ tagRanges: props.tagRanges,
210
+ target: 'R',
211
+ type: this.type,
212
+ name
213
+ });
214
+
215
+ // NOTE: this used to happen on access in SourceMembrane.onString(), though we
216
+ // should be able to determine access of source value during dataflow - when a
217
+ // source is tracked into PROPAGATION or SINK event.
218
+ storeSource({ type: this.type, name: key });
219
+
220
+ obj[key] = result;
221
+ }
222
+
223
+ getTagRanges({ result, name }) {
224
+ const stop = result.length - 1;
225
+ const tagRanges = [new TagRange(0, stop, 'untrusted')];
226
+
227
+ const typeLowerCase = this.type.toLowerCase();
228
+ const nameLowerCase = name.toLocaleLowerCase();
229
+
230
+ if (typeLowerCase === 'header' && nameLowerCase !== 'referer') {
231
+ tagRanges.push(new TagRange(0, stop, 'header'));
232
+ }
233
+
234
+ if (typeLowerCase === 'cookie') {
235
+ tagRanges.push(new TagRange(0, stop, 'cookie'));
236
+ }
237
+
238
+ if (!this.inputExclusions.length) {
239
+ return tagRanges;
240
+ }
241
+
242
+ const rules = new Set();
243
+
244
+ /*
245
+ Maybe it's pointless because we set skipEvent to true if appliesToAllAssessRules
246
+ and we won't get here
247
+ */
248
+ const exclusions = this.inputExclusions.filter(
249
+ (e) => !e.appliesToAllAssessRules()
250
+ );
251
+
252
+ for (const exclusion of exclusions) {
253
+ if (!exclusion.matches(name)) {
254
+ continue;
255
+ }
256
+
257
+ for (const ruleId of exclusion.assessmentRulesList) {
258
+ logger.debug(
259
+ 'excluding %s %s value from %s (%s)',
260
+ this.type,
261
+ name,
262
+ ruleId,
263
+ exclusion.name
264
+ );
265
+
266
+ if (!rules.has(ruleId)) {
267
+ tagRanges.push(new TagRange(0, stop, `exclusion:${ruleId}`));
268
+ }
269
+
270
+ rules.add(ruleId);
271
+ }
272
+ }
273
+
274
+ return tagRanges;
275
+ }
276
+ }
277
+
278
+ function storeSource({ type, name }) {
279
+ let storedSources = AsyncStorage.get(KEYS.SOURCES);
280
+
281
+ if (!storedSources) {
282
+ // if this is the first source stored on the request, initialize a map
283
+ // and set the storedSources to be a reference to the new, empty map.
284
+ storedSources = new Map();
285
+ AsyncStorage.set(KEYS.SOURCES, storedSources);
286
+ }
287
+
288
+ // there are cases where AsyncStorage context is out of sync, add defensive code so we don't
289
+ // crash.
290
+ if (storedSources) {
291
+ // save event for route coverage (data pulled in for RouteInfo object)
292
+ // only want to save unique values
293
+ storedSources.set(`${type}-${name}`, new TraceEventSource({ type, name }));
294
+ }
295
+ }
296
+
297
+ function logExclusionMessage(exclusion, type) {
298
+ const { name } = exclusion;
299
+ logger.debug(
300
+ 'excluding %s inputs from all assess rules (%s)',
301
+ type.toLowerCase(),
302
+ name
303
+ );
304
+ }
305
+
306
+ module.exports.SourceEventHandler = SourceEventHandler;
307
+ module.exports.trackedObjects = trackedObjects;
@@ -26,9 +26,12 @@ const parseurl = require('parseurl');
26
26
  const {
27
27
  EXCLUSION_INPUT_TYPES: { BODY, HEADER, PARAMETER, QUERYSTRING, COOKIE }
28
28
  } = require('../../constants');
29
+ const { Signature } = require('../models');
29
30
 
30
31
  const sources = module.exports;
31
32
 
33
+ const { SourceEventHandler } = require('./event-handler');
34
+
32
35
  /**
33
36
  * Registers sources for assess instrumentation
34
37
  */
@@ -60,19 +63,104 @@ sources.track = function(type, parent, key, membrane) {
60
63
  */
61
64
  sources.registerListeners = function({ config, exclusions }) {
62
65
  agentEmitter.on('assess.body', (obj, prop) => {
63
- sources.handleSourceEvent(config, exclusions, BODY, obj, prop);
66
+ if (!config.agent.traverse_and_track) {
67
+ return sources.handleSourceEvent(config, exclusions, BODY, obj, prop);
68
+ }
69
+
70
+ agentEmitter.emit('assess.source', {
71
+ config,
72
+ exclusions,
73
+ obj,
74
+ prop,
75
+ data: obj[prop],
76
+ type: BODY
77
+ });
64
78
  });
65
79
  agentEmitter.on('assess.headers', (obj, prop) => {
66
- sources.handleSourceEvent(config, exclusions, HEADER, obj, prop);
80
+ if (!config.agent.traverse_and_track) {
81
+ return sources.handleSourceEvent(config, exclusions, HEADER, obj, prop);
82
+ }
83
+
84
+ agentEmitter.emit('assess.source', {
85
+ obj,
86
+ prop,
87
+ data: obj[prop],
88
+ type: HEADER
89
+ });
67
90
  });
68
91
  agentEmitter.on('assess.params', (obj, prop) => {
69
- sources.handleSourceEvent(config, exclusions, PARAMETER, obj, prop);
92
+ if (!config.agent.traverse_and_track) {
93
+ return sources.handleSourceEvent(
94
+ config,
95
+ exclusions,
96
+ PARAMETER,
97
+ obj,
98
+ prop
99
+ );
100
+ }
101
+
102
+ agentEmitter.emit('assess.source', {
103
+ obj,
104
+ prop,
105
+ data: obj[prop],
106
+ type: PARAMETER
107
+ });
70
108
  });
71
109
  agentEmitter.on('assess.query', (obj, prop) => {
72
- sources.handleSourceEvent(config, exclusions, QUERYSTRING, obj, prop);
110
+ if (!config.agent.traverse_and_track) {
111
+ return sources.handleSourceEvent(
112
+ config,
113
+ exclusions,
114
+ QUERYSTRING,
115
+ obj,
116
+ prop
117
+ );
118
+ }
119
+
120
+ agentEmitter.emit('assess.source', {
121
+ obj,
122
+ prop,
123
+ data: obj[prop],
124
+ type: QUERYSTRING
125
+ });
73
126
  });
127
+
74
128
  agentEmitter.on('assess.cookies', (obj, prop) => {
75
- sources.handleSourceEvent(config, exclusions, COOKIE, obj, prop);
129
+ if (!config.agent.traverse_and_track) {
130
+ return sources.handleSourceEvent(config, exclusions, COOKIE, obj, prop);
131
+ }
132
+
133
+ agentEmitter.emit('assess.source', {
134
+ obj,
135
+ prop,
136
+ type: COOKIE
137
+ });
138
+ });
139
+
140
+ // might be helpful for clients to send add'l values in event arg
141
+ // - stackOpts: elide frames from function ref in client instrumentation
142
+ // - signature: rather than create shared one in the handler
143
+ // - or stack snapshot function - could share among SourceEvents
144
+ // - call context to share among SourceEvents
145
+ agentEmitter.on('assess.source', ({ obj, prop, type, signature }) => {
146
+ if (!signature) {
147
+ signature = new Signature({
148
+ moduleName: 'Object',
149
+ methodName: 'getter',
150
+ args: [prop],
151
+ return: 'String',
152
+ isModule: false
153
+ });
154
+ }
155
+
156
+ new SourceEventHandler({
157
+ config,
158
+ exclusions,
159
+ signature,
160
+ type,
161
+ stackOpts: undefined,
162
+ snapshot: undefined
163
+ }).handle({ obj, prop });
76
164
  });
77
165
  };
78
166
 
@@ -0,0 +1,23 @@
1
+ /**
2
+ Copyright: 2022 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
+ 'use strict';
16
+
17
+ const AssessSinks = require('./sinks');
18
+
19
+ module.exports = class SpdyInstrumentation {
20
+ constructor(agent) {
21
+ new AssessSinks(agent);
22
+ }
23
+ };