@lwrjs/shared-utils 0.19.8 → 0.19.9

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.
package/build/cjs/env.cjs CHANGED
@@ -39,7 +39,6 @@ function getFeatureFlags() {
39
39
  LEGACY_LOADER: parseBooleanFlag("LEGACY_LOADER"),
40
40
  LWR_TRACING: parseTracingFlag(),
41
41
  DISABLE_B3_TRACING: parseBooleanFlag("DISABLE_B3_TRACING"),
42
- MRT_B3_TRACING: parseBooleanFlag("MRT_B3_TRACING"),
43
42
  MAX_VIEW_CACHE_TTL: parseStringFlag("MAX_VIEW_CACHE_TTL"),
44
43
  REEVALUATE_MODULES: parseBooleanFlag("REEVALUATE_MODULES"),
45
44
  SSR_COMPILER_ENABLED: parseBooleanFlag("SSR_COMPILER_ENABLED"),
@@ -63,14 +62,6 @@ function parseTracingFlag() {
63
62
  const tracingValue = process.env.LWR_TRACING?.toLowerCase();
64
63
  return tracingValue && tracingValue !== "off" ? process.env.LWR_TRACING : void 0;
65
64
  }
66
- function shouldEnableB3Headers() {
67
- const flags = getFeatureFlags();
68
- const isMRT = isLambdaEnv() || process.env.MRT_REQUEST_CLASS || process.env.MOBIFY_PROPERTY_ID;
69
- if (isMRT) {
70
- return flags.MRT_B3_TRACING !== false;
71
- }
72
- return true;
73
- }
74
65
  function isLambdaEnv() {
75
66
  return process.env.AWS_LAMBDA_FUNCTION_NAME !== void 0;
76
67
  }
@@ -141,7 +132,7 @@ function getTraceHeaders(runtimeParams, span) {
141
132
  headers[TRUE_CLIENT_IP] = runtimeParams.trueClientIP;
142
133
  if (runtimeParams.correlationID)
143
134
  headers[CORRELATION_ID] = runtimeParams.correlationID;
144
- if (shouldEnableB3Headers()) {
135
+ if (!getFeatureFlags().DISABLE_B3_TRACING) {
145
136
  if (span?.traceId) {
146
137
  headers[B3_TRACE_ID] = span.traceId;
147
138
  headers[B3_SAMPLED] = parseTracingFlag() ? "1" : "0";
@@ -37,8 +37,10 @@ __export(exports, {
37
37
  VERSION_NOT_PROVIDED: () => VERSION_NOT_PROVIDED,
38
38
  VERSION_PREFIX: () => VERSION_PREFIX,
39
39
  VERSION_SIGIL: () => VERSION_SIGIL,
40
+ containsPathTraversal: () => containsPathTraversal,
40
41
  explodeSpecifier: () => explodeSpecifier,
41
42
  explodeSpecifiers: () => explodeSpecifiers,
43
+ fullyDecodeSpecifier: () => fullyDecodeSpecifier,
42
44
  getCacheKeyFromJson: () => import_fast_json_stable_stringify.default,
43
45
  getMappingUriPrefix: () => getMappingUriPrefix,
44
46
  getModuleUriPrefix: () => getModuleUriPrefix,
@@ -85,7 +87,30 @@ function normalizeVersionFromUri(version) {
85
87
  function prettyModuleUriSuffix(specifier) {
86
88
  return `${specifier.replace(/[\/\.\?\#]/g, "_")}`;
87
89
  }
90
+ var MAX_DECODE_ITERATIONS = 5;
91
+ function fullyDecodeSpecifier(rawSpecifier) {
92
+ let decoded = rawSpecifier;
93
+ for (let i = 0; i < MAX_DECODE_ITERATIONS; i++) {
94
+ let next;
95
+ try {
96
+ next = decodeURIComponent(decoded);
97
+ } catch {
98
+ return decoded;
99
+ }
100
+ if (next === decoded) {
101
+ return decoded;
102
+ }
103
+ decoded = next;
104
+ }
105
+ throw new Error(`Specifier "${rawSpecifier}" could not be fully decoded within ${MAX_DECODE_ITERATIONS} passes`);
106
+ }
107
+ function containsPathTraversal(decodedSpecifier) {
108
+ return decodedSpecifier.split(/[\\/#?]/).some((segment) => segment === "..");
109
+ }
88
110
  function explodeSpecifier(rawSpecifier) {
111
+ if (containsPathTraversal(fullyDecodeSpecifier(rawSpecifier))) {
112
+ throw new Error(`Invalid module specifier "${rawSpecifier}": path traversal is not allowed`);
113
+ }
89
114
  const decodedSpecifier = decodeURIComponent(rawSpecifier);
90
115
  const versionMatch = decodedSpecifier.match(/(.+)\/v\/([a-zA-Z0-9-_.]+)$/);
91
116
  const version = versionMatch ? versionMatch[2] : void 0;
package/build/es/env.js CHANGED
@@ -15,10 +15,7 @@ export function getFeatureFlags() {
15
15
  // Enable metrics log level 'off', 'default' or 'verbose'
16
16
  LWR_TRACING: parseTracingFlag(),
17
17
  // Turn off distributed tracing (B3 headers, traceparent meta tag, MRT span exporting)
18
- // Legacy flag - kept for backward compatibility but no longer controls B3 headers
19
18
  DISABLE_B3_TRACING: parseBooleanFlag('DISABLE_B3_TRACING'),
20
- // Control x-b3-* distributed tracing headers specifically for MRT sites
21
- MRT_B3_TRACING: parseBooleanFlag('MRT_B3_TRACING'),
22
19
  // Max size of ViewDefinition time to live
23
20
  MAX_VIEW_CACHE_TTL: parseStringFlag('MAX_VIEW_CACHE_TTL'),
24
21
  // Forces SSR to re-evaluate modules for every page render. By default, modules are evaluated only once.
@@ -59,32 +56,13 @@ function parseStringFlag(flag) {
59
56
  return value || undefined;
60
57
  }
61
58
  /**
62
- * Helper function to parse the LWR_TRACING flag for OpenTelemetry spans/instrumentation.
63
- * Returns the value if it's not 'off' and is defined, otherwise returns undefined.
64
- * This controls OpenTelemetry spans, NOT B3 distributed tracing headers.
59
+ * Helper function to parse the LWR_TRACING flag.
60
+ * Returns the value if it's not 'off' and is defined, otherwise returns false.
65
61
  */
66
62
  function parseTracingFlag() {
67
63
  const tracingValue = process.env.LWR_TRACING?.toLowerCase();
68
64
  return tracingValue && tracingValue !== 'off' ? process.env.LWR_TRACING : undefined;
69
65
  }
70
- /**
71
- * Helper function to determine if B3 distributed tracing headers should be enabled.
72
- * Uses MRT_B3_TRACING for MRT sites, always enabled for non-MRT sites.
73
- * Separates OpenTelemetry spans (LWR_TRACING) from B3 header propagation.
74
- *
75
- * @returns true if B3 distributed tracing headers should be set
76
- */
77
- function shouldEnableB3Headers() {
78
- const flags = getFeatureFlags();
79
- // Check if this is an MRT environment
80
- const isMRT = isLambdaEnv() || process.env.MRT_REQUEST_CLASS || process.env.MOBIFY_PROPERTY_ID;
81
- if (isMRT) {
82
- // For MRT sites, use the MRT-specific distributed tracing flag
83
- return flags.MRT_B3_TRACING !== false;
84
- }
85
- // For non-MRT sites, always enable B3 headers
86
- return true;
87
- }
88
66
  /**
89
67
  * This function is used to determine if the current environment is a lambda.
90
68
  *
@@ -191,9 +169,7 @@ export function getTraceHeaders(runtimeParams, span) {
191
169
  headers[TRUE_CLIENT_IP] = runtimeParams.trueClientIP;
192
170
  if (runtimeParams.correlationID)
193
171
  headers[CORRELATION_ID] = runtimeParams.correlationID;
194
- // Set B3 distributed tracing headers for request correlation across microservices
195
- // These headers are controlled independently from OpenTelemetry spans (LWR_TRACING)
196
- if (shouldEnableB3Headers()) {
172
+ if (!getFeatureFlags().DISABLE_B3_TRACING) {
197
173
  if (span?.traceId) {
198
174
  headers[B3_TRACE_ID] = span.traceId;
199
175
  headers[B3_SAMPLED] = parseTracingFlag() ? '1' : '0';
@@ -35,6 +35,23 @@ export declare function normalizeVersionFromUri(version: string): string;
35
35
  * examples/app/scoped/css?scoped=true => examples_app_scoped_css_scoped=true
36
36
  */
37
37
  export declare function prettyModuleUriSuffix(specifier: string): string;
38
+ /**
39
+ * Repeatedly percent-decodes a string until it stabilizes (or the iteration bound is hit).
40
+ * A single decode pass can be defeated by nesting encodings (e.g. '%252e%252e%252f'
41
+ * decodes once to '%2e%2e%2f', which looks harmless until it's decoded again). Fully
42
+ * resolving the encoding first means a later traversal check can't be smuggled past.
43
+ */
44
+ export declare function fullyDecodeSpecifier(rawSpecifier: string): string;
45
+ /**
46
+ * True if any '/'-, '\'-, '#'-, or '?'-delimited segment of an already-decoded specifier
47
+ * is exactly '..' (a directory traversal segment). Segment based so legitimate names
48
+ * containing dots (e.g. 'my.label') are never mistaken for traversal. '#' and '?' are
49
+ * included because callers (e.g. lwc-module-provider) split the fragment after '#' off
50
+ * of a specifier and path.join it against a resolved directory - a '..' glued directly
51
+ * after '#' (e.g. 'c/app#../secret') would otherwise never appear as its own '/'-delimited
52
+ * segment and slip past this check (W-24189528).
53
+ */
54
+ export declare function containsPathTraversal(decodedSpecifier: string): boolean;
38
55
  /**
39
56
  * Create a partial Module Identifier from a specifier
40
57
  * The Module Identifier is partial because the specifier may not contain a version
@@ -42,7 +42,50 @@ export function prettyModuleUriSuffix(specifier) {
42
42
  // eslint-disable-next-line no-useless-escape
43
43
  return `${specifier.replace(/[\/\.\?\#]/g, '_')}`;
44
44
  }
45
+ const MAX_DECODE_ITERATIONS = 5;
46
+ /**
47
+ * Repeatedly percent-decodes a string until it stabilizes (or the iteration bound is hit).
48
+ * A single decode pass can be defeated by nesting encodings (e.g. '%252e%252e%252f'
49
+ * decodes once to '%2e%2e%2f', which looks harmless until it's decoded again). Fully
50
+ * resolving the encoding first means a later traversal check can't be smuggled past.
51
+ */
52
+ export function fullyDecodeSpecifier(rawSpecifier) {
53
+ let decoded = rawSpecifier;
54
+ for (let i = 0; i < MAX_DECODE_ITERATIONS; i++) {
55
+ let next;
56
+ try {
57
+ next = decodeURIComponent(decoded);
58
+ }
59
+ catch {
60
+ return decoded; // malformed escape sequence, nothing further to decode
61
+ }
62
+ if (next === decoded) {
63
+ return decoded; // fully stable within budget
64
+ }
65
+ decoded = next;
66
+ }
67
+ // Every pass up to the budget still changed the string, so the encoding
68
+ // depth is unknown and possibly unbounded. Fail closed instead of checking
69
+ // a value that might still have traversal segments hidden behind more
70
+ // encoding than we were willing to unwrap.
71
+ throw new Error(`Specifier "${rawSpecifier}" could not be fully decoded within ${MAX_DECODE_ITERATIONS} passes`);
72
+ }
73
+ /**
74
+ * True if any '/'-, '\'-, '#'-, or '?'-delimited segment of an already-decoded specifier
75
+ * is exactly '..' (a directory traversal segment). Segment based so legitimate names
76
+ * containing dots (e.g. 'my.label') are never mistaken for traversal. '#' and '?' are
77
+ * included because callers (e.g. lwc-module-provider) split the fragment after '#' off
78
+ * of a specifier and path.join it against a resolved directory - a '..' glued directly
79
+ * after '#' (e.g. 'c/app#../secret') would otherwise never appear as its own '/'-delimited
80
+ * segment and slip past this check (W-24189528).
81
+ */
82
+ export function containsPathTraversal(decodedSpecifier) {
83
+ return decodedSpecifier.split(/[\\/#?]/).some((segment) => segment === '..');
84
+ }
45
85
  export function explodeSpecifier(rawSpecifier) {
86
+ if (containsPathTraversal(fullyDecodeSpecifier(rawSpecifier))) {
87
+ throw new Error(`Invalid module specifier "${rawSpecifier}": path traversal is not allowed`);
88
+ }
46
89
  const decodedSpecifier = decodeURIComponent(rawSpecifier);
47
90
  // Split up the version and bare specifier
48
91
  const versionMatch = decodedSpecifier.match(/(.+)\/v\/([a-zA-Z0-9-_.]+)$/);
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.19.8",
7
+ "version": "0.19.9",
8
8
  "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
9
  "repository": {
10
10
  "type": "git",
@@ -37,7 +37,7 @@
37
37
  "build/**/*.d.ts"
38
38
  ],
39
39
  "dependencies": {
40
- "@lwrjs/diagnostics": "0.19.8",
40
+ "@lwrjs/diagnostics": "0.19.9",
41
41
  "es-module-lexer": "^1.5.4",
42
42
  "fast-json-stable-stringify": "^2.1.0",
43
43
  "magic-string": "^0.30.9",
@@ -50,7 +50,7 @@
50
50
  "slugify": "^1.4.5"
51
51
  },
52
52
  "devDependencies": {
53
- "@lwrjs/types": "0.19.8",
53
+ "@lwrjs/types": "0.19.9",
54
54
  "@types/mime-types": "2.1.4",
55
55
  "@types/path-to-regexp": "^1.7.0",
56
56
  "memfs": "^4.13.0"
@@ -58,5 +58,5 @@
58
58
  "engines": {
59
59
  "node": ">=20.0.0"
60
60
  },
61
- "gitHead": "318e3e2bde6d04a218cc11849f791b7de85e6f9e"
61
+ "gitHead": "721f776d04844cbc99531d449c8200fac74c5fa2"
62
62
  }