@itentialopensource/adapter-utils 5.10.23 → 5.10.25

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.
@@ -32,6 +32,7 @@ const { HttpsProxyAgent } = require('https-proxy-agent');
32
32
  const { SocksProxyAgent } = require('socks-proxy-agent');
33
33
  const AsyncLockCl = require('async-lock');
34
34
  const FormData = require('form-data');
35
+ const validator = require('validator');
35
36
 
36
37
  const ThrottleCl = require(path.join(__dirname, '/throttle.js'));
37
38
 
@@ -134,6 +135,45 @@ let sslCertFilePath = null;
134
135
 
135
136
  const mfaStepsResults = []; // keeps requested result for each step
136
137
 
138
+ // SSRF Protection: Validate requests to prevent malicious requests
139
+ function validateRequestSecurity(requestHeader, options) {
140
+ const { hostname, port: requestPort, path: requestPath } = requestHeader;
141
+ if (!hostname) {
142
+ throw new Error('Hostname is required');
143
+ }
144
+ if (!requestPort) {
145
+ throw new Error('Port is required');
146
+ }
147
+
148
+ const isValidIP = validator.isIP(hostname);
149
+ let isValidDomain = false;
150
+ if (!isValidIP) {
151
+ // Only validate as FQDN if it's NOT an IP address
152
+ const fqdnOptions = {
153
+ require_tld: false,
154
+ allow_underscores: true,
155
+ ignore_max_length: true
156
+ };
157
+
158
+ isValidDomain = validator.isFQDN(hostname, fqdnOptions);
159
+ }
160
+
161
+ if (!isValidIP && !isValidDomain) {
162
+ throw new Error('Hostname must be a valid IP address or domain name');
163
+ }
164
+
165
+ if (!validator.isPort(String(requestPort))) {
166
+ throw new Error('Invalid port number');
167
+ }
168
+
169
+ if (requestPath && options.maxPathLength && options.maxPathLength > 0) {
170
+ if (requestPath.length > options.maxPathLength) {
171
+ throw new Error(`Path too long (${requestPath.length} > ${options.maxPathLength})`);
172
+ }
173
+ }
174
+ return requestHeader;
175
+ }
176
+
137
177
  /* CONNECTOR ENGINE INTERNAL FUNCTIONS */
138
178
  /** Wait for adapter-mongo to be available.
139
179
  * @summary adapter may load before adapter-mongo but it requires UPDATE: test if dbUtil object can connect.
@@ -644,7 +684,8 @@ function makeRequest(request, entitySchema, callProperties, startTrip, attempt,
644
684
  }
645
685
 
646
686
  // make the call to System
647
- const httpRequest = useProt.request(request.header, (res) => {
687
+ const validatedHeader = validateRequestSecurity(request.header, {});
688
+ const httpRequest = useProt.request(validatedHeader, (res) => {
648
689
  let respStr = '';
649
690
  if (res.headers['content-encoding'] !== 'gzip') {
650
691
  res.setEncoding('utf8');
@@ -869,7 +910,18 @@ function makeRequest(request, entitySchema, callProperties, startTrip, attempt,
869
910
  // handle any exception
870
911
  const tripDiff = process.hrtime(roundTTime);
871
912
  const tripEnd = `${Math.round(((tripDiff[0] * NS_PER_SEC) + tripDiff[1]) / 1000000)}ms`;
872
- const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue during make request');
913
+
914
+ // Check if this is a validation error from validateRequestSecurity
915
+ const isValidationError = e.message && (
916
+ e.message.includes('Hostname is required')
917
+ || e.message.includes('Port is required')
918
+ || e.message.includes('Hostname must be a valid IP address or domain name')
919
+ || e.message.includes('Invalid port number')
920
+ || e.message.includes('Path length exceeds maximum allowed')
921
+ );
922
+
923
+ const errorMessage = isValidationError ? 'Request security validation failed' : 'Issue during make request';
924
+ const errorObj = transUtilInst.checkAndReturn(e, origin, errorMessage);
873
925
  errorObj.metrics = {
874
926
  code: 'translate_error',
875
927
  redirects: attempt,
@@ -209,7 +209,7 @@ function handleRestRequest(request, entityId, entitySchema, callProperties, filt
209
209
  retError = JSON.parse(perror.response.trim());
210
210
  } catch (ex) {
211
211
  // otherwise log parse failure but still return the unparsed error
212
- log.warn(`${origin}: An error occurred parsing the error JSON: ${ex}: Error Response is: ${perror}`);
212
+ log.warn(`${origin}: An error occurred parsing the error JSON: ${ex}: Error Response is: ${perror.response}`);
213
213
  }
214
214
  }
215
215
  }
@@ -454,7 +454,7 @@ function handleRestRequest(request, entityId, entitySchema, callProperties, filt
454
454
  retResponse = JSON.parse(resObj.response.trim());
455
455
  } catch (ex) {
456
456
  // otherwise log parse failure and return the unparsed response
457
- log.warn(`${origin}: An error occurred parsing the resulting JSON: ${ex}: Actual Response is: ${resObj}`);
457
+ log.warn(`${origin}: An error occurred parsing the resulting JSON: ${ex}: Actual Response is: ${resObj.response}`);
458
458
  return callback(retObject);
459
459
  }
460
460
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itentialopensource/adapter-utils",
3
- "version": "5.10.23",
3
+ "version": "5.10.25",
4
4
  "description": "Itential Adapter Utility Libraries",
5
5
  "scripts": {
6
6
  "postinstall": "node utils/setup.js",
@@ -37,11 +37,12 @@
37
37
  "json-query": "^2.2.2",
38
38
  "jsontoxml": "^1.0.1",
39
39
  "jsonwebtoken": "^9.0.1",
40
- "mongodb": "^3.7.4",
41
40
  "mocha": "^10.7.3",
41
+ "mongodb": "^3.7.4",
42
42
  "readline-sync": "^1.4.10",
43
43
  "socks-proxy-agent": "^8.0.1",
44
44
  "uuid": "^9.0.0",
45
+ "validator": "^13.15.15",
45
46
  "xml2js": "^0.6.0"
46
47
  },
47
48
  "devDependencies": {