@contrast/assess 1.73.0 → 1.74.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.
@@ -51,8 +51,7 @@ module.exports = function(core) {
51
51
  command,
52
52
  secondArg,
53
53
  thirdArg,
54
- hooked,
55
- orig
54
+ hooked
56
55
  ) {
57
56
  const strInfo = tracker.getData(command);
58
57
  if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags))
@@ -91,7 +90,6 @@ module.exports = function(core) {
91
90
  source: 'P0',
92
91
  stacktraceOpts: {
93
92
  constructorOpt: hooked,
94
- prependFrames: [orig],
95
93
  },
96
94
  });
97
95
 
@@ -120,8 +118,7 @@ module.exports = function(core) {
120
118
  commandInfo,
121
119
  origArgs,
122
120
  options,
123
- hooked,
124
- orig
121
+ hooked
125
122
  ) {
126
123
  if (!Array.isArray(origArgs) || !origArgs?.length) return;
127
124
 
@@ -177,7 +174,6 @@ module.exports = function(core) {
177
174
  source: 'P1',
178
175
  stacktraceOpts: {
179
176
  contructorOpt: hooked,
180
- prependFrames: [orig],
181
177
  },
182
178
  });
183
179
 
@@ -113,7 +113,6 @@ module.exports = function(core) {
113
113
  source: 'P0',
114
114
  stacktraceOpts: {
115
115
  constructorOpt: data.hooked,
116
- prependFrames: [data.orig]
117
116
  },
118
117
  });
119
118
 
@@ -112,7 +112,6 @@ module.exports = function(core) {
112
112
  source: 'P0',
113
113
  stacktraceOpts: {
114
114
  constructorOpt: data.hooked,
115
- prependFrames: [data.orig]
116
115
  },
117
116
  });
118
117
 
@@ -140,7 +140,6 @@ module.exports = function(core) {
140
140
  source: `P${valueIndex}`,
141
141
  stacktraceOpts: {
142
142
  constructorOpt: data.hooked,
143
- prependFrames: [data.orig]
144
143
  },
145
144
  });
146
145
 
@@ -101,7 +101,6 @@ module.exports = function(core) {
101
101
  source: `P${i}`,
102
102
  stacktraceOpts: {
103
103
  contructorOpt: data.hooked,
104
- prependFrames: [data.orig],
105
104
  },
106
105
  });
107
106
 
@@ -65,7 +65,7 @@ module.exports = function (core) {
65
65
  patcher.patch(global.ContrastMethods, 'Function', {
66
66
  name: 'global.ContrastMethods.Function',
67
67
  patchType,
68
- pre({ args: origArgs, hooked, orig, name }) {
68
+ pre({ args: origArgs, hooked, name }) {
69
69
  if (!getSinkContext(ruleId)) return;
70
70
 
71
71
  const fnBody = origArgs[origArgs.length - 1];
@@ -131,7 +131,6 @@ module.exports = function (core) {
131
131
  source: `P${origArgs.length - 1}`,
132
132
  stacktraceOpts: {
133
133
  contructorOpt: hooked,
134
- prependFrames: [orig],
135
134
  },
136
135
  });
137
136
 
@@ -108,7 +108,6 @@ module.exports = function(core) {
108
108
  source: 'P0',
109
109
  stacktraceOpts: {
110
110
  constructorOpt: data.hooked,
111
- prependFrames: [data.orig]
112
111
  },
113
112
  });
114
113
 
@@ -139,7 +139,6 @@ module.exports = function(core) {
139
139
  source: 'P0',
140
140
  stacktraceOpts: {
141
141
  constructorOpt: data.hooked,
142
- prependFrames: [data.orig]
143
142
  },
144
143
  tags: urlTags,
145
144
  });
@@ -76,7 +76,7 @@ module.exports = function(core) {
76
76
  WEAK_URL_ENCODED,
77
77
  ];
78
78
 
79
- const preHook = (moduleName, responseName, method) => ({ args, obj: response, result, hooked, orig }) => {
79
+ const preHook = (moduleName, responseName, method) => ({ args, obj: response, result, hooked }) => {
80
80
  const methodName = `${`${responseName}.prototype`}.${method}`;
81
81
  const name = `${moduleName}.${methodName}`;
82
82
  const sourceContext = getSinkContext(ruleId);
@@ -113,7 +113,6 @@ module.exports = function(core) {
113
113
  source: 'P0',
114
114
  stacktraceOpts: {
115
115
  constructorOpt: hooked,
116
- prependFrames: [orig]
117
116
  },
118
117
  tags: strInfo.tags,
119
118
  });
@@ -126,7 +126,6 @@ module.exports = function(core) {
126
126
  source: `P${isBackRoute ? 1 : 0}`,
127
127
  stacktraceOpts: {
128
128
  constructorOpt: data.hooked,
129
- prependFrames: [data.orig]
130
129
  },
131
130
  });
132
131
 
@@ -103,7 +103,6 @@ module.exports = function(core) {
103
103
  source: 'P0',
104
104
  stacktraceOpts: {
105
105
  contructorOpt: data.hooked,
106
- prependFrames: [data.orig]
107
106
  },
108
107
  });
109
108
 
@@ -120,7 +120,6 @@ module.exports = function (core) {
120
120
  source: `P${argIdx}`,
121
121
  stacktraceOpts: {
122
122
  constructorOpt: data.hooked,
123
- prependFrames: [data.orig]
124
123
  },
125
124
  tags: strInfo.tags,
126
125
  });
