diff --git a/src/test/fixtures/zip-files/index.ts b/src/test/fixtures/zip-files/index.ts index c67c361d708..8997ebd3d57 100644 --- a/src/test/fixtures/zip-files/index.ts +++ b/src/test/fixtures/zip-files/index.ts @@ -10,6 +10,7 @@ export const ZIP_CASES = [ { name: "compressed-standard" }, { name: "uncompressed" }, { name: "zip-slip", wantErr: "a path outside of" }, + { name: "zip-slip-prefix", wantErr: "a path outside of" }, { name: "zip64" }, ].map(({ name, wantErr }) => ({ name, diff --git a/src/test/fixtures/zip-files/node-unzipper-testData/zip-slip-prefix/archive.zip b/src/test/fixtures/zip-files/node-unzipper-testData/zip-slip-prefix/archive.zip new file mode 100644 index 00000000000..3d658a129a5 Binary files /dev/null and b/src/test/fixtures/zip-files/node-unzipper-testData/zip-slip-prefix/archive.zip differ diff --git a/src/unzip.ts b/src/unzip.ts index 3edd9687382..75b19d9b5d8 100644 --- a/src/unzip.ts +++ b/src/unzip.ts @@ -124,17 +124,11 @@ const extractEntriesFromBuffer = async (data: Buffer, outputDir: string): Promis }; function isChildDir(parentDir: string, potentialChild: string): boolean { - try { - // 1. Resolve and normalize both paths to absolute paths - const resolvedParent = path.resolve(parentDir); - const resolvedChild = path.resolve(potentialChild); - // The child path must start with the parent path and not be the same path. - return resolvedChild.startsWith(resolvedParent) && resolvedChild !== resolvedParent; - } catch (error) { - // If either path does not exist, an error will be thrown. - // In this case, the potential child cannot be a subdirectory. - return false; - } + const resolvedParent = path.resolve(parentDir); + const resolvedChild = path.resolve(potentialChild); + const relative = path.relative(resolvedParent, resolvedChild); + // relative path must not escape the parent and must not be the parent itself + return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative); } export const unzip = async (inputPath: string, outputDir: string): Promise => {