fix: handle redirected_statement treesitter node in bash permissions (#6737)

This commit is contained in:
Patrick Schiel
2026-01-30 23:11:45 +01:00
committed by GitHub
parent 2c36cbb87c
commit e7ff7143b6
2 changed files with 49 additions and 2 deletions

View File

@@ -91,6 +91,10 @@ export const BashTool = Tool.define("bash", async () => {
for (const node of tree.rootNode.descendantsOfType("command")) {
if (!node) continue
// Get full command text including redirects if present
let commandText = node.parent?.type === "redirected_statement" ? node.parent.text : node.text
const command = []
for (let i = 0; i < node.childCount; i++) {
const child = node.child(i)
@@ -131,8 +135,8 @@ export const BashTool = Tool.define("bash", async () => {
// cd covered by above check
if (command.length && command[0] !== "cd") {
patterns.add(command.join(" "))
always.add(BashArity.prefix(command).join(" ") + "*")
patterns.add(commandText)
always.add(BashArity.prefix(command).join(" ") + " *")
}
}

View File

@@ -231,6 +231,49 @@ describe("tool.bash permissions", () => {
},
})
})
test("matches redirects in permission pattern", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const bash = await BashTool.init()
const requests: Array<Omit<PermissionNext.Request, "id" | "sessionID" | "tool">> = []
const testCtx = {
...ctx,
ask: async (req: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">) => {
requests.push(req)
},
}
await bash.execute({ command: "cat > /tmp/output.txt", description: "Redirect ls output" }, testCtx)
const bashReq = requests.find((r) => r.permission === "bash")
expect(bashReq).toBeDefined()
expect(bashReq!.patterns).toContain("cat > /tmp/output.txt")
},
})
})
test("always pattern has space before wildcard to not include different commands", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const bash = await BashTool.init()
const requests: Array<Omit<PermissionNext.Request, "id" | "sessionID" | "tool">> = []
const testCtx = {
...ctx,
ask: async (req: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">) => {
requests.push(req)
},
}
await bash.execute({ command: "ls -la", description: "List" }, testCtx)
const bashReq = requests.find((r) => r.permission === "bash")
expect(bashReq).toBeDefined()
const pattern = bashReq!.always[0]
expect(pattern).toBe("ls *")
},
})
})
})
describe("tool.bash truncation", () => {