From 1cfbff4d8f5615180ed6a2b7a02bca27f8080270 Mon Sep 17 00:00:00 2001 From: cnYui Date: Sat, 13 Jun 2026 14:44:53 +0900 Subject: [PATCH 1/7] fix: restrict bash in plan mode --- packages/opencode/src/agent/agent.ts | 15 +++++++++++++++ packages/opencode/test/agent/agent.test.ts | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index ff342ff2..3f5b39ad 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -163,6 +163,21 @@ export const layer = Layer.effect( external_directory: { [path.join(Global.Path.data, "plans", "*")]: "allow", }, + bash: { + "*": "deny", + "pwd *": "allow", + "ls *": "allow", + "cat *": "allow", + "grep *": "allow", + "rg *": "allow", + "find *": "allow", + "git status *": "allow", + "git diff *": "allow", + "git log *": "allow", + "git show *": "allow", + "Get-ChildItem *": "allow", + "Get-Content *": "allow", + }, edit: { "*": "deny", [path.join(".mimocode", "plans", "*.md")]: "allow", diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 9befab0a..3dfc7bbc 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -75,6 +75,25 @@ test("plan agent denies edits except .mimocode/plans/*", async () => { }) }) +test("plan agent allows only read-only bash commands by default", async () => { + await using tmp = await tmpdir() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const plan = await load(tmp.path, (svc) => svc.get("plan")) + expect(plan).toBeDefined() + + for (const pattern of ["mkdir test", "touch output.txt", "echo test > output.txt"]) { + expect(Permission.evaluate("bash", pattern, plan!.permission).action).toBe("deny") + } + + for (const pattern of ["ls -la", "git status --short", "rg needle src", "Get-ChildItem ."]) { + expect(Permission.evaluate("bash", pattern, plan!.permission).action).toBe("allow") + } + }, + }) +}) + test("explore agent denies edit and write", async () => { await using tmp = await tmpdir() await Instance.provide({ From b346994e888696c077f9cf328f41d6f178c44ad1 Mon Sep 17 00:00:00 2001 From: cnYui Date: Sun, 14 Jun 2026 09:22:49 +0900 Subject: [PATCH 2/7] fix: deny write-capable plan bash patterns --- packages/opencode/src/agent/agent.ts | 8 ++++++++ packages/opencode/test/agent/agent.test.ts | 14 +++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 3f5b39ad..3c3c1903 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -177,6 +177,14 @@ export const layer = Layer.effect( "git show *": "allow", "Get-ChildItem *": "allow", "Get-Content *": "allow", + "*>*": "deny", + "*>>*": "deny", + "* > *": "deny", + "* >> *": "deny", + "find * -delete": "deny", + "find * -exec *": "deny", + "git diff --output=*": "deny", + "git diff * --output=*": "deny", }, edit: { "*": "deny", diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 3dfc7bbc..584b297a 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -83,7 +83,19 @@ test("plan agent allows only read-only bash commands by default", async () => { const plan = await load(tmp.path, (svc) => svc.get("plan")) expect(plan).toBeDefined() - for (const pattern of ["mkdir test", "touch output.txt", "echo test > output.txt"]) { + for (const pattern of [ + "mkdir test", + "touch output.txt", + "echo test > output.txt", + "cat package.json > out.txt", + "cat package.json>out.txt", + "grep foo file > out.txt", + "rg needle src >> out.txt", + "rg needle src>>out.txt", + "find . -delete", + "find . -exec rm {} \\;", + "git diff --output=out.patch", + ]) { expect(Permission.evaluate("bash", pattern, plan!.permission).action).toBe("deny") } From ff901bc856810f2155d84d263207ad20ff62e98e Mon Sep 17 00:00:00 2001 From: cnYui Date: Sun, 14 Jun 2026 20:14:42 +0900 Subject: [PATCH 3/7] fix: deny more plan bash write patterns --- packages/opencode/src/agent/agent.ts | 4 ++++ packages/opencode/test/agent/agent.test.ts | 3 +++ 2 files changed, 7 insertions(+) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 3c3c1903..b3469ad7 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -182,8 +182,12 @@ export const layer = Layer.effect( "* > *": "deny", "* >> *": "deny", "find * -delete": "deny", + "find * -delete *": "deny", "find * -exec *": "deny", + "find * -execdir *": "deny", + "git diff --output *": "deny", "git diff --output=*": "deny", + "git diff * --output *": "deny", "git diff * --output=*": "deny", }, edit: { diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 584b297a..dde2f8b4 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -93,8 +93,11 @@ test("plan agent allows only read-only bash commands by default", async () => { "rg needle src >> out.txt", "rg needle src>>out.txt", "find . -delete", + "find . -delete -print", "find . -exec rm {} \\;", + "find . -execdir rm {} \\;", "git diff --output=out.patch", + "git diff --output out.patch", ]) { expect(Permission.evaluate("bash", pattern, plan!.permission).action).toBe("deny") } From 115865add7057dc9668cc79fcb7ce2b5b70181f6 Mon Sep 17 00:00:00 2001 From: cnYui Date: Sun, 14 Jun 2026 20:20:36 +0900 Subject: [PATCH 4/7] fix: deny plan bash pipelines --- packages/opencode/src/agent/agent.ts | 1 + packages/opencode/test/agent/agent.test.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index b3469ad7..e7cfd4d8 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -181,6 +181,7 @@ export const layer = Layer.effect( "*>>*": "deny", "* > *": "deny", "* >> *": "deny", + "*|*": "deny", "find * -delete": "deny", "find * -delete *": "deny", "find * -exec *": "deny", diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index dde2f8b4..8ab698cc 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -92,12 +92,17 @@ test("plan agent allows only read-only bash commands by default", async () => { "grep foo file > out.txt", "rg needle src >> out.txt", "rg needle src>>out.txt", + "cat package.json | tee out.txt", + "rg needle src | tee out.txt", "find . -delete", "find . -delete -print", "find . -exec rm {} \\;", "find . -execdir rm {} \\;", + "find . -print0 | xargs rm -f", "git diff --output=out.patch", "git diff --output out.patch", + "git diff | tee out.patch", + "Get-Content package.json | Set-Content out.txt", ]) { expect(Permission.evaluate("bash", pattern, plan!.permission).action).toBe("deny") } From f59c3c8e87600a7c9178fd6c223e94220fe24894 Mon Sep 17 00:00:00 2001 From: cnYui Date: Sun, 14 Jun 2026 20:34:35 +0900 Subject: [PATCH 5/7] fix: deny plan bash control operators --- packages/opencode/src/agent/agent.ts | 4 ++++ packages/opencode/test/agent/agent.test.ts | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index e7cfd4d8..cec8ca89 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -182,6 +182,10 @@ export const layer = Layer.effect( "* > *": "deny", "* >> *": "deny", "*|*": "deny", + "*;*": "deny", + "*&&*": "deny", + "*`*": "deny", + "*$(*": "deny", "find * -delete": "deny", "find * -delete *": "deny", "find * -exec *": "deny", diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 8ab698cc..2d5f0a91 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -87,11 +87,15 @@ test("plan agent allows only read-only bash commands by default", async () => { "mkdir test", "touch output.txt", "echo test > output.txt", + "cat package.json; touch out.txt", "cat package.json > out.txt", "cat package.json>out.txt", + "cat package.json `touch out.txt`", + "cat package.json $(touch out.txt)", "grep foo file > out.txt", "rg needle src >> out.txt", "rg needle src>>out.txt", + "ls && touch out.txt", "cat package.json | tee out.txt", "rg needle src | tee out.txt", "find . -delete", @@ -102,6 +106,8 @@ test("plan agent allows only read-only bash commands by default", async () => { "git diff --output=out.patch", "git diff --output out.patch", "git diff | tee out.patch", + "git status && rm -rf x", + "Get-Content package.json; Set-Content out.txt", "Get-Content package.json | Set-Content out.txt", ]) { expect(Permission.evaluate("bash", pattern, plan!.permission).action).toBe("deny") From 5343042d683161eaee361eebf16659ba7d98901d Mon Sep 17 00:00:00 2001 From: cnYui Date: Sun, 14 Jun 2026 20:52:37 +0900 Subject: [PATCH 6/7] fix: deny plan bash command separators --- packages/opencode/src/agent/agent.ts | 2 ++ packages/opencode/test/agent/agent.test.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index cec8ca89..ed4bf6d4 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -182,7 +182,9 @@ export const layer = Layer.effect( "* > *": "deny", "* >> *": "deny", "*|*": "deny", + "*\n*": "deny", "*;*": "deny", + "*&*": "deny", "*&&*": "deny", "*`*": "deny", "*$(*": "deny", diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 2d5f0a91..1ac04bad 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -88,6 +88,8 @@ test("plan agent allows only read-only bash commands by default", async () => { "touch output.txt", "echo test > output.txt", "cat package.json; touch out.txt", + "cat package.json\ntouch out.txt", + "cat package.json & touch out.txt", "cat package.json > out.txt", "cat package.json>out.txt", "cat package.json `touch out.txt`", From 4264ce01389f2fcf45c538d06c54c4914cb07829 Mon Sep 17 00:00:00 2001 From: cnYui Date: Mon, 15 Jun 2026 07:51:07 +0900 Subject: [PATCH 7/7] fix: deny plan bash process substitution --- packages/opencode/src/agent/agent.ts | 1 + packages/opencode/test/agent/agent.test.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index ed4bf6d4..2a1f1cb4 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -182,6 +182,7 @@ export const layer = Layer.effect( "* > *": "deny", "* >> *": "deny", "*|*": "deny", + "*<(*": "deny", "*\n*": "deny", "*;*": "deny", "*&*": "deny", diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 1ac04bad..cdef54f4 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -94,6 +94,9 @@ test("plan agent allows only read-only bash commands by default", async () => { "cat package.json>out.txt", "cat package.json `touch out.txt`", "cat package.json $(touch out.txt)", + "cat <(touch out.txt)", + "cat package.json <(touch out.txt)", + "cat < <(touch out.txt)", "grep foo file > out.txt", "rg needle src >> out.txt", "rg needle src>>out.txt",