@contrast/assess 1.8.0 → 1.10.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 (75) hide show
  1. package/lib/dataflow/event-factory.js +17 -13
  2. package/lib/dataflow/propagation/index.js +4 -0
  3. package/lib/dataflow/propagation/install/JSON/index.js +1 -0
  4. package/lib/dataflow/propagation/install/JSON/parse-fn.js +248 -0
  5. package/lib/dataflow/propagation/install/JSON/parse.js +196 -0
  6. package/lib/dataflow/propagation/install/JSON/stringify.js +5 -3
  7. package/lib/dataflow/propagation/install/array-prototype-join.js +21 -14
  8. package/lib/dataflow/propagation/install/buffer.js +2 -0
  9. package/lib/dataflow/propagation/install/contrast-methods/add.js +2 -0
  10. package/lib/dataflow/propagation/install/contrast-methods/index.js +1 -0
  11. package/lib/dataflow/propagation/install/contrast-methods/number.js +58 -0
  12. package/lib/dataflow/propagation/install/contrast-methods/string.js +53 -6
  13. package/lib/dataflow/propagation/install/contrast-methods/tag.js +3 -1
  14. package/lib/dataflow/propagation/install/decode-uri-component.js +9 -2
  15. package/lib/dataflow/propagation/install/ejs/escape-xml.js +8 -2
  16. package/lib/dataflow/propagation/install/encode-uri-component.js +9 -2
  17. package/lib/dataflow/propagation/install/escape-html.js +13 -5
  18. package/lib/dataflow/propagation/install/escape.js +9 -2
  19. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +11 -4
  20. package/lib/dataflow/propagation/install/isnumeric-0.js +59 -0
  21. package/lib/dataflow/propagation/install/mongoose/index.js +36 -0
  22. package/lib/dataflow/propagation/install/mongoose/schema-string.js +156 -0
  23. package/lib/dataflow/propagation/install/mysql-connection-escape.js +5 -0
  24. package/lib/dataflow/propagation/install/parse-int.js +60 -0
  25. package/lib/dataflow/propagation/install/path/basename.js +124 -0
  26. package/lib/dataflow/propagation/install/path/common.js +176 -0
  27. package/lib/dataflow/propagation/install/path/index.js +32 -0
  28. package/lib/dataflow/propagation/install/path/join-and-resolve.js +141 -0
  29. package/lib/dataflow/propagation/install/path/normalize.js +123 -0
  30. package/lib/dataflow/propagation/install/pug-runtime-escape.js +9 -2
  31. package/lib/dataflow/propagation/install/querystring/parse.js +12 -10
  32. package/lib/dataflow/propagation/install/sequelize.js +6 -3
  33. package/lib/dataflow/propagation/install/sql-template-strings.js +9 -2
  34. package/lib/dataflow/propagation/install/string/concat.js +8 -2
  35. package/lib/dataflow/propagation/install/string/format-methods.js +7 -2
  36. package/lib/dataflow/propagation/install/string/html-methods.js +15 -5
  37. package/lib/dataflow/propagation/install/string/match.js +16 -11
  38. package/lib/dataflow/propagation/install/string/replace.js +23 -15
  39. package/lib/dataflow/propagation/install/string/slice.js +14 -6
  40. package/lib/dataflow/propagation/install/string/split.js +16 -12
  41. package/lib/dataflow/propagation/install/string/substring.js +18 -8
  42. package/lib/dataflow/propagation/install/string/trim.js +4 -1
  43. package/lib/dataflow/propagation/install/unescape.js +9 -2
  44. package/lib/dataflow/propagation/install/url/domain-parsers.js +7 -2
  45. package/lib/dataflow/propagation/install/url/index.js +1 -0
  46. package/lib/dataflow/propagation/install/url/url.js +228 -0
  47. package/lib/dataflow/propagation/install/validator/hooks.js +6 -2
  48. package/lib/dataflow/sinks/index.js +8 -4
  49. package/lib/dataflow/sinks/install/child-process.js +116 -50
  50. package/lib/dataflow/sinks/install/eval.js +138 -0
  51. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +7 -4
  52. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +9 -5
  53. package/lib/dataflow/sinks/install/fs.js +45 -13
  54. package/lib/dataflow/sinks/install/function.js +160 -0
  55. package/lib/dataflow/sinks/install/http/index.js +31 -0
  56. package/lib/dataflow/sinks/install/http/request.js +152 -0
  57. package/lib/dataflow/sinks/install/{http.js → http/server-response.js} +7 -4
  58. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +8 -5
  59. package/lib/dataflow/sinks/install/marsdb.js +3 -0
  60. package/lib/dataflow/sinks/install/mongodb.js +7 -24
  61. package/lib/dataflow/sinks/install/mssql.js +49 -29
  62. package/lib/dataflow/sinks/install/mysql.js +9 -4
  63. package/lib/dataflow/sinks/install/postgres.js +6 -3
  64. package/lib/dataflow/sinks/install/sequelize.js +7 -5
  65. package/lib/dataflow/sinks/install/sqlite3.js +7 -3
  66. package/lib/dataflow/sinks/install/vm.js +276 -0
  67. package/lib/dataflow/sources/handler.js +2 -1
  68. package/lib/dataflow/sources/install/http.js +1 -1
  69. package/lib/dataflow/tag-utils.js +95 -2
  70. package/lib/dataflow/tracker.js +6 -6
  71. package/lib/index.js +2 -0
  72. package/lib/response-scanning/handlers/utils.js +2 -2
  73. package/lib/session-configuration/index.js +34 -0
  74. package/lib/session-configuration/install/http.js +79 -0
  75. package/package.json +2 -2
