Skip to content

Commit 1a28924

Browse files
authored
fix: grep external directory permission evaluation (#26958)
1 parent 8713748 commit 1a28924

2 files changed

Lines changed: 64 additions & 10 deletions

File tree

packages/opencode/src/tool/grep.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,20 @@ export const GrepTool = Tool.define(
5454
})
5555

5656
const ins = yield* InstanceState.context
57-
const search = AppFileSystem.resolve(
58-
path.isAbsolute(params.path ?? ins.directory)
59-
? (params.path ?? ins.directory)
60-
: path.join(ins.directory, params.path ?? "."),
61-
)
62-
yield* reference.ensure(search)
57+
const requested = path.isAbsolute(params.path ?? ins.directory)
58+
? (params.path ?? ins.directory)
59+
: path.join(ins.directory, params.path ?? ".")
60+
yield* reference.ensure(requested)
61+
const requestedInfo = yield* fs.stat(requested).pipe(Effect.catch(() => Effect.succeed(undefined)))
62+
yield* assertExternalDirectoryEffect(ctx, requested, {
63+
bypass: yield* reference.contains(requested),
64+
kind: requestedInfo?.type === "Directory" ? "directory" : "file",
65+
})
66+
67+
const search = AppFileSystem.resolve(requested)
6368
const info = yield* fs.stat(search).pipe(Effect.catch(() => Effect.succeed(undefined)))
6469
const cwd = info?.type === "Directory" ? search : path.dirname(search)
6570
const file = info?.type === "Directory" ? undefined : [path.relative(cwd, search)]
66-
yield* assertExternalDirectoryEffect(ctx, search, {
67-
bypass: yield* reference.contains(search),
68-
kind: info?.type === "Directory" ? "directory" : "file",
69-
})
7071

7172
const result = yield* rg.search({
7273
cwd,

packages/opencode/test/tool/grep.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { describe, expect } from "bun:test"
2+
import fs from "fs/promises"
3+
import os from "os"
24
import path from "path"
35
import { Effect, Layer } from "effect"
46
import { GrepTool } from "../../src/tool/grep"
@@ -11,6 +13,8 @@ import { Ripgrep } from "../../src/file/ripgrep"
1113
import { AppFileSystem } from "@opencode-ai/core/filesystem"
1214
import { testEffect } from "../lib/effect"
1315
import { Reference } from "@/reference/reference"
16+
import { Permission } from "../../src/permission"
17+
import type * as Tool from "../../src/tool/tool"
1418

1519
const it = testEffect(
1620
Layer.mergeAll(
@@ -110,4 +114,53 @@ describe("tool.grep", () => {
110114
expect(result.output).toContain("Line 2: line2")
111115
}),
112116
)
117+
118+
it.instance("does not ask for external_directory when alias path is allowed", () =>
119+
Effect.gen(function* () {
120+
if (process.platform === "win32") return
121+
122+
yield* TestInstance
123+
const tmp = yield* Effect.acquireRelease(
124+
Effect.promise(() => fs.mkdtemp(path.join(os.tmpdir(), "opencode-grep-alias-"))),
125+
(dir) => Effect.promise(() => fs.rm(dir, { recursive: true, force: true })),
126+
)
127+
const real = path.join(tmp, "real")
128+
const alias = path.join(tmp, "alias")
129+
yield* Effect.promise(() => fs.mkdir(real))
130+
yield* Effect.promise(() => fs.symlink(real, alias, "dir"))
131+
yield* Effect.promise(() => Bun.write(path.join(real, "test.txt"), "needle"))
132+
133+
const ruleset = Permission.fromConfig({
134+
grep: "allow",
135+
external_directory: {
136+
[path.join(alias, "*")]: "allow",
137+
},
138+
})
139+
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
140+
const next: Tool.Context = {
141+
...ctx,
142+
ask: (req) =>
143+
Effect.sync(() => {
144+
const needsAsk = req.patterns.some(
145+
(pattern) => Permission.evaluate(req.permission, pattern, ruleset).action !== "allow",
146+
)
147+
if (needsAsk) requests.push(req)
148+
}),
149+
}
150+
151+
const info = yield* GrepTool
152+
const grep = yield* info.init()
153+
const result = yield* grep.execute(
154+
{
155+
pattern: "needle",
156+
path: alias,
157+
include: "*.txt",
158+
},
159+
next,
160+
)
161+
162+
expect(result.metadata.matches).toBe(1)
163+
expect(requests.find((req) => req.permission === "external_directory")).toBeUndefined()
164+
}),
165+
)
113166
})

0 commit comments

Comments
 (0)