summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLuke Parker <[email protected]>2026-02-23 12:05:21 +1000
committerGitHub <[email protected]>2026-02-23 12:05:21 +1000
commitee754c46f992dd4024e56e93246421246d16d13f (patch)
treeb08b67a0abf195f2f84cf5d2b9a6da500547214f
parent0042a07052ec0db777b2ea8bff46101466f0a942 (diff)
downloadopencode-ee754c46f992dd4024e56e93246421246d16d13f.tar.gz
opencode-ee754c46f992dd4024e56e93246421246d16d13f.zip
fix(win32): normalize paths at permission boundaries (#14738)
-rw-r--r--packages/opencode/src/tool/external-directory.ts2
-rw-r--r--packages/opencode/src/util/wildcard.ts5
-rw-r--r--packages/opencode/test/tool/bash.test.ts4
-rw-r--r--packages/opencode/test/tool/read.test.ts4
-rw-r--r--packages/opencode/test/util/wildcard.test.ts15
5 files changed, 24 insertions, 6 deletions
diff --git a/packages/opencode/src/tool/external-directory.ts b/packages/opencode/src/tool/external-directory.ts
index 1d3958fc4..5d8885b2a 100644
--- a/packages/opencode/src/tool/external-directory.ts
+++ b/packages/opencode/src/tool/external-directory.ts
@@ -18,7 +18,7 @@ export async function assertExternalDirectory(ctx: Tool.Context, target?: string
const kind = options?.kind ?? "file"
const parentDir = kind === "directory" ? target : path.dirname(target)
- const glob = path.join(parentDir, "*")
+ const glob = path.join(parentDir, "*").replaceAll("\\", "/")
await ctx.ask({
permission: "external_directory",
diff --git a/packages/opencode/src/util/wildcard.ts b/packages/opencode/src/util/wildcard.ts
index 4a6eba96f..f54b6c85f 100644
--- a/packages/opencode/src/util/wildcard.ts
+++ b/packages/opencode/src/util/wildcard.ts
@@ -2,6 +2,8 @@ import { sortBy, pipe } from "remeda"
export namespace Wildcard {
export function match(str: string, pattern: string) {
+ if (str) str = str.replaceAll("\\", "/")
+ if (pattern) pattern = pattern.replaceAll("\\", "/")
let escaped = pattern
.replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape special regex chars
.replace(/\*/g, ".*") // * becomes .*
@@ -13,7 +15,8 @@ export namespace Wildcard {
escaped = escaped.slice(0, -3) + "( .*)?"
}
- return new RegExp("^" + escaped + "$", "s").test(str)
+ const flags = process.platform === "win32" ? "si" : "s"
+ return new RegExp("^" + escaped + "$", flags).test(str)
}
export function all(input: string, patterns: Record<string, any>) {
diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts
index 3bd923b60..db05f8f62 100644
--- a/packages/opencode/test/tool/bash.test.ts
+++ b/packages/opencode/test/tool/bash.test.ts
@@ -203,8 +203,8 @@ describe("tool.bash permissions", () => {
await bash.execute(
{
- command: "rm tmpfile",
- description: "Remove tmpfile",
+ command: `rm -rf ${path.join(tmp.path, "nested")}`,
+ description: "remove nested dir",
},
testCtx,
)
diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts
index 88228f14e..b22fc3e71 100644
--- a/packages/opencode/test/tool/read.test.ts
+++ b/packages/opencode/test/tool/read.test.ts
@@ -74,7 +74,7 @@ describe("tool.read external_directory permission", () => {
await read.execute({ filePath: path.join(outerTmp.path, "secret.txt") }, testCtx)
const extDirReq = requests.find((r) => r.permission === "external_directory")
expect(extDirReq).toBeDefined()
- expect(extDirReq!.patterns.some((p) => p.includes(outerTmp.path))).toBe(true)
+ expect(extDirReq!.patterns.some((p) => p.includes(outerTmp.path.replaceAll("\\", "/")))).toBe(true)
},
})
})
@@ -100,7 +100,7 @@ describe("tool.read external_directory permission", () => {
await read.execute({ filePath: path.join(outerTmp.path, "external") }, testCtx)
const extDirReq = requests.find((r) => r.permission === "external_directory")
expect(extDirReq).toBeDefined()
- expect(extDirReq!.patterns).toContain(path.join(outerTmp.path, "external", "*"))
+ expect(extDirReq!.patterns).toContain(path.join(outerTmp.path, "external", "*").replaceAll("\\", "/"))
},
})
})
diff --git a/packages/opencode/test/util/wildcard.test.ts b/packages/opencode/test/util/wildcard.test.ts
index 9cd0e9b94..56e753d12 100644
--- a/packages/opencode/test/util/wildcard.test.ts
+++ b/packages/opencode/test/util/wildcard.test.ts
@@ -73,3 +73,18 @@ test("allStructured handles sed flags", () => {
expect(Wildcard.allStructured({ head: "sed", tail: ["-n", "1p", "file"] }, rules)).toBe("allow")
expect(Wildcard.allStructured({ head: "sed", tail: ["-i", "-n", "/./p", "myfile.txt"] }, rules)).toBe("ask")
})
+
+test("match normalizes slashes for cross-platform globbing", () => {
+ expect(Wildcard.match("C:\\Windows\\System32\\*", "C:/Windows/System32/*")).toBe(true)
+ expect(Wildcard.match("C:/Windows/System32/drivers", "C:\\Windows\\System32\\*")).toBe(true)
+})
+
+test("match handles case-insensitivity on Windows", () => {
+ if (process.platform === "win32") {
+ expect(Wildcard.match("C:\\windows\\system32\\hosts", "C:/Windows/System32/*")).toBe(true)
+ expect(Wildcard.match("c:/windows/system32/hosts", "C:\\Windows\\System32\\*")).toBe(true)
+ } else {
+ // Unix paths are case-sensitive
+ expect(Wildcard.match("/users/test/file", "/Users/test/*")).toBe(false)
+ }
+})