@n8n/scan-community-package 0.5.0 → 0.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@n8n/scan-community-package",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Static code analyser for n8n community packages",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "bin": "scanner/cli.mjs",
@@ -15,7 +15,7 @@
15
15
  "axios": "1.12.0",
16
16
  "semver": "^7.5.4",
17
17
  "tmp": "0.2.4",
18
- "@n8n/eslint-plugin-community-nodes": "0.3.0"
18
+ "@n8n/eslint-plugin-community-nodes": "0.4.0"
19
19
  },
20
20
  "homepage": "https://n8n.io",
21
21
  "author": {
@@ -16,6 +16,40 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
16
  const TEMP_DIR = tmp.dirSync({ unsafeCleanup: true }).name;
17
17
  const registry = 'https://registry.npmjs.org/';
18
18
 
19
+ /**
20
+ * Checks if the given childPath is contained within the parentPath. Resolves
21
+ * the paths before comparing them, so that relative paths are also supported.
22
+ */
23
+ export function isContainedWithin(parentPath, childPath) {
24
+ parentPath = path.resolve(parentPath);
25
+ childPath = path.resolve(childPath);
26
+
27
+ if (parentPath === childPath) {
28
+ return true;
29
+ }
30
+
31
+ return childPath.startsWith(parentPath + path.sep);
32
+ }
33
+
34
+ /**
35
+ * Joins the given paths to the parentPath, ensuring that the resulting path
36
+ * is still contained within the parentPath. If not, it throws an error to
37
+ * prevent path traversal vulnerabilities.
38
+ *
39
+ * @throws {UnexpectedError} If the resulting path is not contained within the parentPath.
40
+ */
41
+ export function safeJoinPath(parentPath, ...paths) {
42
+ const candidate = path.join(parentPath, ...paths);
43
+
44
+ if (!isContainedWithin(parentPath, candidate)) {
45
+ throw new Error(
46
+ `Path traversal detected, refusing to join paths: ${parentPath} and ${JSON.stringify(paths)}`,
47
+ );
48
+ }
49
+
50
+ return candidate;
51
+ }
52
+
19
53
  export const resolvePackage = (packageSpec) => {
20
54
  // Validate input to prevent command injection
21
55
  if (!/^[a-zA-Z0-9@/_.-]+$/.test(packageSpec)) {
@@ -57,7 +91,7 @@ const downloadAndExtractPackage = async (packageName, version) => {
57
91
  }
58
92
 
59
93
  // Unpack the tarball
60
- const packageDir = path.join(TEMP_DIR, `${packageName}-${version}`);
94
+ const packageDir = safeJoinPath(TEMP_DIR, `${packageName}-${version}`);
61
95
  fs.mkdirSync(packageDir, { recursive: true });
62
96
  const tarResult = spawnSync(
63
97
  'tar',
@@ -70,7 +104,7 @@ const downloadAndExtractPackage = async (packageName, version) => {
70
104
  if (tarResult.status !== 0) {
71
105
  throw new Error(`tar extraction failed: ${tarResult.stderr?.toString()}`);
72
106
  }
73
- fs.unlinkSync(path.join(TEMP_DIR, tarballName));
107
+ fs.unlinkSync(safeJoinPath(TEMP_DIR, tarballName));
74
108
 
75
109
  return packageDir;
76
110
  } catch (error) {