@contrast/agent 4.7.0 → 4.7.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 (76) 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/lib/assess/membrane/deserialization-membrane.js +4 -5
  6. package/lib/assess/membrane/source-membrane.js +15 -19
  7. package/lib/assess/models/call-context.js +1 -1
  8. package/lib/assess/policy/propagators.json +8 -0
  9. package/lib/assess/policy/rules.json +2 -2
  10. package/lib/assess/policy/signatures.json +27 -0
  11. package/lib/assess/policy/util.js +2 -1
  12. package/lib/assess/propagators/JSON/parse.js +1 -1
  13. package/lib/assess/propagators/JSON/stringify.js +3 -3
  14. package/lib/assess/propagators/array-prototype-join.js +7 -8
  15. package/lib/assess/propagators/common.js +7 -5
  16. package/lib/assess/propagators/handlebars-escape-expresssion.js +1 -1
  17. package/lib/assess/propagators/joi/boolean.js +1 -1
  18. package/lib/assess/propagators/joi/expression.js +1 -1
  19. package/lib/assess/propagators/joi/number.js +1 -1
  20. package/lib/assess/propagators/joi/string-base.js +1 -1
  21. package/lib/assess/propagators/joi/string-schema.js +12 -13
  22. package/lib/assess/propagators/joi/values.js +11 -11
  23. package/lib/assess/propagators/manager.js +12 -10
  24. package/lib/assess/propagators/mongoose/helpers.js +20 -0
  25. package/lib/assess/propagators/mongoose/index.js +18 -0
  26. package/lib/assess/propagators/mongoose/map.js +74 -0
  27. package/lib/assess/propagators/mongoose/string.js +104 -0
  28. package/lib/assess/propagators/number.js +54 -0
  29. package/lib/assess/propagators/object.js +6 -7
  30. package/lib/assess/propagators/path/basename.js +14 -13
  31. package/lib/assess/propagators/path/common.js +1 -1
  32. package/lib/assess/propagators/path/dirname.js +14 -13
  33. package/lib/assess/propagators/path/extname.js +14 -13
  34. package/lib/assess/propagators/path/parse.js +1 -1
  35. package/lib/assess/propagators/path/relative.js +7 -5
  36. package/lib/assess/propagators/querystring/escape.js +20 -18
  37. package/lib/assess/propagators/querystring/parse.js +7 -5
  38. package/lib/assess/propagators/querystring/stringify.js +25 -24
  39. package/lib/assess/propagators/querystring/unescape.js +20 -18
  40. package/lib/assess/propagators/sequelize/sql-string-escape.js +1 -1
  41. package/lib/assess/propagators/sequelize/sql-string-format-named-parameters.js +1 -1
  42. package/lib/assess/propagators/sequelize/sql-string-format.js +3 -3
  43. package/lib/assess/propagators/sequelize/utils.js +2 -2
  44. package/lib/assess/propagators/string-prototype-replace.js +30 -28
  45. package/lib/assess/propagators/string-prototype-split.js +36 -36
  46. package/lib/assess/propagators/string-prototype-trim.js +15 -17
  47. package/lib/assess/propagators/string.js +12 -16
  48. package/lib/assess/propagators/template-escape.js +21 -18
  49. package/lib/assess/propagators/templates.js +8 -8
  50. package/lib/assess/propagators/url/url-prototype-parse.js +5 -6
  51. package/lib/assess/propagators/url/url-url.js +51 -43
  52. package/lib/assess/propagators/util/format.js +1 -1
  53. package/lib/assess/propagators/v8/init-hooks.js +3 -3
  54. package/lib/assess/propagators/validator/init-hooks.js +22 -22
  55. package/lib/assess/sinks/common.js +10 -5
  56. package/lib/assess/sinks/libxmljs-xxe.js +1 -1
  57. package/lib/assess/sinks/mongodb.js +2 -1
  58. package/lib/assess/sinks/ssrf-url.js +1 -1
  59. package/lib/constants.js +4 -1
  60. package/lib/core/config/options.js +1 -1
  61. package/lib/core/rewrite/injections.js +8 -0
  62. package/lib/feature-set.js +1 -1
  63. package/lib/hooks/object-to-primitive.js +6 -7
  64. package/lib/hooks/patcher.js +1 -1
  65. package/lib/protect/rules/nosqli/nosql-injection-rule.js +228 -0
  66. package/lib/protect/rules/rule-factory.js +2 -2
  67. package/lib/protect/service.js +23 -11
  68. package/lib/protect/sinks/mongodb.js +56 -55
  69. package/lib/reporter/translations/to-protobuf/dtm/index.js +1 -1
  70. package/lib/reporter/translations/to-protobuf/dtm/ip-denylist-details.js +1 -1
  71. package/lib/reporter/translations/to-protobuf/dtm/rasp-rule-sample.js +1 -1
  72. package/lib/reporter/translations/to-protobuf/settings/defend-features.js +8 -6
  73. package/lib/reporter/translations/to-protobuf/settings/exclusions.js +5 -4
  74. package/lib/tracker.js +13 -65
  75. package/package.json +2 -1
  76. package/lib/protect/rules/nosqli/no-sql-injection-rule.js +0 -109
