From 7076fbbe686e1091c850831d19eb4ac101fa22be Mon Sep 17 00:00:00 2001 From: robobun Date: Sun, 23 Nov 2025 01:41:17 -0800 Subject: [PATCH] fix(glob): fix typo that caused patterns like `.*/*` to escape cwd boundary (#24939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Fixed a typo in `makeComponent` that incorrectly identified 2-character patterns starting with `.` (like `.*`) as `..` (DotBack) patterns - The condition checked `pattern[component.start] == '.'` twice instead of checking both characters at positions 0 and 1 - This caused patterns like `.*/*` to be parsed as `../` + `*`, making the glob walker traverse into parent directories Fixes #24936 ## Test plan - [x] Added tests in `test/js/bun/glob/scan.test.ts` that verify patterns like `.*/*` and `.*/**/*.ts` don't escape the cwd boundary - [x] Tests fail with system bun (bug reproduced) and pass with the fix - [x] All existing glob tests pass (169 tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Bot Co-authored-by: Claude Co-authored-by: Jarred Sumner --- src/glob/GlobWalker.zig | 4 +- test/js/bun/glob/scan.test.ts | 72 +++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/glob/GlobWalker.zig b/src/glob/GlobWalker.zig index 36bf4c12a6..2fda733a46 100644 --- a/src/glob/GlobWalker.zig +++ b/src/glob/GlobWalker.zig @@ -1431,12 +1431,12 @@ pub fn GlobWalker_( if (component.len == 0) return null; out: { - if (component.len == 1 and pattern[component.start] == '.') { + if (bun.strings.eqlComptime(pattern[component.start .. component.start + component.len], ".")) { component.syntax_hint = .Dot; has_relative_patterns.* = true; break :out; } - if (component.len == 2 and pattern[component.start] == '.' and pattern[component.start] == '.') { + if (bun.strings.eqlComptime(pattern[component.start .. component.start + component.len], "..")) { component.syntax_hint = .DotBack; has_relative_patterns.* = true; break :out; diff --git a/test/js/bun/glob/scan.test.ts b/test/js/bun/glob/scan.test.ts index 61e97cc907..54b23bda26 100644 --- a/test/js/bun/glob/scan.test.ts +++ b/test/js/bun/glob/scan.test.ts @@ -665,6 +665,78 @@ describe("absolute path pattern", async () => { }); }); +// https://github.com/oven-sh/bun/issues/24936 +describe("glob scan should not escape cwd boundary", () => { + test("pattern .*/* should not match parent directory via ..", async () => { + // Create a directory structure where we can verify paths don't escape cwd + const tempdir = tempDirWithFiles("glob-cwd-escape", { + ".hidden": { + "file.txt": "hidden file content", + }, + ".dotfile": "dot file", + "regular": { + "file.txt": "regular file", + }, + }); + + const glob = new Glob(".*/*"); + const entries = await Array.fromAsync( + glob.scan({ + cwd: tempdir, + onlyFiles: false, + dot: true, // Need dot:true to match dotfiles/directories + }), + ); + + // All entries should be within the cwd - none should start with ../ + for (const entry of entries) { + expect(entry.startsWith("../")).toBe(false); + expect(entry.startsWith("..\\")).toBe(false); + expect(entry.includes("/../")).toBe(false); + expect(entry.includes("\\..\\")).toBe(false); + } + + // Should match .hidden/file.txt but not escape to parent + expect(entries.sort()).toEqual([`.hidden${path.sep}file.txt`].sort()); + }); + + test("pattern .*/**/*.ts should not escape cwd", async () => { + const tempdir = tempDirWithFiles("glob-cwd-escape-ts", { + ".config": { + "settings.ts": "export default {}", + "nested": { + "deep.ts": "export const x = 1", + }, + }, + "src": { + "index.ts": "console.log('hi')", + }, + }); + + const glob = new Glob(".*/**/*.ts"); + const entries = await Array.fromAsync( + glob.scan({ + cwd: tempdir, + onlyFiles: true, + dot: true, // Need dot:true to match dotfiles/directories + }), + ); + + // All entries should be within the cwd + for (const entry of entries) { + expect(entry.startsWith("../")).toBe(false); + expect(entry.startsWith("..\\")).toBe(false); + expect(entry.includes("/../")).toBe(false); + expect(entry.includes("\\..\\")).toBe(false); + } + + // Should match files in .config but not escape to parent + expect(entries.sort()).toEqual( + [`.config${path.sep}settings.ts`, `.config${path.sep}nested${path.sep}deep.ts`].sort(), + ); + }); +}); + describe("glob.scan wildcard fast path", async () => { test("works", async () => { const tempdir = tempDirWithFiles("glob-scan-wildcard-fast-path", {