From c81624aef743f5b62adef62ca99934b6fe7fb6c3 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sun, 14 Sep 2025 09:01:57 -0500 Subject: [PATCH] tweak: make bash permissions key off of command pattern (#2592) --- packages/opencode/src/permission/index.ts | 28 ++++++++++++++---- packages/opencode/src/tool/bash.ts | 35 +++++++++++++++++++---- packages/opencode/src/tool/edit.ts | 1 - packages/opencode/src/tool/webfetch.ts | 1 - 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index e8b4e681..dd198dac 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -4,15 +4,25 @@ import { Log } from "../util/log" import { Identifier } from "../id/id" import { Plugin } from "../plugin" import { Instance } from "../project/instance" +import { Wildcard } from "../util/wildcard" export namespace Permission { const log = Log.create({ service: "permission" }) + function toKeys(pattern: Info["pattern"], type: string): string[] { + return pattern === undefined ? [type] : Array.isArray(pattern) ? pattern : [pattern] + } + + function covered(keys: string[], approved: Record): boolean { + const pats = Object.keys(approved) + return keys.every((k) => pats.some((p) => Wildcard.match(k, p))) + } + export const Info = z .object({ id: z.string(), type: z.string(), - pattern: z.string().optional(), + pattern: z.union([z.string(), z.array(z.string())]).optional(), sessionID: z.string(), messageID: z.string(), callID: z.string().optional(), @@ -83,7 +93,9 @@ export namespace Permission { toolCallID: input.callID, pattern: input.pattern, }) - if (approved[input.sessionID]?.[input.type]) return + const approvedForSession = approved[input.sessionID] || {} + const keys = toKeys(input.pattern, input.type) + if (covered(keys, approvedForSession)) return const info: Info = { id: Identifier.ascending("permission"), type: input.type, @@ -141,9 +153,15 @@ export namespace Permission { }) if (input.response === "always") { approved[input.sessionID] = approved[input.sessionID] || {} - approved[input.sessionID][match.info.type] = true - for (const item of Object.values(pending[input.sessionID])) { - if (item.info.type === match.info.type) { + const approveKeys = toKeys(match.info.pattern, match.info.type) + for (const k of approveKeys) { + approved[input.sessionID][k] = true + } + const items = pending[input.sessionID] + if (!items) return + for (const item of Object.values(items)) { + const itemKeys = toKeys(item.info.pattern, item.info.type) + if (covered(itemKeys, approved[input.sessionID])) { respond({ sessionID: item.info.sessionID, permissionID: item.info.id, response: input.response }) } } diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index a7b6ec24..8149360f 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -59,7 +59,7 @@ export const BashTool = Tool.define("bash", { const tree = await parser().then((p) => p.parse(params.command)) const permissions = await Agent.get(ctx.agent).then((x) => x.permission.bash) - let needsAsk = false + const askPatterns = new Set() for (const node of tree.rootNode.descendantsOfType("command")) { const command = [] for (let i = 0; i < node.childCount; i++) { @@ -96,27 +96,52 @@ export const BashTool = Tool.define("bash", { } // always allow cd if it passes above check - if (!needsAsk && command[0] !== "cd") { + if (command[0] !== "cd") { const action = Wildcard.all(node.text, permissions) if (action === "deny") { throw new Error( `The user has specifically restricted access to this command, you are not allowed to execute it. Here is the configuration: ${JSON.stringify(permissions)}`, ) } - if (action === "ask") needsAsk = true + if (action === "ask") { + const pattern = (() => { + let head = "" + let sub: string | undefined + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i) + if (!child) continue + if (child.type === "command_name") { + if (!head) { + head = child.text + } + continue + } + if (!sub && child.type === "word") { + if (!child.text.startsWith("-")) sub = child.text + } + } + if (!head) return + return sub ? `${head} ${sub} *` : `${head} *` + })() + if (pattern) { + askPatterns.add(pattern) + } + } } } - if (needsAsk) { + if (askPatterns.size > 0) { + const patterns = Array.from(askPatterns) await Permission.ask({ type: "bash", - pattern: params.command, + pattern: patterns, sessionID: ctx.sessionID, messageID: ctx.messageID, callID: ctx.callID, title: params.command, metadata: { command: params.command, + patterns, }, }) } diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 88e45302..7218575f 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -82,7 +82,6 @@ export const EditTool = Tool.define("edit", { sessionID: ctx.sessionID, messageID: ctx.messageID, callID: ctx.callID, - pattern: filePath, title: "Edit this file: " + filePath, metadata: { filePath, diff --git a/packages/opencode/src/tool/webfetch.ts b/packages/opencode/src/tool/webfetch.ts index e4519c0c..621421fe 100644 --- a/packages/opencode/src/tool/webfetch.ts +++ b/packages/opencode/src/tool/webfetch.ts @@ -28,7 +28,6 @@ export const WebFetchTool = Tool.define("webfetch", { if (cfg.permission?.webfetch === "ask") await Permission.ask({ type: "webfetch", - pattern: params.url, sessionID: ctx.sessionID, messageID: ctx.messageID, callID: ctx.callID,