Skip to content

Commit c6110f2

Browse files
committed
add command black list
1 parent 3f72bca commit c6110f2

2 files changed

Lines changed: 70 additions & 1 deletion

File tree

packages/opencode/src/tool/bash.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,26 @@ import { Plugin } from "@/plugin"
2020

2121
const MAX_METADATA_LENGTH = 30_000
2222
const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000
23+
const BASE_BLOCKED_BASH_COMMANDS = ["ipython", "jupyter", "nohup"] as const
24+
const COMMAND_SEPARATORS = /&&|\|\||;|\|/
2325

2426
export const log = Log.create({ service: "bash-tool" })
2527

28+
function blockedBashCommands() {
29+
return process.env.ALLOW_GIT === "1" ? [...BASE_BLOCKED_BASH_COMMANDS] : ["git", ...BASE_BLOCKED_BASH_COMMANDS]
30+
}
31+
32+
function findBlockedCommandInChain(command: string, blocked: Set<string>) {
33+
const segments = command.split(COMMAND_SEPARATORS)
34+
for (const segment of segments) {
35+
const trimmed = segment.trim()
36+
if (!trimmed) continue
37+
const firstToken = trimmed.split(/\s+/)[0]
38+
if (blocked.has(firstToken)) return firstToken
39+
}
40+
return undefined
41+
}
42+
2643
const resolveWasm = (asset: string) => {
2744
if (asset.startsWith("file://")) return fileURLToPath(asset)
2845
if (asset.startsWith("/") || /^[a-z]:/i.test(asset)) return asset
@@ -76,6 +93,11 @@ export const BashTool = Tool.define("bash", async () => {
7693
throw new Error(`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`)
7794
}
7895
const timeout = params.timeout ?? DEFAULT_TIMEOUT
96+
const blocked = new Set(blockedBashCommands())
97+
const blockedCommand = findBlockedCommandInChain(params.command, blocked)
98+
if (blockedCommand) {
99+
throw new Error(`Bash command '${blockedCommand}' is not allowed. Please use a different command or tool.`)
100+
}
79101
const tree = await parser().then((p) => p.parse(params.command))
80102
if (!tree) {
81103
throw new Error("Failed to parse command")

packages/opencode/test/tool/bash.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ describe("tool.bash permissions", () => {
222222
}
223223
await bash.execute(
224224
{
225-
command: "git log --oneline -5",
225+
command: "ls -la",
226226
},
227227
testCtx,
228228
)
@@ -302,6 +302,53 @@ describe("tool.bash permissions", () => {
302302
})
303303
})
304304

305+
describe("tool.bash blocklist", () => {
306+
test("blocks git by default", async () => {
307+
await Instance.provide({
308+
directory: projectRoot,
309+
fn: async () => {
310+
const bash = await BashTool.init()
311+
await expect(bash.execute({ command: "git status" }, ctx)).rejects.toThrow(
312+
"Bash command 'git' is not allowed. Please use a different command or tool.",
313+
)
314+
},
315+
})
316+
})
317+
318+
test("allows git when ALLOW_GIT=1", async () => {
319+
const prev = process.env.ALLOW_GIT
320+
process.env.ALLOW_GIT = "1"
321+
try {
322+
await Instance.provide({
323+
directory: projectRoot,
324+
fn: async () => {
325+
const bash = await BashTool.init()
326+
const result = await bash.execute({ command: "git --version >/dev/null 2>&1 || true" }, ctx)
327+
expect(result.metadata.exit).toBe(0)
328+
},
329+
})
330+
} finally {
331+
if (prev === undefined) {
332+
delete process.env.ALLOW_GIT
333+
} else {
334+
process.env.ALLOW_GIT = prev
335+
}
336+
}
337+
})
338+
339+
test("blocks commands in chained segments", async () => {
340+
await Instance.provide({
341+
directory: projectRoot,
342+
fn: async () => {
343+
const bash = await BashTool.init()
344+
await expect(bash.execute({ command: "echo ok && nohup sleep 1" }, ctx)).rejects.toThrow(
345+
"Bash command 'nohup' is not allowed. Please use a different command or tool.",
346+
)
347+
},
348+
})
349+
})
350+
})
351+
305352
describe("tool.bash truncation", () => {
306353
test("truncates output exceeding line limit", async () => {
307354
await Instance.provide({

0 commit comments

Comments
 (0)