@lwrjs/fs-asset-provider 0.24.0 → 0.24.1

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.
@@ -72,13 +72,17 @@ var FsAssetProvider = class {
72
72
  const fullAssetPath = (0, import_shared_utils.normalizeAssetSpecifier)(assetId, this.assetPathMap, this.resourcePaths, this.basePath);
73
73
  return this.getAssetFromFSPath(fullAssetPath);
74
74
  }
75
+ isWithinConfiguredRoots(resolvedPath) {
76
+ const roots = [...this.assetPathMap.values(), ...Object.values(this.resourcePaths)].filter((root) => typeof root === "string" && root.length > 0).map((root) => (0, import_path.resolve)(root));
77
+ return roots.some((root) => resolvedPath === root || resolvedPath.startsWith(root + import_path.sep));
78
+ }
75
79
  async getAssetFromFSPath(assetPath) {
76
- assetPath = (0, import_shared_utils.toForwardSlash)((0, import_path.normalize)(assetPath));
77
- const fullAssetPath = decodeURI(assetPath);
80
+ const decodedAssetPath = decodeURI(assetPath);
81
+ const fullAssetPath = (0, import_shared_utils.toForwardSlash)((0, import_path.normalize)(decodedAssetPath));
78
82
  if (!this.cachedAssets.has(fullAssetPath)) {
79
83
  const [assetPath2] = fullAssetPath.split("?");
80
84
  const ext = (0, import_path.extname)(`x.${assetPath2}`).toLowerCase().substring(1);
81
- if (ext && import_fs_extra.default.existsSync(assetPath2)) {
85
+ if (ext && import_fs_extra.default.existsSync(assetPath2) && this.isWithinConfiguredRoots((0, import_path.resolve)(assetPath2))) {
82
86
  const ownHash = hash(assetPath2);
83
87
  const mimeType = (0, import_shared_utils.mimeLookup)(assetPath2);
84
88
  const assetDef = {
@@ -14,6 +14,12 @@ export default class FsAssetProvider implements AssetProvider {
14
14
  * Public API to get an AssetSource object from an asset identifier
15
15
  */
16
16
  getAsset(assetId: AssetIdentifier): Promise<AssetSource | undefined>;
17
+ /**
18
+ * True if the resolved path falls within one of the roots this provider is configured
19
+ * to serve from. Last line of defense against path traversal, independent of whatever
20
+ * validation (or lack thereof) happened upstream of this provider.
21
+ */
22
+ private isWithinConfiguredRoots;
17
23
  /**
18
24
  * Get and AssetSource object from a resolved filesystem file path.
19
25
  */
package/build/es/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import fs from 'fs-extra';
2
- import { extname, normalize } from 'path';
2
+ import { extname, normalize, resolve, sep } from 'path';
3
3
  import { logger } from '@lwrjs/diagnostics';
4
4
  import { mimeLookup, normalizeAssetSpecifier, toForwardSlash } from '@lwrjs/shared-utils';
5
5
  function hash(filePath) {
@@ -45,17 +45,33 @@ export default class FsAssetProvider {
45
45
  const fullAssetPath = normalizeAssetSpecifier(assetId, this.assetPathMap, this.resourcePaths, this.basePath);
46
46
  return this.getAssetFromFSPath(fullAssetPath);
47
47
  }
48
+ /**
49
+ * True if the resolved path falls within one of the roots this provider is configured
50
+ * to serve from. Last line of defense against path traversal, independent of whatever
51
+ * validation (or lack thereof) happened upstream of this provider.
52
+ */
53
+ isWithinConfiguredRoots(resolvedPath) {
54
+ const roots = [...this.assetPathMap.values(), ...Object.values(this.resourcePaths)]
55
+ .filter((root) => typeof root === 'string' && root.length > 0)
56
+ .map((root) => resolve(root));
57
+ return roots.some((root) => resolvedPath === root || resolvedPath.startsWith(root + sep));
58
+ }
48
59
  /**
49
60
  * Get and AssetSource object from a resolved filesystem file path.
50
61
  */
51
62
  async getAssetFromFSPath(assetPath) {
52
- assetPath = toForwardSlash(normalize(assetPath));
53
- const fullAssetPath = decodeURI(assetPath); // decodeURIComponent decodes too many things (eg: /)
63
+ // Decode BEFORE normalizing: percent-encoded traversal segments (eg '%2e%2e%2f')
64
+ // must become literal '..' segments before normalize() can collapse them. Decoding
65
+ // after normalize (the previous order) let encoded traversal sequences pass through
66
+ // untouched.
67
+ // decodeURIComponent decodes too many things (eg: /), so use decodeURI here.
68
+ const decodedAssetPath = decodeURI(assetPath);
69
+ const fullAssetPath = toForwardSlash(normalize(decodedAssetPath));
54
70
  if (!this.cachedAssets.has(fullAssetPath)) {
55
71
  const [assetPath] = fullAssetPath.split('?'); // remove query params
56
72
  // Clever normalization to avoid corner cases
57
73
  const ext = extname(`x.${assetPath}`).toLowerCase().substring(1);
58
- if (ext && fs.existsSync(assetPath)) {
74
+ if (ext && fs.existsSync(assetPath) && this.isWithinConfiguredRoots(resolve(assetPath))) {
59
75
  const ownHash = hash(assetPath);
60
76
  const mimeType = mimeLookup(assetPath);
61
77
  const assetDef = {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.24.0",
7
+ "version": "0.24.1",
8
8
  "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
9
  "repository": {
10
10
  "type": "git",
@@ -33,16 +33,16 @@
33
33
  "build": "tsc -b"
34
34
  },
35
35
  "dependencies": {
36
- "@lwrjs/diagnostics": "0.24.0",
37
- "@lwrjs/shared-utils": "0.24.0",
36
+ "@lwrjs/diagnostics": "0.24.1",
37
+ "@lwrjs/shared-utils": "0.24.1",
38
38
  "fs-extra": "^11.2.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@lwrjs/types": "0.24.0",
41
+ "@lwrjs/types": "0.24.1",
42
42
  "memfs": "^4.13.0"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=22.0.0"
46
46
  },
47
- "gitHead": "6b7aace3739a07eae17d4220fd86ceb8a65f147a"
47
+ "gitHead": "ebc0aaa310f5ebd008732d0203a15a1c51f77e5e"
48
48
  }