Skip to content

Commit e4ef65c

Browse files
committed
fix: root-level permission config not being respected
In fromConfig, wildcard permission keys like "*" could end up after specific tool keys (like "bash") due to key reordering from mergeDeep during config merging. Since evaluate uses findLast, the catch-all "*" rule would win over the more specific tool rule. Fix by sorting wildcard permission keys before specific keys in fromConfig so specific tool rules always appear later and take precedence. Closes anomalyco#8832
1 parent 25f3eef commit e4ef65c

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

‎packages/opencode/src/permission/next.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,15 @@ export namespace PermissionNext {
4545

4646
export function fromConfig(permission: Config.Permission) {
4747
const ruleset: Ruleset = []
48-
for (const [key, value] of Object.entries(permission)) {
48+
// Process wildcard permission keys first so that specific tool rules
49+
// (like "bash") always appear later and win via findLast.
50+
// This prevents key ordering issues from mergeDeep where "*" can end up
51+
// after specific tools in the object.
52+
// See: https://github.com/anomalyco/opencode/issues/8832
53+
const entries = Object.entries(permission)
54+
const wildcards = entries.filter(([key]) => key.includes("*"))
55+
const rest = entries.filter(([key]) => !key.includes("*"))
56+
for (const [key, value] of [...wildcards, ...rest]) {
4957
if (typeof value === "string") {
5058
ruleset.push({
5159
permission: key,

‎packages/opencode/test/agent/agent.test.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,19 @@ test("defaultAgent throws when default_agent points to non-existent agent", asyn
652652
})
653653
})
654654

655+
test("root-level permission config is respected", () => {
656+
// https://github.com/anomalyco/opencode/issues/8832
657+
// User has { "permission": { "*": "ask", "bash": { "*": "ask", "ls *": "allow" } } }
658+
// After config merging, key order may put "*" after "bash", so { *, *, ask }
659+
// ends up after { bash, ls *, allow } in the ruleset. The specific bash rule
660+
// should still win over the catch-all "*" rule.
661+
const ruleset = PermissionNext.fromConfig({
662+
bash: { "*": "ask", "ls *": "allow" },
663+
"*": "ask",
664+
})
665+
expect(PermissionNext.evaluate("bash", "ls -la", ruleset).action).toBe("allow")
666+
})
667+
655668
test("defaultAgent returns plan when build is disabled and default_agent not set", async () => {
656669
await using tmp = await tmpdir({
657670
config: {

0 commit comments

Comments
 (0)