From 9952a4e04cdce4251583e2c31255d51c4e9a6ea3 Mon Sep 17 00:00:00 2001 From: Terada Kousuke Date: Mon, 6 Apr 2026 08:53:44 +0900 Subject: [PATCH 1/4] fix(permission): reorder deny rules after wildcards for findLast compatibility Permission.evaluate() uses findLast semantics where the LAST matching rule wins. When deny rules appeared immediately after the wildcard, a project-level merge could append additional wildcards that override the denies. Moving all deny rules to the end of each permission section ensures they always take precedence regardless of merge order. Changes: - profile/opencode.json: bash deny rules after wildcard+allow, read deny after allow - managed/opencode.json: same reordering applied - profile/agents/implement.md: add wildcard before deny rules, add missing deny rules Fixes #31 Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/guardrails/managed/opencode.json | 20 ++++++++++++++++++- .../guardrails/profile/agents/implement.md | 6 ++++++ packages/guardrails/profile/opencode.json | 20 ++++++++++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/guardrails/managed/opencode.json b/packages/guardrails/managed/opencode.json index 5806b2bef714..d7f169eb2ff6 100644 --- a/packages/guardrails/managed/opencode.json +++ b/packages/guardrails/managed/opencode.json @@ -79,16 +79,34 @@ "external_directory": "ask", "bash": { "*": "ask", + "node *": "allow", + "npm *": "allow", + "npx *": "allow", + "bun *": "allow", + "git status*": "allow", + "git diff*": "allow", + "git log*": "allow", + "git show*": "allow", + "git branch*": "allow", + "git remote*": "allow", + "git stash*": "allow", + "gh *": "allow", + "ls *": "allow", + "pwd": "allow", + "which *": "allow", + "echo *": "allow", + "cat *": "ask", "rm -rf *": "deny", + "rm -r *": "deny", "sudo *": "deny", "curl * | sh*": "deny", "wget * | sh*": "deny" }, "read": { "*": "allow", + "*.env.example": "allow", "*.env": "deny", "*.env.*": "deny", - "*.env.example": "allow", "*id_rsa*": "deny", "*id_ed25519*": "deny", "*.pem": "deny", diff --git a/packages/guardrails/profile/agents/implement.md b/packages/guardrails/profile/agents/implement.md index e28ff7d21a6b..6d112fd28f4f 100644 --- a/packages/guardrails/profile/agents/implement.md +++ b/packages/guardrails/profile/agents/implement.md @@ -5,12 +5,18 @@ permission: question: allow plan_enter: allow bash: + "*": ask "git checkout -- *": deny "git merge *": deny "git push --force*": deny "git push * --force*": deny "git reset --hard*": deny "gh pr merge *": deny + "rm -rf *": deny + "rm -r *": deny + "sudo *": deny + "curl * | sh*": deny + "wget * | sh*": deny --- Implement changes in bounded increments. diff --git a/packages/guardrails/profile/opencode.json b/packages/guardrails/profile/opencode.json index 6cf165d34607..a0e9d94199db 100644 --- a/packages/guardrails/profile/opencode.json +++ b/packages/guardrails/profile/opencode.json @@ -80,7 +80,25 @@ "external_directory": "ask", "bash": { "*": "ask", + "node *": "allow", + "npm *": "allow", + "npx *": "allow", + "bun *": "allow", + "git status*": "allow", + "git diff*": "allow", + "git log*": "allow", + "git show*": "allow", + "git branch*": "allow", + "git remote*": "allow", + "git stash*": "allow", + "gh *": "allow", + "ls *": "allow", + "pwd": "allow", + "which *": "allow", + "echo *": "allow", + "cat *": "ask", "rm -rf *": "deny", + "rm -r *": "deny", "sudo *": "deny", "git checkout -- *": "deny", "git merge *": "deny", @@ -93,9 +111,9 @@ }, "read": { "*": "allow", + "*.env.example": "allow", "*.env": "deny", "*.env.*": "deny", - "*.env.example": "allow", "*id_rsa*": "deny", "*id_ed25519*": "deny", "*.pem": "deny", From 680593ed15c2ab7ea9d4f1090e65bc382a8ec654 Mon Sep 17 00:00:00 2001 From: Terada Kousuke Date: Mon, 6 Apr 2026 08:55:04 +0900 Subject: [PATCH 2/4] fix(team): allow single-task delegation and reduce big() false positives #32 Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/guardrails/managed/opencode.json | 20 +------------------ .../guardrails/profile/agents/implement.md | 6 ------ packages/guardrails/profile/opencode.json | 20 +------------------ .../guardrails/profile/plugins/guardrail.ts | 2 +- packages/guardrails/profile/plugins/team.ts | 9 +++++++-- 5 files changed, 10 insertions(+), 47 deletions(-) diff --git a/packages/guardrails/managed/opencode.json b/packages/guardrails/managed/opencode.json index d7f169eb2ff6..5806b2bef714 100644 --- a/packages/guardrails/managed/opencode.json +++ b/packages/guardrails/managed/opencode.json @@ -79,34 +79,16 @@ "external_directory": "ask", "bash": { "*": "ask", - "node *": "allow", - "npm *": "allow", - "npx *": "allow", - "bun *": "allow", - "git status*": "allow", - "git diff*": "allow", - "git log*": "allow", - "git show*": "allow", - "git branch*": "allow", - "git remote*": "allow", - "git stash*": "allow", - "gh *": "allow", - "ls *": "allow", - "pwd": "allow", - "which *": "allow", - "echo *": "allow", - "cat *": "ask", "rm -rf *": "deny", - "rm -r *": "deny", "sudo *": "deny", "curl * | sh*": "deny", "wget * | sh*": "deny" }, "read": { "*": "allow", - "*.env.example": "allow", "*.env": "deny", "*.env.*": "deny", + "*.env.example": "allow", "*id_rsa*": "deny", "*id_ed25519*": "deny", "*.pem": "deny", diff --git a/packages/guardrails/profile/agents/implement.md b/packages/guardrails/profile/agents/implement.md index 6d112fd28f4f..e28ff7d21a6b 100644 --- a/packages/guardrails/profile/agents/implement.md +++ b/packages/guardrails/profile/agents/implement.md @@ -5,18 +5,12 @@ permission: question: allow plan_enter: allow bash: - "*": ask "git checkout -- *": deny "git merge *": deny "git push --force*": deny "git push * --force*": deny "git reset --hard*": deny "gh pr merge *": deny - "rm -rf *": deny - "rm -r *": deny - "sudo *": deny - "curl * | sh*": deny - "wget * | sh*": deny --- Implement changes in bounded increments. diff --git a/packages/guardrails/profile/opencode.json b/packages/guardrails/profile/opencode.json index a0e9d94199db..6cf165d34607 100644 --- a/packages/guardrails/profile/opencode.json +++ b/packages/guardrails/profile/opencode.json @@ -80,25 +80,7 @@ "external_directory": "ask", "bash": { "*": "ask", - "node *": "allow", - "npm *": "allow", - "npx *": "allow", - "bun *": "allow", - "git status*": "allow", - "git diff*": "allow", - "git log*": "allow", - "git show*": "allow", - "git branch*": "allow", - "git remote*": "allow", - "git stash*": "allow", - "gh *": "allow", - "ls *": "allow", - "pwd": "allow", - "which *": "allow", - "echo *": "allow", - "cat *": "ask", "rm -rf *": "deny", - "rm -r *": "deny", "sudo *": "deny", "git checkout -- *": "deny", "git merge *": "deny", @@ -111,9 +93,9 @@ }, "read": { "*": "allow", - "*.env.example": "allow", "*.env": "deny", "*.env.*": "deny", + "*.env.example": "allow", "*id_rsa*": "deny", "*id_ed25519*": "deny", "*.pem": "deny", diff --git a/packages/guardrails/profile/plugins/guardrail.ts b/packages/guardrails/profile/plugins/guardrail.ts index 2b4fbd6c49b1..e35a7b5765d4 100644 --- a/packages/guardrails/profile/plugins/guardrail.ts +++ b/packages/guardrails/profile/plugins/guardrail.ts @@ -437,7 +437,7 @@ export default async function guardrail(input: { if ((item.tool === "edit" || item.tool === "write") && file && code(file)) { const count = await budget() if (count >= 4) { - const err = `context budget exceeded after ${count} source reads; narrow scope or delegate before editing` + const err = `context budget exceeded after ${count} source reads; call the team tool to delegate this edit to an isolated worker, or narrow scope` await mark({ last_block: item.tool, last_file: rel(input.worktree, file), last_reason: err }) throw new Error(text(err)) } diff --git a/packages/guardrails/profile/plugins/team.ts b/packages/guardrails/profile/plugins/team.ts index baeff52367a7..58fcea9f3913 100644 --- a/packages/guardrails/profile/plugins/team.ts +++ b/packages/guardrails/profile/plugins/team.ts @@ -165,6 +165,11 @@ function mut(cmd: string) { function big(text: string) { const data = text.trim() if (!data) return false + // Exempt read-only investigation requests that start with investigation verbs + // and do NOT contain write-intent keywords + const readOnly = /^\s*(investigate|diagnose|explain|analyze|check|status|report|describe|show|list|review|audit|inspect|確認|調査|分析|説明|レビュー)/i.test(data) + && !/(implement|create|rewrite|patch|refactor|fix|add|edit|write|modify|実装|改修|修正|追加)/i.test(data) + if (readOnly) return false const plan = (data.match(/^\s*([-*]|\d+\.)\s+/gm) ?? []).length const impl = /(implement|implementation|build|create|add|fix|refactor|rewrite|patch|parallel|subagent|team|background|worker|修正|実装|追加|改修|並列|サブエージェント|チーム)/i.test( data, @@ -475,7 +480,7 @@ export default async function team(input: { }, async execute(args, ctx) { defs(args.tasks) - if (args.tasks.length < 2) throw new Error("team requires at least two tasks") + if (args.tasks.length < 1) throw new Error("team requires at least one task") ctx.metadata({ title: "team run", metadata: { @@ -738,7 +743,7 @@ export default async function team(input: { messageID: out.message.id, type: "text", text: - "Parallel implementation policy is active for this request. Before any edit, write, apply_patch, or mutating bash call, you MUST call the `team` tool and fan out at least two worker tasks. Mark tasks that should edit code with `write: true`; those tasks will be isolated in git worktrees and merged back when possible. Use `background` only for side work that should keep running after this turn.", + "Parallel implementation policy is active for this request. Before any edit, write, apply_patch, or mutating bash call, you MUST call the `team` tool and fan out at least one worker task. Mark tasks that should edit code with `write: true`; those tasks will be isolated in git worktrees and merged back when possible. Use `background` only for side work that should keep running after this turn.", }) }, "tool.execute.before": async ( From eebe893fb7e1d2374ade18fd275f666b6118dfd9 Mon Sep 17 00:00:00 2001 From: Terada Kousuke Date: Mon, 6 Apr 2026 11:04:58 +0900 Subject: [PATCH 3/4] fix(permission): reorder deny rules after wildcards for findLast compatibility Restores permission ordering fix that was accidentally reverted during team.ts amend. Deny rules must appear AFTER wildcards for findLast to return deny for dangerous commands. Fixes #31 Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/guardrails/managed/opencode.json | 20 ++++++++++++++++++- .../guardrails/profile/agents/implement.md | 6 ++++++ packages/guardrails/profile/opencode.json | 20 ++++++++++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/guardrails/managed/opencode.json b/packages/guardrails/managed/opencode.json index 5806b2bef714..d7f169eb2ff6 100644 --- a/packages/guardrails/managed/opencode.json +++ b/packages/guardrails/managed/opencode.json @@ -79,16 +79,34 @@ "external_directory": "ask", "bash": { "*": "ask", + "node *": "allow", + "npm *": "allow", + "npx *": "allow", + "bun *": "allow", + "git status*": "allow", + "git diff*": "allow", + "git log*": "allow", + "git show*": "allow", + "git branch*": "allow", + "git remote*": "allow", + "git stash*": "allow", + "gh *": "allow", + "ls *": "allow", + "pwd": "allow", + "which *": "allow", + "echo *": "allow", + "cat *": "ask", "rm -rf *": "deny", + "rm -r *": "deny", "sudo *": "deny", "curl * | sh*": "deny", "wget * | sh*": "deny" }, "read": { "*": "allow", + "*.env.example": "allow", "*.env": "deny", "*.env.*": "deny", - "*.env.example": "allow", "*id_rsa*": "deny", "*id_ed25519*": "deny", "*.pem": "deny", diff --git a/packages/guardrails/profile/agents/implement.md b/packages/guardrails/profile/agents/implement.md index e28ff7d21a6b..6d112fd28f4f 100644 --- a/packages/guardrails/profile/agents/implement.md +++ b/packages/guardrails/profile/agents/implement.md @@ -5,12 +5,18 @@ permission: question: allow plan_enter: allow bash: + "*": ask "git checkout -- *": deny "git merge *": deny "git push --force*": deny "git push * --force*": deny "git reset --hard*": deny "gh pr merge *": deny + "rm -rf *": deny + "rm -r *": deny + "sudo *": deny + "curl * | sh*": deny + "wget * | sh*": deny --- Implement changes in bounded increments. diff --git a/packages/guardrails/profile/opencode.json b/packages/guardrails/profile/opencode.json index 6cf165d34607..a0e9d94199db 100644 --- a/packages/guardrails/profile/opencode.json +++ b/packages/guardrails/profile/opencode.json @@ -80,7 +80,25 @@ "external_directory": "ask", "bash": { "*": "ask", + "node *": "allow", + "npm *": "allow", + "npx *": "allow", + "bun *": "allow", + "git status*": "allow", + "git diff*": "allow", + "git log*": "allow", + "git show*": "allow", + "git branch*": "allow", + "git remote*": "allow", + "git stash*": "allow", + "gh *": "allow", + "ls *": "allow", + "pwd": "allow", + "which *": "allow", + "echo *": "allow", + "cat *": "ask", "rm -rf *": "deny", + "rm -r *": "deny", "sudo *": "deny", "git checkout -- *": "deny", "git merge *": "deny", @@ -93,9 +111,9 @@ }, "read": { "*": "allow", + "*.env.example": "allow", "*.env": "deny", "*.env.*": "deny", - "*.env.example": "allow", "*id_rsa*": "deny", "*id_ed25519*": "deny", "*.pem": "deny", From e055d6352618096519221820b812ea17f3445c31 Mon Sep 17 00:00:00 2001 From: Terada Kousuke Date: Mon, 6 Apr 2026 11:43:38 +0900 Subject: [PATCH 4/4] fix(permission): correct .env.example allow position after deny rules #31 Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/guardrails/managed/opencode.json | 2 +- packages/guardrails/profile/opencode.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/guardrails/managed/opencode.json b/packages/guardrails/managed/opencode.json index d7f169eb2ff6..b352db54d0db 100644 --- a/packages/guardrails/managed/opencode.json +++ b/packages/guardrails/managed/opencode.json @@ -104,9 +104,9 @@ }, "read": { "*": "allow", - "*.env.example": "allow", "*.env": "deny", "*.env.*": "deny", + "*.env.example": "allow", "*id_rsa*": "deny", "*id_ed25519*": "deny", "*.pem": "deny", diff --git a/packages/guardrails/profile/opencode.json b/packages/guardrails/profile/opencode.json index a0e9d94199db..dbe1326ead04 100644 --- a/packages/guardrails/profile/opencode.json +++ b/packages/guardrails/profile/opencode.json @@ -111,9 +111,9 @@ }, "read": { "*": "allow", - "*.env.example": "allow", "*.env": "deny", "*.env.*": "deny", + "*.env.example": "allow", "*id_rsa*": "deny", "*id_ed25519*": "deny", "*.pem": "deny",