From b1a2ce1e9f068219bc23463b4e89c7bee8f60dfb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Mar 2026 19:06:45 +0000 Subject: [PATCH] fix: prevent findFiles from escaping parent when directory is outside workspace If path.relative() produces a path like '../../outside' (when a PHP file is opened from outside the workspace root), findFiles was walking up from outside the parent boundary and could return a config file from an unrelated project. Add an early-exit guard: if the resolved starting directory does not sit inside resolvedParent, return null immediately. Also replace the raw string comparison 'parent === currentDir' with the normalised equivalent 'resolvedParent === currentDir' (carries forward the normalisation from the resolvedParent variable). Add a unit test that demonstrates the previous escape and confirms the fix: 'returns null when directory resolves outside parent'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/utils.js | 15 +++++++++++++-- test/unit.test.js | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 5627bec..624e359 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -13,7 +13,18 @@ const fs = require("fs"); */ function findFiles(parent, directory, name) { const names = [].concat(name); - const chunks = path.resolve(parent, directory).split(path.sep); + const resolvedParent = path.resolve(parent); + const resolvedDir = path.resolve(resolvedParent, directory); + + // If the resolved starting directory escapes the parent (e.g. a + // path.relative() result like "../../outside"), do not search outside the + // workspace — return null immediately. + if (resolvedDir !== resolvedParent && + !resolvedDir.startsWith(resolvedParent + path.sep)) { + return null; + } + + const chunks = resolvedDir.split(path.sep); while (chunks.length) { let currentDir = chunks.join(path.sep); @@ -23,7 +34,7 @@ function findFiles(parent, directory, name) { return filePath; } } - if (parent === currentDir) { + if (resolvedParent === currentDir) { break; } chunks.pop(); diff --git a/test/unit.test.js b/test/unit.test.js index 0fb759e..0a7cdec 100644 --- a/test/unit.test.js +++ b/test/unit.test.js @@ -103,4 +103,18 @@ describe("findFiles", () => { const result = findFiles(path.join(tmpRoot, "single"), ".", "phpcs.xml"); assert.equal(result, expected); }); + + test("returns null when directory resolves outside parent (no workspace escape)", () => { + // Simulates a file opened from outside the workspace, where + // path.relative(workspaceRoot, filePath) produces "../../outside". + // findFiles must NOT search outside the parent boundary. + mkFile("outside-config.xml"); // exists above the workspace root + mkDir("workspace"); + const result = findFiles( + path.join(tmpRoot, "workspace"), + path.join("..", ".."), // escapes tmpRoot/workspace + "outside-config.xml" + ); + assert.equal(result, null); + }); });