fix(producer): isPathInside accepts children whose names start with two dots (#5834)

This commit is contained in:
kevin
2026-10-28 01:31:40 +00:00
committed by GitHub
parent e5b4f2bf78
commit c8052fbdae
4 changed files with 52 additions and 2 deletions
+23
View File
@@ -0,0 +1,23 @@
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
import { isPathInside } from "./assetPaths.js";
describe("isPathInside", () => {
it("accepts the parent itself and nested children", () => {
expect(isPathInside(resolve("/project"), resolve("/project"))).toBe(true);
expect(isPathInside(resolve("/project/assets/logo.png"), resolve("/project"))).toBe(true);
});
it("rejects paths above the parent and siblings sharing its prefix", () => {
expect(isPathInside(resolve("/project/../secret.txt"), resolve("/project"))).toBe(false);
expect(isPathInside(resolve("/"), resolve("/project"))).toBe(false);
expect(isPathInside(resolve("/project-other/logo.png"), resolve("/project"))).toBe(false);
});
it("accepts a child whose name starts with two dots", () => {
// `..assets` is an ordinary directory name, not a parent-dir segment.
expect(isPathInside(resolve("/project/..assets/logo.png"), resolve("/project"))).toBe(true);
expect(isPathInside(resolve("/project/..intro.mp4"), resolve("/project"))).toBe(true);
});
});
+3 -1
View File
@@ -36,12 +36,14 @@ export function isNonRelativeUrl(val: string): boolean {
* Cross-platform containment check: is `childPath` inside `parentPath`?
* Equality counts as "inside".
*/
const LEADING_PARENT_SEGMENT_RE = /^\.\.(?:[\\/]|$)/;
export function isPathInside(childPath: string, parentPath: string): boolean {
const absChild = resolve(childPath);
const absParent = resolve(parentPath);
if (absChild === absParent) return true;
const rel = relative(absParent, absChild);
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
return rel !== "" && !LEADING_PARENT_SEGMENT_RE.test(rel) && !isAbsolute(rel);
}
export {
+23
View File
@@ -49,6 +49,29 @@ describe("isPathInside", () => {
expect(isPathInside(resolve("/foo/bar/../../etc/passwd"), resolve("/foo/bar"))).toBe(false);
});
it("returns true for a child whose name starts with two dots", () => {
// `..assets` is an ordinary directory name, not a parent-dir segment.
expect(isPathInside(resolve("/foo/bar/..assets/logo.png"), resolve("/foo/bar"))).toBe(true);
expect(isPathInside(resolve("/foo/bar/..intro.mp4"), resolve("/foo/bar"))).toBe(true);
expect(
isPathInside(
win32.resolve("C:\\project\\..fonts\\Inter.woff2"),
win32.resolve("C:\\project"),
{
pathModule: win32,
},
),
).toBe(true);
});
it("still rejects a Windows child above the parent", () => {
expect(
isPathInside(win32.resolve("C:\\project\\..\\secret.txt"), win32.resolve("C:\\project"), {
pathModule: win32,
}),
).toBe(false);
});
it("normalises trailing slashes on parent", () => {
expect(isPathInside(resolve("/foo/bar/baz"), resolve("/foo/bar/"))).toBe(true);
});
+3 -1
View File
@@ -28,6 +28,8 @@ type PathModuleLike = {
isAbsolute: (path: string) => boolean;
};
const LEADING_PARENT_SEGMENT_RE = /^\.\.(?:[\\/]|$)/;
type IsPathInsideOptions = {
pathModule?: PathModuleLike;
};
@@ -59,7 +61,7 @@ export function isPathInside(
// `relative()` returns "" when paths are equal, ".." or "..\\foo" when child
// is above the parent, and an absolute path when they live on different
// drives/volumes (Windows) — none of which count as "inside".
return rel !== "" && !rel.startsWith("..") && !isPathAbsolute(rel);
return rel !== "" && !LEADING_PARENT_SEGMENT_RE.test(rel) && !isPathAbsolute(rel);
}
/**