@@ -92,7 +92,6 @@ module.exports = function (core) {
92
92
  source: 'P0',
93
93
  stacktraceOpts: {
94
94
  contructorOpt: data.hooked,
95
- prependFrames: [data.orig],
96
95
  },
97
96
  });
98
97
 
@@ -109,7 +109,6 @@ module.exports = function(core) {
109
109
  source: 'P0',
110
110
  stacktraceOpts: {
111
111
  contructorOpt: data.hooked,
112
- prependFrames: [data.orig]
113
112
  },
114
113
  });
115
114
 
@@ -82,7 +82,6 @@ module.exports = function(core) {
82
82
  source: 'P0',
83
83
  stacktraceOpts: {
84
84
  contructorOpt: data.hooked,
85
- prependFrames: [data.orig]
86
85
  },
87
86
  });
88
87
 
@@ -99,7 +99,6 @@ module.exports = function(core) {
99
99
  source: 'P0',
100
100
  stacktraceOpts: {
101
101
  constructorOpt: data.hooked,
102
- prependFrames: [data.orig]
103
102
  },
104
103
  });
105
104
 
@@ -79,7 +79,6 @@ module.exports = function(core) {
79
79
  },
80
80
  stacktraceOpts: {
81
81
  constructorOpt: data.hooked,
82
- prependFrames: [data.orig]
83
82
  },
84
83
  };
85
84
  }