@@ -15,10 +15,11 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const util = require('util');
19
18
  const querystring = require('querystring');
20
19
  const {
21
- DataflowTag: { URL_ENCODED }
20
+ DataflowTag: { URL_ENCODED },
21
+ inspect,
22
+ join
22
23
  } = require('@contrast/common');
23
24
 
24
25
  const { patchType } = require('../../common');
@@ -41,25 +42,26 @@ module.exports = function(core) {
41
42
  function unescapeWrapper(part) {
42
43
  let result = unescape(part);
43
44
  const start = input.indexOf(part, data.idx);
44
- const tagRanges = createSubsetTags(trackingData.tags, start, result.length - 1);
45
+ const tagRanges = createSubsetTags(trackingData.tags, start, result.length);
45
46
 
46
47
  if (!tagRanges) return result;
47
48
 
48
49
  const resultInfo = tracker.getData(result);
50
+ const [, ...restOfArgsValues] = data.origArgs.map(inspect);
49
51
  const event = createPropagationEvent({
50
52
  name: data.name,
53
+ context: `querystring.parse('${trackingData.value}', ${join(restOfArgsValues, ', ')})`,
54
+ moduleName: 'querystring',
55
+ methodName: 'parse',
51
56
  history: [trackingData],
52
57
  object: {
53
58
  value: part,
54
59
  tracked: true,
55
60
  },
56
- args: data.origArgs.map((arg) => {
57
- const argInfo = tracker.getData(arg);
58
- return {
59
- value: argInfo ? argInfo.value : util.inspect(arg),
60
- tracked: !!argInfo
61
- };
62
- }),
61
+ args: data.origArgs.map((_arg, idx) => ({
62
+ value: idx === 0 ? trackingData.value : restOfArgsValues[idx - 1],
63
+ tracked: !!idx === 0
64
+ })),
63
65
  result: {
64
66
  value: result,
65
67
  tracked: !!resultInfo
@@ -21,7 +21,7 @@ const {
21
21
  } = require('@contrast/common');
22
22
  const { patchType, createModuleLabel } = require('../common');
23
23
 
24
- module.exports = function (core) {
24
+ module.exports = function(core) {
25
25
  const {
26
26
  scopes: { sources, instrumentation },
27
27
  patcher,
@@ -58,9 +58,10 @@ module.exports = function (core) {
58
58
  { name: 'sequelize', file: 'lib/sql-string.js' },
59
59
  (sqlString, version) => {
60
60
  const origEscape = sqlString.escape;
61
+ const name = 'sequelize.escape';
61
62
 
62
63
  patcher.patch(sqlString, 'escape', {
63
- name: 'sequelize.escape',
64
+ name,
64
65
  patchType,
65
66
  post(data) {
66
67
  const { args, result, hooked, orig } = data;
@@ -85,8 +86,10 @@ module.exports = function (core) {
85
86
  newTags[SQL_ENCODED] = [0, result.length - 1];
86
87
 
87
88
  const event = createPropagationEvent({
88
- context: 'sequelize.escape',
89
+ context: `sequelize.escape('${argInfo.value}')`,
89
90
  name: 'sequelize/lib/sql-string.escape',
91
+ moduleName: 'sequelize',
92
+ methodName: 'escape',
90
93
  object: {
91
94
  value: createModuleLabel('sequelize/lib/sql-string.escape', version),
92
95
  tracked: false,
@@ -33,8 +33,10 @@ module.exports = function(core) {
33
33
  return core.assess.dataflow.propagation.sqlTemplateStrings = {
34
34
  install() {
35
35
  depHooks.resolve({ name: 'sql-template-strings' }, (sqlTemplateStrings, version) => {
36
+ const name = 'sql-template-strings.SQL';
37
+
36
38
  patcher.patch(sqlTemplateStrings, 'SQL', {
37
- name: 'sql-template-strings.SQL',
39
+ name,
38
40
  patchType,
39
41
  post(data) {
40
42
  const { args, result, hooked, orig } = data;
@@ -55,7 +57,10 @@ module.exports = function(core) {
55
57
  newTags[SQL_ENCODED] = [0, resultValue.length - 1];
56
58
 
57
59
  const event = createPropagationEvent({
58
- name: 'sql-template-strings.SQL',
60
+ name,
61
+ moduleName: 'sql-template-strings',
62
+ methodName: 'SQL',
63
+ context: `SQL\`${argInfo.value}\``,
59
64
  object: {
60
65
  value: createModuleLabel('sql-template-strings', version),
61
66
  tracked: false
@@ -68,6 +73,8 @@ module.exports = function(core) {
68
73
  tags: newTags,
69
74
  addedTags: [SQL_ENCODED],
70
75
  history,
76
+ source: 'P',
77
+ target: 'R',
71
78
  stacktraceOpts: {
72
79
  constructorOpt: hooked,
73
80
  prependFrames: [orig]
@@ -18,6 +18,7 @@
18
18
  const {
19
19
  createAppendTags
20
20
  } = require('../../../tag-utils');
21
+ const { join, inspect } = require('@contrast/common');
21
22
  const { patchType } = require('../../common');
22
23
 
23
24
  module.exports = function(core) {
@@ -31,8 +32,10 @@ module.exports = function(core) {
31
32
 
32
33
  return core.assess.dataflow.propagation.stringInstrumentation.concat = {
33
34
  install() {
35
+ const name = 'String.prototype.concat';
36
+
34
37
  patcher.patch(String.prototype, 'concat', {
35
- name: 'String.prototype.concat',
38
+ name,
36
39
  patchType,
37
40
  post(data) {
38
41
  const { args, obj, result, hooked, orig } = data;
@@ -70,7 +73,10 @@ module.exports = function(core) {
70
73
 
71
74
  if (history.size) {
72
75
  const event = createPropagationEvent({
73
- name: 'String.prototype.concat',
76
+ name,
77
+ moduleName: 'String',
78
+ methodName: 'prototype.concat',
79
+ context: `${inspect(objInfo?.value) || String(obj)}.concat(${join(argsData.map(d => d.value), ', ')})`,
74
80
  object: {
75
81
  value: objInfo?.value || String(obj),
76
82
  tracked: !!objInfo
@@ -29,8 +29,10 @@ module.exports = function(core) {
29
29
  return core.assess.dataflow.propagation.stringInstrumentation.formatMethods = {
30
30
  install() {
31
31
  ['toLowerCase', 'toUpperCase', 'toLocaleLowerCase', 'toLocaleUpperCase'].forEach((method) => {
32
+ const name = `String.prototype.${method}`;
33
+
32
34
  patcher.patch(String.prototype, method, {
33
- name: `String.prototype.${method}`,
35
+ name,
34
36
  patchType,
35
37
  post(data) {
36
38
  const { obj, result, hooked, orig } = data;
@@ -43,7 +45,10 @@ module.exports = function(core) {
43
45
  const history = [objInfo];
44
46
 
45
47
  const event = createPropagationEvent({
46
- name: `String.prototype.${method}`,
48
+ name,
49
+ moduleName: 'String',
50
+ methodName: `prototype.${method}`,
51
+ context: `'${objInfo.value}'.${method}()`,
47
52
  object: {
48
53
  value: objInfo.value,
49
54
  tracked: true
@@ -18,6 +18,7 @@
18
18
  const {
19
19
  createAppendTags
20
20
  } = require('../../../tag-utils');
21
+ const { inspect } = require('@contrast/common');
21
22
  const { patchType } = require('../../common');
22
23
  const htmlTagsLengths = {
23
24
  anchor: 11,
@@ -57,8 +58,9 @@ module.exports = function(core) {
57
58
 
58
59
  return core.assess.dataflow.propagation.stringInstrumentation.htmlMethods = {
59
60
  install() {
61
+ const name = 'String.prototype.anchor';
60
62
  patcher.patch(String.prototype, 'anchor', {
61
- name: 'String.prototype.anchor',
63
+ name,
62
64
  patchType,
63
65
  post(data) {
64
66
  const { args, obj, result, hooked, orig } = data;
@@ -75,7 +77,10 @@ module.exports = function(core) {
75
77
 
76
78
  if (history.size) {
77
79
  const event = createPropagationEvent({
78
- name: 'String.prototype.anchor',
80
+ name,
81
+ moduleName: 'String',
82
+ methodName: 'prototype.anchor',
83
+ context: `${inspect(objInfo?.value) || String(obj)}.anchor(${argInfo ? argInfo.value : arg})`,
79
84
  object: {
80
85
  value: objInfo?.value || String(obj),
81
86
  tracked: !!objInfo
@@ -85,7 +90,7 @@ module.exports = function(core) {
85
90
  tracked: true
86
91
  },
87
92
  args: [
88
- { value: arg, tracked: !!argInfo }
93
+ { value: argInfo ? argInfo.value : arg, tracked: !!argInfo }
89
94
  ],
90
95
  tags: adjustTags('anchor', objInfo?.tags, `${arg}`.length, argInfo?.tags),
91
96
  history: Array.from(history),
@@ -109,8 +114,10 @@ module.exports = function(core) {
109
114
  });
110
115
 
111
116
  ['big', 'blink', 'italics', 'small', 'strike', 'sub', 'fixed'].forEach((method) => {
117
+ const name = `String.prototype.${method}`;
118
+
112
119
  patcher.patch(String.prototype, method, {
113
- name: `String.prototype.${method}`,
120
+ name,
114
121
  patchType,
115
122
  post(data) {
116
123
  const { obj, result, hooked, orig } = data;
@@ -123,7 +130,10 @@ module.exports = function(core) {
123
130
  const history = [objInfo];
124
131
 
125
132
  const event = createPropagationEvent({
126
- name: `String.prototype.${method}`,
133
+ name,
134
+ moduleName: 'String',
135
+ methodName: `prototype.${method}`,
136
+ context: `${objInfo.value}.${method}()`,
127
137
  object: {
128
138
  value: objInfo.value,
129
139
  tracked: true
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  'use strict';
17
- const { join } = require('@contrast/common');
17
+ const { join, inspect } = require('@contrast/common');
18
18
  const { patchType } = require('../../common');
19
19
  const { createSubsetTags } = require('../../../tag-utils');
20
20
 
@@ -28,24 +28,29 @@ module.exports = function(core) {
28
28
  } = core;
29
29
 
30
30
  function getPropagationEvent(data, res, objInfo, start) {
31
- const { name, args, result, hooked, orig } = data;
32
- const tags = createSubsetTags(objInfo.tags, start, res.length - 1);
31
+ const { name, args: origArgs, result, hooked, orig } = data;
32
+ const tags = createSubsetTags(objInfo.tags, start, res.length);
33
33
  if (!tags) return;
34
34
 
35
+ const args = origArgs.map((arg) => {
36
+ const argInfo = tracker.getData(arg);
37
+ return {
38
+ value: argInfo ? argInfo.value : inspect(arg),
39
+ tracked: !!argInfo
40
+ };
41
+ });
42
+
35
43
  return createPropagationEvent({
36
44
  name,
45
+ moduleName: 'String',
46
+ methodName: 'prototype.match',
47
+ context: `'${objInfo.value}'.match(${join(args.map(a => a.value), ', ')})`,
37
48
  history: [objInfo],
38
49
  object: {
39
50
  value: objInfo.value,
40
51
  tracked: true,
41
52
  },
42
- args: args.map((arg) => {
43
- const argInfo = tracker.getData(arg);
44
- return {
45
- value: argInfo ? argInfo.value : arg.toString(),
46
- tracked: !!argInfo
47
- };
48
- }),
53
+ args,
49
54
  tags,
50
55
  result: {
51
56
  value: join(result),
@@ -89,7 +94,7 @@ module.exports = function(core) {
89
94
  const res = result[i];
90
95
  if (!res || res === obj) continue;
91
96
  const start = obj.indexOf(res, idx);
92
- idx += hasCaptureGroups ? 0 : res.length;
97
+ idx += hasCaptureGroups ? 0 : start + res.length;
93
98
  const event = getPropagationEvent(data, res, objInfo, start);
94
99
  if (event) {
95
100
  const { extern } = tracker.track(res, event);
@@ -16,8 +16,11 @@
16
16
  'use strict';
17
17
 
18
18
  const {
19
- DataflowTag: { UNTRUSTED }
19
+ DataflowTag: { UNTRUSTED },
20
+ match: origMatch,
21
+ substring
20
22
  } = require('@contrast/common');
23
+ const { inspect, join } = require('@contrast/common');
21
24
  const { patchType } = require('../../common');
22
25
  const { createSubsetTags, createAppendTags } = require('../../../tag-utils');
23
26
 
@@ -63,7 +66,7 @@ module.exports = function(core) {
63
66
  replace: str.substring(str.indexOf(match) + match.length, str.length)
64
67
  }
65
68
  ].forEach(({ regex, replace }) => {
66
- if (ret && ret.match(regex)) {
69
+ if (ret && origMatch(ret, regex)) {
67
70
  // If the match string is tracked, we can actually use the patched replace
68
71
  // to keep track of its tag ranges
69
72
  if (tracker.getData(replace)) {
@@ -77,7 +80,7 @@ module.exports = function(core) {
77
80
  const numberedGroupMatches = replacementType !== 'function' && replacement.match(/\$[1-9][0-9]|\$[1-9]/g);
78
81
  if (numberedGroupMatches) {
79
82
  numberedGroupMatches.forEach((numberedGroup) => {
80
- const group = Number(numberedGroup.substring(1));
83
+ const group = Number(substring(numberedGroup, 1));
81
84
  ret = origReplace.call(ret, numberedGroup, captureGroups[group - 1]);
82
85
  });
83
86
  }
@@ -112,7 +115,7 @@ module.exports = function(core) {
112
115
  const { _accumOffset, _accumTags } = data;
113
116
  const { replacement, replacementInfo } = getReplacementInfo(data, args, parsedArgs);
114
117
 
115
- const preTags = createSubsetTags(_accumTags, 0, _accumOffset + matchIdx - 1);
118
+ const preTags = createSubsetTags(_accumTags, 0, _accumOffset + matchIdx);
116
119
  const postTags = createSubsetTags(_accumTags, _accumOffset + matchIdx + match.length, str.length - matchIdx - match.length);
117
120
  data._accumOffset += (replacement.length - match.length);
118
121
  if (preTags || postTags || replacementInfo) {
@@ -130,8 +133,9 @@ module.exports = function(core) {
130
133
 
131
134
  return core.assess.dataflow.propagation.stringInstrumentation.replace = {
132
135
  install() {
136
+ const name = 'String.prototype.replace';
133
137
  patcher.patch(String.prototype, 'replace', {
134
- name: 'String.prototype.replace',
138
+ name,
135
139
  patchType,
136
140
  pre(data) {
137
141
  if (!sources.getStore()?.assess || instrumentation.isLocked()) return;
@@ -160,23 +164,27 @@ module.exports = function(core) {
160
164
  return;
161
165
  }
162
166
 
163
- const { _replacementInfo, obj, args, result, hooked, orig } = data;
167
+ const { _replacementInfo, obj, args: origArgs, result, hooked, orig } = data;
168
+ const args = [{
169
+ value: inspect(origArgs[0]),
170
+ tracked: !!tracker.getData(origArgs[0])
171
+ },
172
+ {
173
+ value: data._replacement,
174
+ tracked: !!_replacementInfo
175
+ }];
164
176
 
165
177
  const event = createPropagationEvent({
166
- name: 'String.prototype.replace',
178
+ name,
179
+ moduleName: 'String',
180
+ methodName: 'prototype.replace',
181
+ context: `'${obj}'.replace(${join(args.map(a => a.value), ', ')})`,
167
182
  history: Array.from(data._history),
168
183
  object: {
169
184
  value: obj,
170
185
  tracked: !!data._objInfo
171
186
  },
172
- args: [{
173
- value: args[0].toString(),
174
- tracked: !!tracker.getData(args[0])
175
- },
176
- {
177
- value: data._replacement,
178
- tracked: !!_replacementInfo
179
- }],
187
+ args,
180
188
  result: {
181
189
  value: result,
182
190
  tracked: true
@@ -14,6 +14,7 @@
14
14
  */
15
15
  'use strict';
16
16
  const { patchType } = require('../../common');
17
+ const { inspect, join } = require('@contrast/common');
17
18
  const { createSubsetTags } = require('../../../tag-utils');
18
19
 
19
20
  module.exports = function(core) {
@@ -36,7 +37,7 @@ module.exports = function(core) {
36
37
  end = obj.length - (Math.abs(end) || 0);
37
38
  }
38
39
 
39
- const subsetLen = (hasSingleArg ? obj.length - start : Math.abs(end - start)) - 1;
40
+ const subsetLen = (hasSingleArg ? obj.length - start : Math.abs(end - start));
40
41
 
41
42
  return {
42
43
  startIdx: start,
@@ -52,7 +53,7 @@ module.exports = function(core) {
52
53
  name,
53
54
  patchType,
54
55
  post(data) {
55
- const { name, args, obj, result, hooked, orig } = data;
56
+ const { name, args: origArgs, obj, result, hooked, orig } = data;
56
57
  if (!result || !sources.getStore() || instrumentation.isLocked()) return;
57
58
 
58
59
  const objInfo = tracker.getData(obj);
@@ -68,22 +69,29 @@ module.exports = function(core) {
68
69
  const tags = createSubsetTags(objInfo.tags, startIdx, subsetLen);
69
70
  if (!tags) return;
70
71
 
72
+ const args = origArgs.map((arg) => ({
73
+ value: inspect(arg),
74
+ tracked: false
75
+ }));
76
+
71
77
  const event = createPropagationEvent({
72
78
  name,
79
+ moduleName: 'String',
80
+ methodName: 'prototype.slice',
81
+ context: `'${objInfo.value}'.slice(${join(args.map(a => a.value), ', ')})`,
73
82
  history: [objInfo],
74
83
  object: {
75
84
  value: obj,
76
85
  tracked: true,
77
86
  },
78
- args: args.map((arg) => ({
79
- value: arg.toString(),
80
- tracked: false
81
- })),
87
+ args,
82
88
  result: {
83
89
  value: result,
84
90
  tracked: true
85
91
  },
86
92
  tags,
93
+ source: 'O',
94
+ target: 'R',
87
95
  stacktraceOpts: {
88
96
  constructorOpt: hooked,
89
97
  prependFrames: [orig]
@@ -16,7 +16,7 @@
16
16
  'use strict';
17
17
 
18
18
  const { patchType } = require('../../common');
19
- const { join } = require('@contrast/common');
19
+ const { join, inspect } = require('@contrast/common');
20
20
  const { createSubsetTags } = require('../../../tag-utils');
21
21
 
22
22
 
@@ -37,16 +37,16 @@ module.exports = function(core) {
37
37
  name,
38
38
  patchType,
39
39
  post(data) {
40
- const { name, args, obj, result, hooked, orig } = data;
40
+ const { name, args: origArgs, obj, result, hooked, orig } = data;
41
41
  if (
42
42
  !obj ||
43
43
  !result ||
44
- args.length === 0 ||
44
+ origArgs.length === 0 ||
45
45
  result.length === 0 ||
46
46
  !sources.getStore() ||
47
47
  typeof obj !== 'string' ||
48
48
  instrumentation.isLocked() ||
49
- (args.length === 1 && args[0] == null)
49
+ (origArgs.length === 1 && origArgs[0] == null)
50
50
  ) return;
51
51
 
52
52
  const objInfo = tracker.getData(obj);
@@ -60,23 +60,27 @@ module.exports = function(core) {
60
60
  const objSubstr = obj.substring(start, start + res.length);
61
61
  const objSubstrInfo = tracker.getData(objSubstr);
62
62
  if (objSubstrInfo) {
63
- const tags = createSubsetTags(objInfo.tags, start, res.length - 1);
63
+ const tags = createSubsetTags(objInfo.tags, start, res.length);
64
64
  if (!tags) continue;
65
65
 
66
+ const args = origArgs.map((arg) => {
67
+ const argInfo = tracker.getData(arg);
68
+ return {
69
+ value: argInfo ? argInfo.value : inspect(arg),
70
+ tracked: !!argInfo
71
+ };
72
+ });
66
73
  const event = createPropagationEvent({
67
74
  name,
75
+ moduleName: 'String',
76
+ methodName: 'prototype.slice',
77
+ context: `'${objInfo.value}'.split(${join(args.map(a => a.value), ', ')})`,
68
78
  history: [objInfo],
69
79
  object: {
70
80
  value: obj,
71
81
  tracked: true,
72
82
  },
73
- args: args.map((arg) => {
74
- const argInfo = tracker.getData(arg);
75
- return {
76
- value: argInfo ? argInfo.value : arg.toString(),
77
- tracked: !!argInfo
78
- };
79
- }),
83
+ args,
80
84
  tags,
81
85
  result: {
82
86
  value: join(result),
@@ -16,6 +16,7 @@
16
16
  'use strict';
17
17
 
18
18
  const { createSubsetTags } = require('../../../tag-utils');
19
+ const { join, inspect } = require('@contrast/common');
19
20
  const { patchType } = require('../../common');
20
21
 
21
22
  module.exports = function(core) {
@@ -29,7 +30,7 @@ module.exports = function(core) {
29
30
 
30
31
  function calculateSubsetRangeForSubstr({ args, result }) {
31
32
  const startIdx = args[0] || 0;
32
- const subsetLen = args[1] === undefined ? result.length - startIdx - 1 : args[1] - 1;
33
+ const subsetLen = args[1] === undefined ? result.length - startIdx : args[1];
33
34
 
34
35
  return {
35
36
  startIdx,
@@ -40,7 +41,7 @@ module.exports = function(core) {
40
41
  function calculateSubsetRangeForSubstring({ obj, args }) {
41
42
  const hasSingleArg = args[1] === undefined;
42
43
  const startIdx = hasSingleArg ? args[0] : Math.min(...args);
43
- const subsetLen = (hasSingleArg ? obj.length - args[0] : Math.abs(args[1] - args[0])) - 1;
44
+ const subsetLen = (hasSingleArg ? obj.length - args[0] : Math.abs(args[1] - args[0]));
44
45
 
45
46
  return {
46
47
  startIdx,
@@ -60,8 +61,8 @@ module.exports = function(core) {
60
61
  name: `String.prototype.${method}`,
61
62
  patchType,
62
63
  post(data) {
63
- const { obj, args, result, name, hooked, orig } = data;
64
- if (!result || !sources.getStore() || instrumentation.isLocked()) return;
64
+ const { obj, args: origArgs, result, name, hooked, orig } = data;
65
+ if (!result || !sources.getStore()?.assess || instrumentation.isLocked()) return;
65
66
 
66
67
  const objInfo = tracker.getData(obj);
67
68
  if (!objInfo) return;
@@ -78,17 +79,21 @@ module.exports = function(core) {
78
79
 
79
80
  if (!tags) return;
80
81
 
82
+ const args = origArgs.map((arg) => ({
83
+ value: inspect(arg),
84
+ tracked: false
85
+ }));
81
86
  const event = createPropagationEvent({
82
87
  name,
88
+ moduleName: 'String',
89
+ methodName: 'prototype.substring',
90
+ context: `'${objInfo.value}'.substring(${join(args.map(a => a.value), ', ')})`,
83
91
  history: [objInfo],
84
92
  object: {
85
93
  value: obj,
86
94
  tracked: true,
87
95
  },
88
- args: args.map((arg) => ({
89
- value: arg.toString(),
90
- tracked: false
91
- })),
96
+ args,
92
97
  result: {
93
98
  value: result,
94
99
  tracked: true
@@ -101,6 +106,11 @@ module.exports = function(core) {
101
106
  },
102
107
  target: 'R',
103
108
  });
109
+
110
+ if (!event) {
111
+ return;
112
+ }
113
+
104
114
  const { extern } = tracker.track(result, event);
105
115
 
106
116
  if (extern) {
@@ -52,7 +52,7 @@ module.exports = function(core) {
52
52
  const start = presetStart || obj.indexOf(result);
53
53
  const newTags = {};
54
54
  const objTags = objInfo.tags || {};
55
- Object.assign(newTags, createSubsetTags(objTags, start, result.length - 1));
55
+ Object.assign(newTags, createSubsetTags(objTags, start, result.length));
56
56
 
57
57
  if (!newTags.untrusted) {
58
58
  return;
@@ -60,6 +60,9 @@ module.exports = function(core) {
60
60
 
61
61
  const event = createPropagationEvent({
62
62
  name: `String.prototype.${methodName}`,
63
+ moduleName: 'String',
64
+ methodName: `prototype.${methodName}`,
65
+ context: `'${obj}'.${methodName}()`,
63
66
  history,
64
67
  object: {
65
68
  value: obj,
@@ -34,8 +34,10 @@ module.exports = function(core) {
34
34
 
35
35
  return core.assess.dataflow.propagation.unescape = {
36
36
  install() {
37
+ const name = 'global.unescape';
38
+
37
39
  patcher.patch(global, 'unescape', {
38
- name: 'global.unescape',
40
+ name,
39
41
  patchType,
40
42
  post(data) {
41
43
  const { args, result, hooked, orig } = data;
@@ -53,7 +55,10 @@ module.exports = function(core) {
53
55
  if (!Object.keys(newTags).length) return;
54
56
 
55
57
  const event = createPropagationEvent({
56
- name: 'global.unescape',
58
+ name,
59
+ moduleName: 'global',
60
+ methodName: 'unescape',
61
+ context: `unescape('${argInfo.value}')`,
57
62
  object: {
58
63
  value: createObjectLabel('global'),
59
64
  tracked: false
@@ -65,6 +70,8 @@ module.exports = function(core) {
65
70
  args: [{ value: argInfo.value, tracked: true }],
66
71
  tags: newTags,
67
72
  history,
73
+ source: 'P',
74
+ target: 'R',
68
75
  removedTags: [WEAK_URL_ENCODED],
69
76
  stacktraceOpts: {
70
77
  constructorOpt: hooked,
@@ -34,8 +34,10 @@ module.exports = function(core) {
34
34
  install() {
35
35
  depHooks.resolve({ name: 'url' }, (url, version) => {
36
36
  ['domainToASCII', 'domainToUnicode'].forEach((method) => {
37
+ const name = `url.${method}`;
38
+
37
39
  patcher.patch(url, method, {
38
- name: `url.${method}`,
40
+ name,
39
41
  patchType,
40
42
  post(data) {
41
43
  const { args, result, hooked, orig } = data;
@@ -49,7 +51,10 @@ module.exports = function(core) {
49
51
  const history = [argInfo];
50
52
 
51
53
  const event = createPropagationEvent({
52
- name: `url.${method}`,
54
+ name,
55
+ moduleName: 'url',
56
+ methodName: method,
57
+ context: `url.${method}('${argInfo.value}')`,
53
58
  object: {
54
59
  value: createModuleLabel('url', version),
55
60
  tracked: false
@@ -28,6 +28,7 @@ module.exports = function(core) {
28
28
  };
29
29
 
30
30
  require('./domain-parsers')(core);
31
+ require('./url')(core);
31
32
 
32
33
  return urlInstrumentation;
33
34
  };