@commercetools-frontend/application-config 25.1.0 → 25.2.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.
@@ -31,12 +31,13 @@ var _possibleConstructorReturn = require('@babel/runtime-corejs3/helpers/possibl
31
31
  var _getPrototypeOf = require('@babel/runtime-corejs3/helpers/getPrototypeOf');
32
32
  var _inherits = require('@babel/runtime-corejs3/helpers/inherits');
33
33
  var _wrapNativeSuper = require('@babel/runtime-corejs3/helpers/wrapNativeSuper');
34
+ var _startsWithInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/starts-with');
34
35
  var _trimInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/trim');
35
36
  var _JSON$stringify = require('@babel/runtime-corejs3/core-js-stable/json/stringify');
36
- var _startsWithInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/starts-with');
37
+ var path$1 = require('path');
37
38
  var _reduceInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/reduce');
38
39
  var _bindInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/bind');
39
- var formatters = require('./formatters-a76b45b9.cjs.dev.js');
40
+ var formatters = require('./formatters-5a68b5ac.cjs.dev.js');
40
41
  var _Set = require('@babel/runtime-corejs3/core-js-stable/set');
41
42
  var _Array$isArray = require('@babel/runtime-corejs3/core-js-stable/array/is-array');
42
43
  var Ajv = require('ajv');
@@ -67,9 +68,10 @@ var _mapInstanceProperty__default = /*#__PURE__*/_interopDefault(_mapInstancePro
67
68
  var _parseInt__default = /*#__PURE__*/_interopDefault(_parseInt);
68
69
  var fs__default = /*#__PURE__*/_interopDefault(fs);
69
70
  var _Reflect$construct__default = /*#__PURE__*/_interopDefault(_Reflect$construct);
71
+ var _startsWithInstanceProperty__default = /*#__PURE__*/_interopDefault(_startsWithInstanceProperty);
70
72
  var _trimInstanceProperty__default = /*#__PURE__*/_interopDefault(_trimInstanceProperty);
71
73
  var _JSON$stringify__default = /*#__PURE__*/_interopDefault(_JSON$stringify);
72
- var _startsWithInstanceProperty__default = /*#__PURE__*/_interopDefault(_startsWithInstanceProperty);
74
+ var path__default$1 = /*#__PURE__*/_interopDefault(path$1);
73
75
  var _reduceInstanceProperty__default = /*#__PURE__*/_interopDefault(_reduceInstanceProperty);
74
76
  var _bindInstanceProperty__default = /*#__PURE__*/_interopDefault(_bindInstanceProperty);
75
77
  var _Set__default = /*#__PURE__*/_interopDefault(_Set);
@@ -218,6 +220,11 @@ const variableSyntax = /\${([ ~:\w.'",\-/()@]+?)}/g;
218
220
  const envRefSyntax = /^env:/g;
219
221
  const intlRefSyntax = /^intl:/g;
220
222
  const filePathRefSyntax = /^path:/g;
223
+
224
+ // Safe regex pattern escaping function
225
+ const escapeRegExp = string => {
226
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
227
+ };
221
228
  const hasVariablePlaceholder = valueOfEnvConfig => typeof valueOfEnvConfig === 'string' &&
222
229
  // Using `{regex}.test()` might cause false positives if called multiple
223
230
  // times on a global regular expression:
@@ -237,8 +244,7 @@ const substituteEnvVariablePlaceholder = (valueOfPlaceholder, matchedString, val
237
244
  if (!hasEnvField) {
238
245
  throw new Error(`Missing environment variable '${requestedEnvVar}' specified in config as 'env:${requestedEnvVar}'.`);
239
246
  }
240
- const escapedMatchedString = matchedString.replace(/[${}:]/g, '\\$&');
241
- return valueOfEnvConfig.replace(new RegExp(`(${escapedMatchedString})+`, 'g'), loadingOptions.processEnv[requestedEnvVar]);
247
+ return valueOfEnvConfig.replace(new RegExp(escapeRegExp(matchedString), 'g'), loadingOptions.processEnv[requestedEnvVar]);
242
248
  };
243
249
  const substituteIntlVariablePlaceholder = (valueOfPlaceholder, matchedString, valueOfEnvConfig, loadingOptions) => {
244
250
  const _valueOfPlaceholder$s3 = valueOfPlaceholder.split(':'),
@@ -255,43 +261,77 @@ const substituteIntlVariablePlaceholder = (valueOfPlaceholder, matchedString, va
255
261
  }
256
262
  const translation = translations[requestedIntlMessageId];
257
263
  const translationValue = isStructuredJson(translation) ? translation.string : translation;
258
- const escapedMatchedString = matchedString.replace(/[${}:]/g, '\\$&');
259
- return valueOfEnvConfig.replace(new RegExp(`(${escapedMatchedString})+`, 'g'), translationValue);
264
+ return valueOfEnvConfig.replace(new RegExp(escapeRegExp(matchedString), 'g'), translationValue);
260
265
  };
261
266
  const substituteFilePathVariablePlaceholder = (valueOfPlaceholder, matchedString, valueOfEnvConfig, loadingOptions) => {
262
267
  const _valueOfPlaceholder$s5 = valueOfPlaceholder.split(':'),
263
268
  _valueOfPlaceholder$s6 = _slicedToArray(_valueOfPlaceholder$s5, 2),
264
269
  filePathOrModule = _valueOfPlaceholder$s6[1];
265
- const content = fs__default$1["default"].readFileSync(require.resolve(filePathOrModule, {
266
- // Relative paths should be resolved from the application folder.
270
+ const resolvedPath = require.resolve(filePathOrModule, {
267
271
  paths: [loadingOptions.applicationPath]
268
- }), {
272
+ });
273
+
274
+ // Security check: Prevent path traversal attacks.
275
+ // require.resolve() already provides protection by only resolving modules
276
+ // accessible from the applicationPath. However, we add an extra layer to
277
+ // prevent access to sensitive system files outside the workspace.
278
+ const normalizedPath = path__default$1["default"].normalize(resolvedPath);
279
+ const applicationPath = path__default$1["default"].normalize(loadingOptions.applicationPath);
280
+
281
+ // Find workspace root by traversing up from applicationPath until we find
282
+ // package.json, pnpm-workspace.yaml, or reach root
283
+ let workspaceRoot = applicationPath;
284
+ let currentPath = applicationPath;
285
+ const rootPath = path__default$1["default"].parse(currentPath).root;
286
+ while (currentPath !== rootPath) {
287
+ const hasPackageJson = fs__default$1["default"].existsSync(path__default$1["default"].join(currentPath, 'package.json'));
288
+ const hasWorkspaceConfig = fs__default$1["default"].existsSync(path__default$1["default"].join(currentPath, 'pnpm-workspace.yaml')) || fs__default$1["default"].existsSync(path__default$1["default"].join(currentPath, 'lerna.json'));
289
+ if (hasPackageJson) {
290
+ workspaceRoot = currentPath;
291
+ if (hasWorkspaceConfig) {
292
+ // Found workspace root
293
+ break;
294
+ }
295
+ }
296
+ currentPath = path__default$1["default"].dirname(currentPath);
297
+ }
298
+ const relativePath = path__default$1["default"].relative(workspaceRoot, normalizedPath);
299
+
300
+ // Path is safe if it's within the workspace root.
301
+ // Use path.relative() to avoid string prefix vulnerabilities (e.g., "/app" vs "/app-evil")
302
+ const isSafePath = !_startsWithInstanceProperty__default["default"](relativePath).call(relativePath, '..') && !path__default$1["default"].isAbsolute(relativePath);
303
+ if (!isSafePath) {
304
+ throw new Error(`Access to files outside workspace directory is not allowed: ${filePathOrModule}`);
305
+ }
306
+ const content = fs__default$1["default"].readFileSync(normalizedPath, {
269
307
  encoding: 'utf-8'
270
308
  });
271
- const escapedMatchedString = matchedString.replace(/[${}:]/g, '\\$&');
272
- return valueOfEnvConfig.replace(new RegExp(`(${escapedMatchedString})+`, 'g'), content);
309
+ return valueOfEnvConfig.replace(new RegExp(escapeRegExp(matchedString), 'g'), content);
273
310
  };
274
311
  const getValueOfPlaceholder = valueWithPlaceholder => valueWithPlaceholder.replace(variableSyntax, (_match, varName) => _trimInstanceProperty__default["default"](varName).call(varName)).replace(/\s/g, '');
275
- const substituteVariablePlaceholders = (config, loadingOptions) => JSON.parse(_JSON$stringify__default["default"](config), (_key, value) => {
276
- // Only strings are allowed
277
- let substitutedValue = value;
278
- if (hasVariablePlaceholder(substitutedValue)) {
279
- const matchResult = substitutedValue.match(variableSyntax);
280
- if (matchResult) {
281
- _forEachInstanceProperty__default["default"](matchResult).call(matchResult, matchedString => {
282
- const valueOfPlaceholder = getValueOfPlaceholder(matchedString);
283
- if (isEnvVariablePlaceholder(valueOfPlaceholder)) {
284
- substitutedValue = substituteEnvVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
285
- } else if (isIntlVariablePlaceholder(valueOfPlaceholder)) {
286
- substitutedValue = substituteIntlVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
287
- } else if (isFilePathVariablePlaceholder(valueOfPlaceholder)) {
288
- substitutedValue = substituteFilePathVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
289
- }
290
- });
312
+ const substituteVariablePlaceholders = (config, loadingOptions) => {
313
+ const result = JSON.parse(_JSON$stringify__default["default"](config), (_key, value) => {
314
+ // Only strings are allowed
315
+ let substitutedValue = value;
316
+ if (hasVariablePlaceholder(substitutedValue)) {
317
+ const matchResult = substitutedValue.match(variableSyntax);
318
+ if (matchResult) {
319
+ _forEachInstanceProperty__default["default"](matchResult).call(matchResult, matchedString => {
320
+ const valueOfPlaceholder = getValueOfPlaceholder(matchedString);
321
+ if (isEnvVariablePlaceholder(valueOfPlaceholder)) {
322
+ substitutedValue = substituteEnvVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
323
+ } else if (isIntlVariablePlaceholder(valueOfPlaceholder)) {
324
+ substitutedValue = substituteIntlVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
325
+ } else if (isFilePathVariablePlaceholder(valueOfPlaceholder)) {
326
+ substitutedValue = substituteFilePathVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
327
+ }
328
+ });
329
+ }
291
330
  }
292
- }
293
- return substitutedValue;
294
- });
331
+ return substitutedValue;
332
+ });
333
+ return result;
334
+ };
295
335
 
296
336
  var customApplicationSchemaJson = {
297
337
  $schema: "http://json-schema.org/draft-07/schema",
@@ -31,12 +31,13 @@ var _possibleConstructorReturn = require('@babel/runtime-corejs3/helpers/possibl
31
31
  var _getPrototypeOf = require('@babel/runtime-corejs3/helpers/getPrototypeOf');
32
32
  var _inherits = require('@babel/runtime-corejs3/helpers/inherits');
33
33
  var _wrapNativeSuper = require('@babel/runtime-corejs3/helpers/wrapNativeSuper');
34
+ var _startsWithInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/starts-with');
34
35
  var _trimInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/trim');
35
36
  var _JSON$stringify = require('@babel/runtime-corejs3/core-js-stable/json/stringify');
36
- var _startsWithInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/starts-with');
37
+ var path$1 = require('path');
37
38
  var _reduceInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/reduce');
38
39
  var _bindInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/bind');
39
- var formatters = require('./formatters-7f327585.cjs.prod.js');
40
+ var formatters = require('./formatters-4515015b.cjs.prod.js');
40
41
  var _Set = require('@babel/runtime-corejs3/core-js-stable/set');
41
42
  var _Array$isArray = require('@babel/runtime-corejs3/core-js-stable/array/is-array');
42
43
  var Ajv = require('ajv');
@@ -67,9 +68,10 @@ var _mapInstanceProperty__default = /*#__PURE__*/_interopDefault(_mapInstancePro
67
68
  var _parseInt__default = /*#__PURE__*/_interopDefault(_parseInt);
68
69
  var fs__default = /*#__PURE__*/_interopDefault(fs);
69
70
  var _Reflect$construct__default = /*#__PURE__*/_interopDefault(_Reflect$construct);
71
+ var _startsWithInstanceProperty__default = /*#__PURE__*/_interopDefault(_startsWithInstanceProperty);
70
72
  var _trimInstanceProperty__default = /*#__PURE__*/_interopDefault(_trimInstanceProperty);
71
73
  var _JSON$stringify__default = /*#__PURE__*/_interopDefault(_JSON$stringify);
72
- var _startsWithInstanceProperty__default = /*#__PURE__*/_interopDefault(_startsWithInstanceProperty);
74
+ var path__default$1 = /*#__PURE__*/_interopDefault(path$1);
73
75
  var _reduceInstanceProperty__default = /*#__PURE__*/_interopDefault(_reduceInstanceProperty);
74
76
  var _bindInstanceProperty__default = /*#__PURE__*/_interopDefault(_bindInstanceProperty);
75
77
  var _Set__default = /*#__PURE__*/_interopDefault(_Set);
@@ -218,6 +220,11 @@ const variableSyntax = /\${([ ~:\w.'",\-/()@]+?)}/g;
218
220
  const envRefSyntax = /^env:/g;
219
221
  const intlRefSyntax = /^intl:/g;
220
222
  const filePathRefSyntax = /^path:/g;
223
+
224
+ // Safe regex pattern escaping function
225
+ const escapeRegExp = string => {
226
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
227
+ };
221
228
  const hasVariablePlaceholder = valueOfEnvConfig => typeof valueOfEnvConfig === 'string' &&
222
229
  // Using `{regex}.test()` might cause false positives if called multiple
223
230
  // times on a global regular expression:
@@ -237,8 +244,7 @@ const substituteEnvVariablePlaceholder = (valueOfPlaceholder, matchedString, val
237
244
  if (!hasEnvField) {
238
245
  throw new Error(`Missing environment variable '${requestedEnvVar}' specified in config as 'env:${requestedEnvVar}'.`);
239
246
  }
240
- const escapedMatchedString = matchedString.replace(/[${}:]/g, '\\$&');
241
- return valueOfEnvConfig.replace(new RegExp(`(${escapedMatchedString})+`, 'g'), loadingOptions.processEnv[requestedEnvVar]);
247
+ return valueOfEnvConfig.replace(new RegExp(escapeRegExp(matchedString), 'g'), loadingOptions.processEnv[requestedEnvVar]);
242
248
  };
243
249
  const substituteIntlVariablePlaceholder = (valueOfPlaceholder, matchedString, valueOfEnvConfig, loadingOptions) => {
244
250
  const _valueOfPlaceholder$s3 = valueOfPlaceholder.split(':'),
@@ -255,43 +261,77 @@ const substituteIntlVariablePlaceholder = (valueOfPlaceholder, matchedString, va
255
261
  }
256
262
  const translation = translations[requestedIntlMessageId];
257
263
  const translationValue = isStructuredJson(translation) ? translation.string : translation;
258
- const escapedMatchedString = matchedString.replace(/[${}:]/g, '\\$&');
259
- return valueOfEnvConfig.replace(new RegExp(`(${escapedMatchedString})+`, 'g'), translationValue);
264
+ return valueOfEnvConfig.replace(new RegExp(escapeRegExp(matchedString), 'g'), translationValue);
260
265
  };
261
266
  const substituteFilePathVariablePlaceholder = (valueOfPlaceholder, matchedString, valueOfEnvConfig, loadingOptions) => {
262
267
  const _valueOfPlaceholder$s5 = valueOfPlaceholder.split(':'),
263
268
  _valueOfPlaceholder$s6 = _slicedToArray(_valueOfPlaceholder$s5, 2),
264
269
  filePathOrModule = _valueOfPlaceholder$s6[1];
265
- const content = fs__default$1["default"].readFileSync(require.resolve(filePathOrModule, {
266
- // Relative paths should be resolved from the application folder.
270
+ const resolvedPath = require.resolve(filePathOrModule, {
267
271
  paths: [loadingOptions.applicationPath]
268
- }), {
272
+ });
273
+
274
+ // Security check: Prevent path traversal attacks.
275
+ // require.resolve() already provides protection by only resolving modules
276
+ // accessible from the applicationPath. However, we add an extra layer to
277
+ // prevent access to sensitive system files outside the workspace.
278
+ const normalizedPath = path__default$1["default"].normalize(resolvedPath);
279
+ const applicationPath = path__default$1["default"].normalize(loadingOptions.applicationPath);
280
+
281
+ // Find workspace root by traversing up from applicationPath until we find
282
+ // package.json, pnpm-workspace.yaml, or reach root
283
+ let workspaceRoot = applicationPath;
284
+ let currentPath = applicationPath;
285
+ const rootPath = path__default$1["default"].parse(currentPath).root;
286
+ while (currentPath !== rootPath) {
287
+ const hasPackageJson = fs__default$1["default"].existsSync(path__default$1["default"].join(currentPath, 'package.json'));
288
+ const hasWorkspaceConfig = fs__default$1["default"].existsSync(path__default$1["default"].join(currentPath, 'pnpm-workspace.yaml')) || fs__default$1["default"].existsSync(path__default$1["default"].join(currentPath, 'lerna.json'));
289
+ if (hasPackageJson) {
290
+ workspaceRoot = currentPath;
291
+ if (hasWorkspaceConfig) {
292
+ // Found workspace root
293
+ break;
294
+ }
295
+ }
296
+ currentPath = path__default$1["default"].dirname(currentPath);
297
+ }
298
+ const relativePath = path__default$1["default"].relative(workspaceRoot, normalizedPath);
299
+
300
+ // Path is safe if it's within the workspace root.
301
+ // Use path.relative() to avoid string prefix vulnerabilities (e.g., "/app" vs "/app-evil")
302
+ const isSafePath = !_startsWithInstanceProperty__default["default"](relativePath).call(relativePath, '..') && !path__default$1["default"].isAbsolute(relativePath);
303
+ if (!isSafePath) {
304
+ throw new Error(`Access to files outside workspace directory is not allowed: ${filePathOrModule}`);
305
+ }
306
+ const content = fs__default$1["default"].readFileSync(normalizedPath, {
269
307
  encoding: 'utf-8'
270
308
  });
271
- const escapedMatchedString = matchedString.replace(/[${}:]/g, '\\$&');
272
- return valueOfEnvConfig.replace(new RegExp(`(${escapedMatchedString})+`, 'g'), content);
309
+ return valueOfEnvConfig.replace(new RegExp(escapeRegExp(matchedString), 'g'), content);
273
310
  };
274
311
  const getValueOfPlaceholder = valueWithPlaceholder => valueWithPlaceholder.replace(variableSyntax, (_match, varName) => _trimInstanceProperty__default["default"](varName).call(varName)).replace(/\s/g, '');
275
- const substituteVariablePlaceholders = (config, loadingOptions) => JSON.parse(_JSON$stringify__default["default"](config), (_key, value) => {
276
- // Only strings are allowed
277
- let substitutedValue = value;
278
- if (hasVariablePlaceholder(substitutedValue)) {
279
- const matchResult = substitutedValue.match(variableSyntax);
280
- if (matchResult) {
281
- _forEachInstanceProperty__default["default"](matchResult).call(matchResult, matchedString => {
282
- const valueOfPlaceholder = getValueOfPlaceholder(matchedString);
283
- if (isEnvVariablePlaceholder(valueOfPlaceholder)) {
284
- substitutedValue = substituteEnvVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
285
- } else if (isIntlVariablePlaceholder(valueOfPlaceholder)) {
286
- substitutedValue = substituteIntlVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
287
- } else if (isFilePathVariablePlaceholder(valueOfPlaceholder)) {
288
- substitutedValue = substituteFilePathVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
289
- }
290
- });
312
+ const substituteVariablePlaceholders = (config, loadingOptions) => {
313
+ const result = JSON.parse(_JSON$stringify__default["default"](config), (_key, value) => {
314
+ // Only strings are allowed
315
+ let substitutedValue = value;
316
+ if (hasVariablePlaceholder(substitutedValue)) {
317
+ const matchResult = substitutedValue.match(variableSyntax);
318
+ if (matchResult) {
319
+ _forEachInstanceProperty__default["default"](matchResult).call(matchResult, matchedString => {
320
+ const valueOfPlaceholder = getValueOfPlaceholder(matchedString);
321
+ if (isEnvVariablePlaceholder(valueOfPlaceholder)) {
322
+ substitutedValue = substituteEnvVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
323
+ } else if (isIntlVariablePlaceholder(valueOfPlaceholder)) {
324
+ substitutedValue = substituteIntlVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
325
+ } else if (isFilePathVariablePlaceholder(valueOfPlaceholder)) {
326
+ substitutedValue = substituteFilePathVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
327
+ }
328
+ });
329
+ }
291
330
  }
292
- }
293
- return substitutedValue;
294
- });
331
+ return substitutedValue;
332
+ });
333
+ return result;
334
+ };
295
335
 
296
336
  var customApplicationSchemaJson = {
297
337
  $schema: "http://json-schema.org/draft-07/schema",
@@ -28,12 +28,13 @@ import _possibleConstructorReturn from '@babel/runtime-corejs3/helpers/esm/possi
28
28
  import _getPrototypeOf from '@babel/runtime-corejs3/helpers/esm/getPrototypeOf';
29
29
  import _inherits from '@babel/runtime-corejs3/helpers/esm/inherits';
30
30
  import _wrapNativeSuper from '@babel/runtime-corejs3/helpers/esm/wrapNativeSuper';
31
+ import _startsWithInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/starts-with';
31
32
  import _trimInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/trim';
32
33
  import _JSON$stringify from '@babel/runtime-corejs3/core-js-stable/json/stringify';
33
- import _startsWithInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/starts-with';
34
+ import path$1 from 'path';
34
35
  import _reduceInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/reduce';
35
36
  import _bindInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/bind';
36
- import { f as formatEntryPointUriPathToResourceAccessKey, e as entryPointUriPathToResourceAccesses } from './formatters-882eafa8.esm.js';
37
+ import { f as formatEntryPointUriPathToResourceAccessKey, e as entryPointUriPathToResourceAccesses } from './formatters-5629a23b.esm.js';
37
38
  import _Set from '@babel/runtime-corejs3/core-js-stable/set';
38
39
  import _Array$isArray from '@babel/runtime-corejs3/core-js-stable/array/is-array';
39
40
  import Ajv from 'ajv';
@@ -183,6 +184,11 @@ const variableSyntax = /\${([ ~:\w.'",\-/()@]+?)}/g;
183
184
  const envRefSyntax = /^env:/g;
184
185
  const intlRefSyntax = /^intl:/g;
185
186
  const filePathRefSyntax = /^path:/g;
187
+
188
+ // Safe regex pattern escaping function
189
+ const escapeRegExp = string => {
190
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
191
+ };
186
192
  const hasVariablePlaceholder = valueOfEnvConfig => typeof valueOfEnvConfig === 'string' &&
187
193
  // Using `{regex}.test()` might cause false positives if called multiple
188
194
  // times on a global regular expression:
@@ -202,8 +208,7 @@ const substituteEnvVariablePlaceholder = (valueOfPlaceholder, matchedString, val
202
208
  if (!hasEnvField) {
203
209
  throw new Error(`Missing environment variable '${requestedEnvVar}' specified in config as 'env:${requestedEnvVar}'.`);
204
210
  }
205
- const escapedMatchedString = matchedString.replace(/[${}:]/g, '\\$&');
206
- return valueOfEnvConfig.replace(new RegExp(`(${escapedMatchedString})+`, 'g'), loadingOptions.processEnv[requestedEnvVar]);
211
+ return valueOfEnvConfig.replace(new RegExp(escapeRegExp(matchedString), 'g'), loadingOptions.processEnv[requestedEnvVar]);
207
212
  };
208
213
  const substituteIntlVariablePlaceholder = (valueOfPlaceholder, matchedString, valueOfEnvConfig, loadingOptions) => {
209
214
  const _valueOfPlaceholder$s3 = valueOfPlaceholder.split(':'),
@@ -220,43 +225,77 @@ const substituteIntlVariablePlaceholder = (valueOfPlaceholder, matchedString, va
220
225
  }
221
226
  const translation = translations[requestedIntlMessageId];
222
227
  const translationValue = isStructuredJson(translation) ? translation.string : translation;
223
- const escapedMatchedString = matchedString.replace(/[${}:]/g, '\\$&');
224
- return valueOfEnvConfig.replace(new RegExp(`(${escapedMatchedString})+`, 'g'), translationValue);
228
+ return valueOfEnvConfig.replace(new RegExp(escapeRegExp(matchedString), 'g'), translationValue);
225
229
  };
226
230
  const substituteFilePathVariablePlaceholder = (valueOfPlaceholder, matchedString, valueOfEnvConfig, loadingOptions) => {
227
231
  const _valueOfPlaceholder$s5 = valueOfPlaceholder.split(':'),
228
232
  _valueOfPlaceholder$s6 = _slicedToArray(_valueOfPlaceholder$s5, 2),
229
233
  filePathOrModule = _valueOfPlaceholder$s6[1];
230
- const content = fs$1.readFileSync(require.resolve(filePathOrModule, {
231
- // Relative paths should be resolved from the application folder.
234
+ const resolvedPath = require.resolve(filePathOrModule, {
232
235
  paths: [loadingOptions.applicationPath]
233
- }), {
236
+ });
237
+
238
+ // Security check: Prevent path traversal attacks.
239
+ // require.resolve() already provides protection by only resolving modules
240
+ // accessible from the applicationPath. However, we add an extra layer to
241
+ // prevent access to sensitive system files outside the workspace.
242
+ const normalizedPath = path$1.normalize(resolvedPath);
243
+ const applicationPath = path$1.normalize(loadingOptions.applicationPath);
244
+
245
+ // Find workspace root by traversing up from applicationPath until we find
246
+ // package.json, pnpm-workspace.yaml, or reach root
247
+ let workspaceRoot = applicationPath;
248
+ let currentPath = applicationPath;
249
+ const rootPath = path$1.parse(currentPath).root;
250
+ while (currentPath !== rootPath) {
251
+ const hasPackageJson = fs$1.existsSync(path$1.join(currentPath, 'package.json'));
252
+ const hasWorkspaceConfig = fs$1.existsSync(path$1.join(currentPath, 'pnpm-workspace.yaml')) || fs$1.existsSync(path$1.join(currentPath, 'lerna.json'));
253
+ if (hasPackageJson) {
254
+ workspaceRoot = currentPath;
255
+ if (hasWorkspaceConfig) {
256
+ // Found workspace root
257
+ break;
258
+ }
259
+ }
260
+ currentPath = path$1.dirname(currentPath);
261
+ }
262
+ const relativePath = path$1.relative(workspaceRoot, normalizedPath);
263
+
264
+ // Path is safe if it's within the workspace root.
265
+ // Use path.relative() to avoid string prefix vulnerabilities (e.g., "/app" vs "/app-evil")
266
+ const isSafePath = !_startsWithInstanceProperty(relativePath).call(relativePath, '..') && !path$1.isAbsolute(relativePath);
267
+ if (!isSafePath) {
268
+ throw new Error(`Access to files outside workspace directory is not allowed: ${filePathOrModule}`);
269
+ }
270
+ const content = fs$1.readFileSync(normalizedPath, {
234
271
  encoding: 'utf-8'
235
272
  });
236
- const escapedMatchedString = matchedString.replace(/[${}:]/g, '\\$&');
237
- return valueOfEnvConfig.replace(new RegExp(`(${escapedMatchedString})+`, 'g'), content);
273
+ return valueOfEnvConfig.replace(new RegExp(escapeRegExp(matchedString), 'g'), content);
238
274
  };
239
275
  const getValueOfPlaceholder = valueWithPlaceholder => valueWithPlaceholder.replace(variableSyntax, (_match, varName) => _trimInstanceProperty(varName).call(varName)).replace(/\s/g, '');
240
- const substituteVariablePlaceholders = (config, loadingOptions) => JSON.parse(_JSON$stringify(config), (_key, value) => {
241
- // Only strings are allowed
242
- let substitutedValue = value;
243
- if (hasVariablePlaceholder(substitutedValue)) {
244
- const matchResult = substitutedValue.match(variableSyntax);
245
- if (matchResult) {
246
- _forEachInstanceProperty(matchResult).call(matchResult, matchedString => {
247
- const valueOfPlaceholder = getValueOfPlaceholder(matchedString);
248
- if (isEnvVariablePlaceholder(valueOfPlaceholder)) {
249
- substitutedValue = substituteEnvVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
250
- } else if (isIntlVariablePlaceholder(valueOfPlaceholder)) {
251
- substitutedValue = substituteIntlVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
252
- } else if (isFilePathVariablePlaceholder(valueOfPlaceholder)) {
253
- substitutedValue = substituteFilePathVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
254
- }
255
- });
276
+ const substituteVariablePlaceholders = (config, loadingOptions) => {
277
+ const result = JSON.parse(_JSON$stringify(config), (_key, value) => {
278
+ // Only strings are allowed
279
+ let substitutedValue = value;
280
+ if (hasVariablePlaceholder(substitutedValue)) {
281
+ const matchResult = substitutedValue.match(variableSyntax);
282
+ if (matchResult) {
283
+ _forEachInstanceProperty(matchResult).call(matchResult, matchedString => {
284
+ const valueOfPlaceholder = getValueOfPlaceholder(matchedString);
285
+ if (isEnvVariablePlaceholder(valueOfPlaceholder)) {
286
+ substitutedValue = substituteEnvVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
287
+ } else if (isIntlVariablePlaceholder(valueOfPlaceholder)) {
288
+ substitutedValue = substituteIntlVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
289
+ } else if (isFilePathVariablePlaceholder(valueOfPlaceholder)) {
290
+ substitutedValue = substituteFilePathVariablePlaceholder(valueOfPlaceholder, matchedString, substitutedValue, loadingOptions);
291
+ }
292
+ });
293
+ }
256
294
  }
257
- }
258
- return substitutedValue;
259
- });
295
+ return substitutedValue;
296
+ });
297
+ return result;
298
+ };
260
299
 
261
300
  var customApplicationSchemaJson = {
262
301
  $schema: "http://json-schema.org/draft-07/schema",
@@ -59,7 +59,7 @@ const formatEntryPointUriPathToResourceAccessKey = entryPointUriPath => {
59
59
  // Regex below checking if the character is numeric.
60
60
  // If the word after the hyphen is numeric, replace the hyphen with a forward slash.
61
61
  // If not, omit the hyphen and uppercase the first character
62
- if (i > 0 && /^-?\d+$/.test(word[0])) {
62
+ if (i > 0 && /^\d+$/.test(word[0])) {
63
63
  return `/${word}`;
64
64
  }
65
65
  return upperFirst__default["default"](word);
@@ -42,7 +42,7 @@ const formatEntryPointUriPathToResourceAccessKey = entryPointUriPath => {
42
42
  // Regex below checking if the character is numeric.
43
43
  // If the word after the hyphen is numeric, replace the hyphen with a forward slash.
44
44
  // If not, omit the hyphen and uppercase the first character
45
- if (i > 0 && /^-?\d+$/.test(word[0])) {
45
+ if (i > 0 && /^\d+$/.test(word[0])) {
46
46
  return `/${word}`;
47
47
  }
48
48
  return upperFirst(word);
@@ -59,7 +59,7 @@ const formatEntryPointUriPathToResourceAccessKey = entryPointUriPath => {
59
59
  // Regex below checking if the character is numeric.
60
60
  // If the word after the hyphen is numeric, replace the hyphen with a forward slash.
61
61
  // If not, omit the hyphen and uppercase the first character
62
- if (i > 0 && /^-?\d+$/.test(word[0])) {
62
+ if (i > 0 && /^\d+$/.test(word[0])) {
63
63
  return `/${word}`;
64
64
  }
65
65
  return upperFirst__default["default"](word);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercetools-frontend/application-config",
3
- "version": "25.1.0",
3
+ "version": "25.2.0",
4
4
  "description": "Configuration utilities for building Custom Applications",
5
5
  "bugs": "https://github.com/commercetools/merchant-center-application-kit/issues",
6
6
  "repository": {
@@ -45,7 +45,7 @@
45
45
  "@babel/register": "^7.22.15",
46
46
  "@babel/runtime": "^7.22.15",
47
47
  "@babel/runtime-corejs3": "^7.22.15",
48
- "@commercetools-frontend/constants": "25.1.0",
48
+ "@commercetools-frontend/constants": "25.2.0",
49
49
  "@types/lodash": "^4.14.198",
50
50
  "@types/react": "^19.0.3",
51
51
  "ajv": "8.17.1",
@@ -54,13 +54,13 @@
54
54
  "cosmiconfig-typescript-loader": "6.1.0",
55
55
  "dompurify": "^3.0.0",
56
56
  "jsdom": "^21.1.2",
57
- "lodash": "4.17.21",
57
+ "lodash": "4.17.23",
58
58
  "omit-empty-es": "1.2.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/jsdom": "^21.1.2",
62
62
  "json-schema-to-typescript": "15.0.4",
63
- "@commercetools-frontend/assets": "25.1.0"
63
+ "@commercetools-frontend/assets": "25.2.0"
64
64
  },
65
65
  "engines": {
66
66
  "node": "18.x || 20.x || >=22.0.0"
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var formatters = require('../../dist/formatters-a76b45b9.cjs.dev.js');
5
+ var formatters = require('../../dist/formatters-5a68b5ac.cjs.dev.js');
6
6
  require('@babel/runtime-corejs3/core-js-stable/object/keys');
7
7
  require('@babel/runtime-corejs3/core-js-stable/object/get-own-property-symbols');
8
8
  require('@babel/runtime-corejs3/core-js-stable/instance/filter');
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var formatters = require('../../dist/formatters-7f327585.cjs.prod.js');
5
+ var formatters = require('../../dist/formatters-4515015b.cjs.prod.js');
6
6
  require('@babel/runtime-corejs3/core-js-stable/object/keys');
7
7
  require('@babel/runtime-corejs3/core-js-stable/object/get-own-property-symbols');
8
8
  require('@babel/runtime-corejs3/core-js-stable/instance/filter');
@@ -1,4 +1,4 @@
1
- export { d as computeCustomViewPermissionsKeys, c as computeCustomViewResourceAccesses, a as entryPointUriPathToPermissionKeys, e as entryPointUriPathToResourceAccesses, f as formatEntryPointUriPathToResourceAccessKey, b as formatPermissionGroupNameToResourceAccessKey } from '../../dist/formatters-882eafa8.esm.js';
1
+ export { d as computeCustomViewPermissionsKeys, c as computeCustomViewResourceAccesses, a as entryPointUriPathToPermissionKeys, e as entryPointUriPathToResourceAccesses, f as formatEntryPointUriPathToResourceAccessKey, b as formatPermissionGroupNameToResourceAccessKey } from '../../dist/formatters-5629a23b.esm.js';
2
2
  import '@babel/runtime-corejs3/core-js-stable/object/keys';
3
3
  import '@babel/runtime-corejs3/core-js-stable/object/get-own-property-symbols';
4
4
  import '@babel/runtime-corejs3/core-js-stable/instance/filter';