@@ -69,7 +69,7 @@ module.exports = function (core) {
69
69
  around(next, data) {
70
70
  if (!getSinkContext(ruleId) || !data.args[0]) return next();
71
71
 
72
- const { args, hooked, orig } = data;
72
+ const { args, hooked } = data;
73
73
  const query = typeof args[0] === 'string' ? args[0] : args[0].query;
74
74
 
75
75
  try {
@@ -121,7 +121,6 @@ module.exports = function (core) {
121
121
  source: 'P0',
122
122
  stacktraceOpts: {
123
123
  contructorOpt: hooked,
124
- prependFrames: [orig],
125
124
  },
126
125
  });
127
126
 
@@ -93,7 +93,6 @@ module.exports = function(core) {
93
93
  source: 'P0',
94
94
  stacktraceOpts: {
95
95
  contructorOpt: data.hooked,
96
- prependFrames: [data.orig]
97
96
  },
98
97
  });
99
98
 
@@ -142,7 +142,7 @@ module.exports = function (core) {
142
142
  return argsInfo;
143
143
  }
144
144
 
145
- function around(next, { args: origArgs, hooked, orig, name }) {
145
+ function around(next, { args: origArgs, hooked, name }) {
146
146
  if (!getSinkContext(ruleId) || isLocked(ruleId)) return next();
147
147
 
148
148
  const methodPath = StringPrototypeSplit.call(name, '.');
@@ -232,7 +232,6 @@ module.exports = function (core) {
232
232
  source: `P${vulnerableArg.idx}`,
233
233
  stacktraceOpts: {
234
234
  contructorOpt: hooked,
235
- prependFrames: [orig],
236
235
  },
237
236
  });
238
237
 
@@ -21,6 +21,7 @@ module.exports = function (core) {
21
21
  const sources = core.assess.dataflow.sources = {};
22
22
 
23
23
  core.initComponentSync(require('./handler'));
24
+ core.initComponentSync(require('./install/@sap'));
24
25
  require('./install/express')(core);
25
26
  require('./install/fastify')(core);
26
27
  require('./install/hapi')(core);
@@ -0,0 +1,132 @@
1
+ /*
2
+ * Copyright: 2026 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 { kComponentName, ComponentBase } = require('@contrast/core/lib/ioc/core');
19
+ const {
20
+ InputType,
21
+ traverseValues,
22
+ primordials: { ArrayPrototypeJoin, StringPrototypeSlice },
23
+ } = require('@contrast/common');
24
+ const { patchType } = require('../common');
25
+
26
+ const COMPONENT_NAME = 'assess.dataflow.sources.sapServiceInstrumentation';
27
+
28
+ module.exports = class SAPInstrumentation extends ComponentBase {
29
+ static [kComponentName] = COMPONENT_NAME;
30
+
31
+ install() {
32
+ const {
33
+ depHooks,
34
+ patcher,
35
+ assess,
36
+ } = this.core;
37
+
38
+ depHooks.resolve({ name: '@sap/cds', version: '*', file: 'lib/srv/cds.Service.js' }, (Service) => {
39
+ patcher.patch(Service.prototype, 'dispatch', {
40
+ name: '@sap/cds.Service.prototype.dispatch',
41
+ patchType,
42
+ pre(data) {
43
+ const sourceContext = assess.getSourceContext();
44
+ if (!sourceContext || sourceContext.cdsDispatchHandled) return;
45
+
46
+ // dispatch can run multiple times e.g. with $batch
47
+ sourceContext.cdsDispatchHandled = true;
48
+ const cdsReq = data.args[0];
49
+
50
+ // this should also take care of cdsReq.req._data and cdsReq.data
51
+ if (cdsReq?.req?.body) {
52
+ assess.dataflow.sources.handle({
53
+ context: 'req.body',
54
+ data: cdsReq.req.body,
55
+ name: data.name,
56
+ inputType: InputType.BODY,
57
+ sourceContext,
58
+ stacktraceOpts: {
59
+ constructorOpt: data.hooked,
60
+ },
61
+ });
62
+
63
+ if (cdsReq?.req?._raw) {
64
+ assess.dataflow.sources.handle({
65
+ context: 'req',
66
+ data: cdsReq,
67
+ name: data.name,
68
+ keys: ['_raw'],
69
+ inputType: InputType.BODY,
70
+ sourceContext,
71
+ stacktraceOpts: {
72
+ constructorOpt: data.hooked,
73
+ },
74
+ });
75
+ }
76
+ }
77
+
78
+ if (cdsReq?.params?.length) {
79
+ assess.dataflow.sources.handle({
80
+ context: 'req.params',
81
+ data: cdsReq.params,
82
+ name: data.name,
83
+ inputType: InputType.URL_PARAMETER,
84
+ sourceContext,
85
+ stacktraceOpts: {
86
+ constructorOpt: data.hooked,
87
+ },
88
+ });
89
+ }
90
+
91
+ // Handle cdsReq.query (CQN AST) since the OData/REST URL parser is deadzoned.
92
+ // Only track user-controlled leaf nodes, which are `val` and `ref` values.
93
+ // Other strings in the CQN are grammar tokens or schema-derived identifiers.
94
+ // Note: this also handles cdsReq.req._target since it's an alias for query values e.g.
95
+ // req._target.ref[0].where[2].val === cdsReq.query.SELECT.from.ref[0].where[2].val
96
+ const queries = Array.isArray(cdsReq?.query)
97
+ ? cdsReq.query
98
+ : cdsReq?.query ? [cdsReq.query] : [];
99
+
100
+ for (const cqn of queries) {
101
+ if (!cqn || typeof cqn !== 'object') continue;
102
+
103
+ traverseValues(cqn, (path, _type, _value, parent) => {
104
+ const lastKey = path[path.length - 1];
105
+ const inValObject = !Array.isArray(parent) && lastKey === 'val';
106
+ const inRefArray = Array.isArray(parent) && path[path.length - 2] === 'ref';
107
+ if (!inValObject && !inRefArray) return;
108
+
109
+ // Build context as 'req.query' + path without the trailing leaf key
110
+ // (sources.handle appends the leaf key automatically).
111
+ const fullPath = ArrayPrototypeJoin.call(path, '.');
112
+ const lastKeyLen = `${lastKey}`.length + (path.length > 1 ? 1 : 0);
113
+ const prefix = StringPrototypeSlice.call(fullPath, 0, fullPath.length - lastKeyLen);
114
+
115
+ assess.dataflow.sources.handle({
116
+ context: prefix ? `req.query.${prefix}` : 'req.query',
117
+ data: parent,
118
+ name: data.name,
119
+ keys: [lastKey],
120
+ inputType: InputType.URL_PARAMETER,
121
+ sourceContext,
122
+ stacktraceOpts: {
123
+ constructorOpt: data.hooked,
124
+ },
125
+ });
126
+ });
127
+ }
128
+ }
129
+ });
130
+ });
131
+ }
132
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.73.0",
3
+ "version": "1.74.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)",
@@ -29,10 +29,10 @@
29
29
  "@contrast/logger": "1.36.2",
30
30
  "@contrast/patcher": "1.35.2",
31
31
  "@contrast/rewriter": "1.40.3",
32
- "@contrast/route-coverage": "1.57.0",
32
+ "@contrast/route-coverage": "1.57.1",
33
33
  "@contrast/scopes": "1.33.2",
34
34
  "@contrast/sources": "1.9.2",
35
- "@contrast/stack-trace-factory": "1.3.3",
35
+ "@contrast/stack-trace-factory": "1.4.0",
36
36
  "semver": "7.6.0"
37
37
  }
38
38
  }