@@ -0,0 +1,74 @@
1
+ /**
2
+ Copyright: 2021 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 TagRange = require('../../models/tag-range');
25
+ const { CallContext, PropagationEvent, Signature } = require('../../models');
26
+ const { hasUserDefinedValidator } = require('./helpers');
27
+
28
+ const doValidateSyncPatcher = (SchemaMap) => {
29
+ patcher.patch(SchemaMap.prototype, 'doValidateSync', {
30
+ alwaysRun: true,
31
+ name: 'mongoose.map.doValidateSync',
32
+ patchType: ASSESS_PROPAGATOR,
33
+ post(data) {
34
+ if (data.result || data.obj.options.of.name !== 'String') return;
35
+
36
+ if (!hasUserDefinedValidator(data)) return;
37
+
38
+ for (const value of data.args[0].values()) {
39
+ const trackingData = tracker.track(value);
40
+
41
+ if (!trackingData) return;
42
+
43
+ const { props } = trackingData;
44
+ const stringLength = value.length - 1;
45
+
46
+ props.tagRanges = tagRangeUtil.add(
47
+ props.tagRanges,
48
+ new TagRange(0, stringLength, 'custom-validated-nosql-injection')
49
+ );
50
+
51
+ props.tagRanges = tagRangeUtil.add(
52
+ props.tagRanges,
53
+ new TagRange(0, stringLength, 'string-type-checked')
54
+ );
55
+
56
+ props.event = new PropagationEvent({
57
+ context: new CallContext(data),
58
+ signature: new Signature('mongoose.map.doValidateSync'),
59
+ tagRanges: props.tagRanges,
60
+ source: 'P',
61
+ target: 'A',
62
+ parents: [props.event]
63
+ });
64
+ }
65
+ }
66
+ });
67
+ };
68
+
69
+ requireHook.resolve(
70
+ { name: 'mongoose', file: 'lib/schema/map.js', version: '>=5.0.0' },
71
+ (SchemaMap) => {
72
+ doValidateSyncPatcher(SchemaMap);
73
+ }
74
+ );
@@ -0,0 +1,104 @@
1
+ /**
2
+ Copyright: 2021 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 TagRange = require('../../models/tag-range');
25
+ const { CallContext, PropagationEvent, Signature } = require('../../models');
26
+ const { hasUserDefinedValidator } = require('./helpers');
27
+
28
+ const enumPatcher = (SchemaString) => {
29
+ patcher.patch(SchemaString.prototype, 'enum', {
30
+ alwaysRun: true,
31
+ name: 'mongoose.string.enum',
32
+ patchType: ASSESS_PROPAGATOR,
33
+ post(data) {
34
+ if (!data.result) return;
35
+
36
+ const enumValidator = data.result.validators.find(
37
+ (validator) => validator.type === 'enum'
38
+ );
39
+
40
+ if (!enumValidator) return;
41
+
42
+ patcher.patch(enumValidator, 'validator', {
43
+ alwaysRun: true,
44
+ name: 'mongoose.string.enumValidator',
45
+ patchType: ASSESS_PROPAGATOR,
46
+ post(data) {
47
+ if (!data.result) return;
48
+
49
+ tracker.untrack(data.args[0]);
50
+ }
51
+ });
52
+ }
53
+ });
54
+ };
55
+
56
+ const doValidateSyncPatcher = (SchemaString) => {
57
+ patcher.patch(SchemaString.prototype, 'doValidateSync', {
58
+ alwaysRun: true,
59
+ name: 'mongoose.string.doValidateSync',
60
+ patchType: ASSESS_PROPAGATOR,
61
+ post(data) {
62
+ if (data.result) return;
63
+
64
+ if (!hasUserDefinedValidator(data)) return;
65
+
66
+ const trackingData = tracker.track(data.args[0]);
67
+ if (!trackingData) return;
68
+
69
+ const { props } = trackingData;
70
+ const incomingStringLength = data.args[0].length - 1;
71
+
72
+ props.tagRanges = tagRangeUtil.add(
73
+ props.tagRanges,
74
+ new TagRange(
75
+ 0,
76
+ incomingStringLength,
77
+ 'custom-validated-nosql-injection'
78
+ )
79
+ );
80
+
81
+ props.tagRanges = tagRangeUtil.add(
82
+ props.tagRanges,
83
+ new TagRange(0, incomingStringLength, 'string-type-checked')
84
+ );
85
+
86
+ props.event = new PropagationEvent({
87
+ context: new CallContext(data),
88
+ signature: new Signature('mongoose.string.doValidateSync'),
89
+ tagRanges: props.tagRanges,
90
+ source: 'P',
91
+ target: 'A',
92
+ parents: [props.event]
93
+ });
94
+ }
95
+ });
96
+ };
97
+
98
+ requireHook.resolve(
99
+ { name: 'mongoose', file: 'lib/schema/string.js', version: '>=5.0.0' },
100
+ (SchemaString) => {
101
+ enumPatcher(SchemaString);
102
+ doValidateSyncPatcher(SchemaString);
103
+ }
104
+ );
@@ -0,0 +1,54 @@
1
+ /**
2
+ Copyright: 2021 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.js');
18
+ const patcher = require('../../hooks/patcher.js');
19
+ const { PATCH_TYPES } = require('../../constants');
20
+ const tagRangeUtil = require('../models/tag-range/util');
21
+ const TagRange = require('../models/tag-range');
22
+ const ContrastNumber = require('../../core/rewrite/injections').get('Number');
23
+ const { CallContext, PropagationEvent, Signature } = require('../models');
24
+
25
+ function handle() {
26
+ ContrastNumber.enable();
27
+
28
+ patcher.patch(ContrastNumber.value, {
29
+ name: 'Number',
30
+ patchType: PATCH_TYPES.ASSESS_PROPAGATOR,
31
+ post(data) {
32
+ const trackingData = tracker.getData(data.args[0]);
33
+
34
+ if (!Number.isNaN(data.result) && trackingData) {
35
+ const { event } = trackingData;
36
+ trackingData.tagRanges = tagRangeUtil.add(
37
+ trackingData.tagRanges,
38
+ new TagRange(0, data.args[0].length - 1, 'limited-chars')
39
+ );
40
+
41
+ trackingData.event = new PropagationEvent({
42
+ context: new CallContext(data),
43
+ signature: new Signature('Number'),
44
+ tagRanges: trackingData.tagRanges,
45
+ source: 'P',
46
+ target: 'P',
47
+ parents: [event]
48
+ });
49
+ }
50
+ }
51
+ });
52
+ }
53
+
54
+ module.exports = { handle };
@@ -41,18 +41,17 @@ function handle() {
41
41
  }
42
42
 
43
43
  const argContrastProperties = tracker.getData(arg);
44
- if (!argContrastProperties.tracked) {
44
+ if (!argContrastProperties) {
45
45
  return;
46
46
  }
47
47
 
48
- const str = tracker.track(data.result);
49
- const strContrastProperties = tracker.getData(str);
50
- if (strContrastProperties.tracked) {
51
- strContrastProperties.event = argContrastProperties.event;
52
- strContrastProperties.tagRanges = strContrastProperties.tagRanges.concat(
48
+ const tracked = tracker.track(data.result);
49
+ if (tracked) {
50
+ tracked.props.event = argContrastProperties.event;
51
+ tracked.props.tagRanges = tracked.props.tagRanges.concat(
53
52
  argContrastProperties.tagRanges
54
53
  );
55
- data.result = str;
54
+ data.result = tracked.str;
56
55
  }
57
56
  }
58
57
  });
@@ -45,7 +45,7 @@ module.exports.handle = function handle() {
45
45
  if (!path || !data.result) return;
46
46
 
47
47
  const trackingData = tracker.getData(path);
48
- if (!trackingData.tracked) return;
48
+ if (!trackingData) return;
49
49
 
50
50
  if (extension) {
51
51
  const extIndex = _path.lastIndexOf(extension);
@@ -68,19 +68,20 @@ module.exports.handle = function handle() {
68
68
  // no tags propagated to the result
69
69
  if (!tagRanges.length) return;
70
70
 
71
- const result = tracker.track(data.result);
72
- const resultData = tracker.getData(result);
73
71
  const parentEvent = trackingData.event;
74
- resultData.tagRanges = tagRanges;
75
- resultData.event = new PropagationEvent({
76
- context: new CallContext(data),
77
- parents: [parentEvent],
78
- signature,
79
- source: 'P',
80
- tagRanges,
81
- target: 'R'
82
- });
83
- data.result = result;
72
+ const tracked = tracker.track(data.result);
73
+ if (tracked) {
74
+ tracked.props.tagRanges = tagRanges;
75
+ tracked.props.event = new PropagationEvent({
76
+ context: new CallContext(data),
77
+ parents: [parentEvent],
78
+ signature,
79
+ source: 'P',
80
+ tagRanges,
81
+ target: 'R'
82
+ });
83
+ data.result = tracked.str;
84
+ }
84
85
  }
85
86
  });
86
87
  }
@@ -333,7 +333,7 @@ function propagate({ resultMeta, data, win32 }) {
333
333
  evaluator: (segmentOffset) => segmentOffset > -1,
334
334
  offset: 0,
335
335
  str: arg,
336
- tagRanges: argData.tracked ? argData.tagRanges : []
336
+ tagRanges: argData ? argData.tagRanges : []
337
337
  };
338
338
 
339
339
  const targetTagRanges = adjustTagsToPart(
@@ -42,7 +42,7 @@ module.exports.handle = function handle() {
42
42
  if (!data.args[0]) return;
43
43
 
44
44
  const trackingData = tracker.getData(data.args[0]);
45
- if (!trackingData.tracked) return;
45
+ if (!trackingData) return;
46
46
 
47
47
  // path.dirname() does a slice at 0 to the calculated end separator
48
48
  const tagRanges = createSubsetTagRanges({
@@ -57,19 +57,20 @@ module.exports.handle = function handle() {
57
57
 
58
58
  if (!tagRanges.length) return;
59
59
 
60
- const result = tracker.track(data.result);
61
- const resultData = tracker.getData(result);
60
+ const tracked = tracker.track(data.result);
62
61
  const parentEvent = trackingData.event;
63
- resultData.tagRanges = tagRanges;
64
- resultData.event = new PropagationEvent({
65
- context: new CallContext(data),
66
- parents: [parentEvent],
67
- signature,
68
- source: 'P',
69
- tagRanges,
70
- target: 'R'
71
- });
72
- data.result = result;
62
+ if (tracked) {
63
+ tracked.props.tagRanges = tagRanges;
64
+ tracked.props.event = new PropagationEvent({
65
+ context: new CallContext(data),
66
+ parents: [parentEvent],
67
+ signature,
68
+ source: 'P',
69
+ tagRanges,
70
+ target: 'R'
71
+ });
72
+ data.result = tracked.str;
73
+ }
73
74
  }
74
75
  });
75
76
  }
@@ -42,7 +42,7 @@ module.exports.handle = function handle() {
42
42
  if (!data.args[0] || !data.result) return;
43
43
 
44
44
  const trackingData = tracker.getData(data.args[0]);
45
- if (!trackingData.tracked) return;
45
+ if (!trackingData) return;
46
46
 
47
47
  // The path.extname() implementation does a substr on the argument
48
48
  // based on the calculated index of the last dot
@@ -62,19 +62,20 @@ module.exports.handle = function handle() {
62
62
  // no tags propagated to the result
63
63
  if (!tagRanges.length) return;
64
64
 
65
- const result = tracker.track(data.result);
66
- const resultData = tracker.getData(result);
65
+ const tracked = tracker.track(data.result);
67
66
  const parentEvent = trackingData.event;
68
- resultData.tagRanges = tagRanges;
69
- resultData.event = new PropagationEvent({
70
- context: new CallContext(data),
71
- parents: [parentEvent],
72
- signature,
73
- source: 'P',
74
- tagRanges,
75
- target: 'R'
76
- });
77
- data.result = result;
67
+ if (tracked) {
68
+ tracked.props.tagRanges = tagRanges;
69
+ tracked.props.event = new PropagationEvent({
70
+ context: new CallContext(data),
71
+ parents: [parentEvent],
72
+ signature,
73
+ source: 'P',
74
+ tagRanges,
75
+ target: 'R'
76
+ });
77
+ data.result = tracked.str;
78
+ }
78
79
  }
79
80
  });
80
81
  }
@@ -24,7 +24,7 @@ const propagate = function propagate(data) {
24
24
  const props = tracker.getData(data.args[0]);
25
25
  const { result } = data;
26
26
 
27
- if (props.tracked && result) {
27
+ if (props && result) {
28
28
  const membrane = new DeserializationMembrane(data, props);
29
29
  data.result = membrane.wrap(result);
30
30
  }
@@ -35,7 +35,7 @@ const provider = {
35
35
  const { args, result } = data;
36
36
  const trackData = tracker.getData(args[1]);
37
37
 
38
- if (!trackData.tracked) {
38
+ if (!trackData) {
39
39
  return;
40
40
  }
41
41
 
@@ -61,10 +61,12 @@ const provider = {
61
61
  },
62
62
  data
63
63
  );
64
- data.result = tracker.track(data.result);
65
- const resultTrackData = tracker.getData(data.result);
66
- resultTrackData.tagRanges = shiftedRanges;
67
- resultTrackData.event = event;
64
+ const tracked = tracker.track(data.result);
65
+ if (tracked) {
66
+ data.result = tracked.str;
67
+ tracked.props.tagRanges = shiftedRanges;
68
+ tracked.props.event = event;
69
+ }
68
70
  }
69
71
  },
70
72
  /**
@@ -21,34 +21,36 @@ const qsUtils = require('./utils');
21
21
 
22
22
  function handler(data) {
23
23
  const input = data.args[0];
24
+ const trackedData = tracker.getData(input);
24
25
 
25
- if (!input || !tracker.getData(input).tracked) {
26
+ if (!input || !trackedData) {
26
27
  return;
27
28
  }
28
29
 
29
30
  // adjust tag ranges
30
31
  const tagRanges = [];
31
- tracker.getData(input).tagRanges.forEach((tag) => {
32
+ trackedData.tagRanges.forEach((tag) => {
32
33
  tagRanges.push(qsUtils.adjustRangeEscape(tag, 0, input));
33
34
  });
34
35
  tagRanges.push(new TagRange(0, data.result.length - 1, 'url-encoded'));
35
36
 
36
- const result = tracker.track(data.result);
37
- const trackData = tracker.getData(result);
38
- trackData.tagRanges = tagRanges;
39
- trackData.event = new PropagationEvent({
40
- context: new CallContext({
41
- ...data,
42
- obj: null
43
- }),
44
- parents: [trackData.event],
45
- signature: new Signature('querystring.escape'),
46
- source: 'P',
47
- target: 'R',
48
- tagRanges,
49
- tags: ['url-encoded']
50
- });
51
- data.result = result;
37
+ const tracked = tracker.track(data.result);
38
+ if (tracked) {
39
+ tracked.props.tagRanges = tagRanges;
40
+ tracked.props.event = new PropagationEvent({
41
+ context: new CallContext({
42
+ ...data,
43
+ obj: null
44
+ }),
45
+ parents: [tracked.props.event],
46
+ signature: new Signature('querystring.escape'),
47
+ source: 'P',
48
+ target: 'R',
49
+ tagRanges,
50
+ tags: ['url-encoded']
51
+ });
52
+ data.result = tracked.str;
53
+ }
52
54
  }
53
55
 
54
56
  module.exports.handle = handler;
@@ -47,9 +47,11 @@ function getUnescapeWrapper(data, unescape) {
47
47
 
48
48
  // track the part w/ trimmed ranges if applicable
49
49
  if (tagRanges.length) {
50
- result = tracker.track(part);
51
- resultData = tracker.getData(result);
52
- resultData.tagRanges = tagRanges;
50
+ const tracked = tracker.track(part);
51
+ if (tracked) {
52
+ result = tracked.str;
53
+ tracked.props.tagRanges = tagRanges;
54
+ }
53
55
  } else {
54
56
  result = part;
55
57
  }
@@ -58,7 +60,7 @@ function getUnescapeWrapper(data, unescape) {
58
60
  result = Scopes.runInAllowAllScope(() => unescape(result));
59
61
  resultData = tracker.getData(result);
60
62
 
61
- if (resultData.tracked) {
63
+ if (resultData) {
62
64
  resultData.event = new PropagationEvent({
63
65
  context: new CallContext({
64
66
  ...data,
@@ -91,7 +93,7 @@ function pre(data) {
91
93
  }
92
94
  const trackingData = tracker.getData(input);
93
95
 
94
- if (!trackingData.tracked) {
96
+ if (!trackingData) {
95
97
  return;
96
98
  }
97
99
 
@@ -129,7 +129,7 @@ class OnEscapeHandler {
129
129
  // set current key for reference if when array-valued
130
130
  this.currentKey = {
131
131
  value: escaped,
132
- tagRanges: trackingData.tracked ? trackingData.tagRanges : null
132
+ tagRanges: trackingData ? trackingData.tagRanges : null
133
133
  };
134
134
 
135
135
  // capture track info before updating state
@@ -175,7 +175,7 @@ class OnEscapeHandler {
175
175
  this.offset += this.currentKey.value.length + this.eqLen;
176
176
  }
177
177
 
178
- if (trackingData.tracked) {
178
+ if (trackingData) {
179
179
  ret.push({
180
180
  offset: this.offset,
181
181
  tagRanges: trackingData.tagRanges,
@@ -279,34 +279,35 @@ function post(data) {
279
279
  }
280
280
 
281
281
  const tracked = tracker.track(data.result);
282
- const trackData = tracker.getData(tracked);
283
282
 
284
283
  if (data.state.escape === querystring.escape) {
285
284
  data.state.tagRanges.push(
286
285
  new TagRange(0, data.result.length - 1, 'url-encoded')
287
286
  );
288
287
  }
289
- const sorted = _.sortBy(data.state.tagRanges, 'start');
290
- trackData.tagRanges = [];
291
- tagRangeUtil.addAllInPlace(trackData.tagRanges, sorted);
292
-
293
- // stringify / encode
294
- const method = data.funcKey.split('.')[1];
295
-
296
- trackData.event = new PropagationEvent({
297
- context: new CallContext({
298
- ...data,
299
- args: data.state.origArgs,
300
- obj: null
301
- }),
302
- parents: Array.from(data.state.events),
303
- signature: new Signature(`querystring.${method}`),
304
- source: 'P',
305
- tagRanges: trackData.tagRanges,
306
- target: 'R',
307
- tags: ['url-encoded']
308
- });
309
- data.result = tracked;
288
+ if (tracked) {
289
+ const sorted = _.sortBy(data.state.tagRanges, 'start');
290
+ tracked.props.tagRanges = [];
291
+ tagRangeUtil.addAllInPlace(tracked.props.tagRanges, sorted);
292
+
293
+ // stringify / encode
294
+ const method = data.funcKey.split('.')[1];
295
+
296
+ tracked.props.event = new PropagationEvent({
297
+ context: new CallContext({
298
+ ...data,
299
+ args: data.state.origArgs,
300
+ obj: null
301
+ }),
302
+ parents: Array.from(data.state.events),
303
+ signature: new Signature(`querystring.${method}`),
304
+ source: 'P',
305
+ tagRanges: tracked.props.tagRanges,
306
+ target: 'R',
307
+ tags: ['url-encoded']
308
+ });
309
+ data.result = tracked.str;
310
+ }
310
311
  }
311
312
 
312
313
  module.exports.handle = { pre, post };
@@ -20,14 +20,15 @@ const qsUtils = require('./utils');
20
20
 
21
21
  function handler(data) {
22
22
  const input = data.args[0];
23
+ const trackedData = tracker.getData(input);
23
24
 
24
- if (!input || !tracker.getData(input).tracked) {
25
+ if (!input || !trackedData) {
25
26
  return;
26
27
  }
27
28
 
28
29
  // adjust tag ranges
29
30
  const tagRanges = [];
30
- tracker.getData(input).tagRanges.forEach((tag) => {
31
+ trackedData.tagRanges.forEach((tag) => {
31
32
  if (tag.tag === 'url-encoded') {
32
33
  return;
33
34
  }
@@ -37,22 +38,23 @@ function handler(data) {
37
38
  return;
38
39
  }
39
40
 
40
- const trackedResult = tracker.track(data.result);
41
- const trackData = tracker.getData(trackedResult);
42
- trackData.tagRanges = tagRanges;
43
- trackData.event = new PropagationEvent({
44
- context: new CallContext({
45
- ...data,
46
- obj: null
47
- }),
48
- parents: [trackData.event],
49
- signature: new Signature('querystring.unescape'),
50
- source: 'P',
51
- tagRanges,
52
- target: 'R',
53
- untags: ['url-encoded']
54
- });
55
- data.result = trackedResult;
41
+ const tracked = tracker.track(data.result);
42
+ if (tracked) {
43
+ tracked.props.tagRanges = tagRanges;
44
+ tracked.props.event = new PropagationEvent({
45
+ context: new CallContext({
46
+ ...data,
47
+ obj: null
48
+ }),
49
+ parents: [tracked.props.event],
50
+ signature: new Signature('querystring.unescape'),
51
+ source: 'P',
52
+ tagRanges,
53
+ target: 'R',
54
+ untags: ['url-encoded']
55
+ });
56
+ data.result = tracked.str;
57
+ }
56
58
  }
57
59
 
58
60
  module.exports.handle = handler;
@@ -35,7 +35,7 @@ module.exports.handle = function() {
35
35
  post(data) {
36
36
  const trackingData = tracker.getData(data.result);
37
37
 
38
- if (!trackingData.tracked) {
38
+ if (!trackingData) {
39
39
  return;
40
40
  }
41
41