summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAiden Cline <[email protected]>2026-01-12 13:02:29 -0600
committerAiden Cline <[email protected]>2026-01-12 13:02:29 -0600
commit62702fbd11879bde0c6153346f4ad9f02defa51f (patch)
treeb60e39310719aae84eb51237ec9a5d1da0e84761
parent22c68a6992e9764d71c2a3e058c3fd97136febba (diff)
downloadopencode-62702fbd11879bde0c6153346f4ad9f02defa51f.tar.gz
opencode-62702fbd11879bde0c6153346f4ad9f02defa51f.zip
fix: permissions wildcarding so that for ex: 'ls *' includes ls * AND 'ls' to prevent having to double state commands or use 'ls*'
-rw-r--r--packages/opencode/src/util/wildcard.ts22
-rw-r--r--packages/opencode/test/util/wildcard.test.ts20
2 files changed, 32 insertions, 10 deletions
diff --git a/packages/opencode/src/util/wildcard.ts b/packages/opencode/src/util/wildcard.ts
index 9b595a0a9..4a6eba96f 100644
--- a/packages/opencode/src/util/wildcard.ts
+++ b/packages/opencode/src/util/wildcard.ts
@@ -2,16 +2,18 @@ import { sortBy, pipe } from "remeda"
export namespace Wildcard {
export function match(str: string, pattern: string) {
- const regex = new RegExp(
- "^" +
- pattern
- .replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape special regex chars
- .replace(/\*/g, ".*") // * becomes .*
- .replace(/\?/g, ".") + // ? becomes .
- "$",
- "s", // s flag enables multiline matching
- )
- return regex.test(str)
+ let escaped = pattern
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape special regex chars
+ .replace(/\*/g, ".*") // * becomes .*
+ .replace(/\?/g, ".") // ? becomes .
+
+ // If pattern ends with " *" (space + wildcard), make the trailing part optional
+ // This allows "ls *" to match both "ls" and "ls -la"
+ if (escaped.endsWith(" .*")) {
+ escaped = escaped.slice(0, -3) + "( .*)?"
+ }
+
+ return new RegExp("^" + escaped + "$", "s").test(str)
}
export function all(input: string, patterns: Record<string, any>) {
diff --git a/packages/opencode/test/util/wildcard.test.ts b/packages/opencode/test/util/wildcard.test.ts
index f7f1e1545..9cd0e9b94 100644
--- a/packages/opencode/test/util/wildcard.test.ts
+++ b/packages/opencode/test/util/wildcard.test.ts
@@ -7,6 +7,26 @@ test("match handles glob tokens", () => {
expect(Wildcard.match("foo+bar", "foo+bar")).toBe(true)
})
+test("match with trailing space+wildcard matches command with or without args", () => {
+ // "ls *" should match "ls" (no args) and "ls -la" (with args)
+ expect(Wildcard.match("ls", "ls *")).toBe(true)
+ expect(Wildcard.match("ls -la", "ls *")).toBe(true)
+ expect(Wildcard.match("ls foo bar", "ls *")).toBe(true)
+
+ // "ls*" (no space) should NOT match "ls" alone — wait, it should because .* matches empty
+ // but it WILL match "lstmeval" which is the dangerous case users should avoid
+ expect(Wildcard.match("ls", "ls*")).toBe(true)
+ expect(Wildcard.match("lstmeval", "ls*")).toBe(true)
+
+ // "ls *" (with space) should NOT match "lstmeval"
+ expect(Wildcard.match("lstmeval", "ls *")).toBe(false)
+
+ // multi-word commands
+ expect(Wildcard.match("git status", "git *")).toBe(true)
+ expect(Wildcard.match("git", "git *")).toBe(true)
+ expect(Wildcard.match("git commit -m foo", "git *")).toBe(true)
+})
+
test("all picks the most specific pattern", () => {
const rules = {
"*": "deny",