Przeglądaj źródła

fix: grep external directory permission evaluation (#26958)

Aiden Cline 3 miesięcy temu
rodzic
commit
1a28924ed8

+ 11 - 10
packages/opencode/src/tool/grep.ts

@@ -54,19 +54,20 @@ export const GrepTool = Tool.define(
           })
 
           const ins = yield* InstanceState.context
-          const search = AppFileSystem.resolve(
-            path.isAbsolute(params.path ?? ins.directory)
-              ? (params.path ?? ins.directory)
-              : path.join(ins.directory, params.path ?? "."),
-          )
-          yield* reference.ensure(search)
+          const requested = path.isAbsolute(params.path ?? ins.directory)
+            ? (params.path ?? ins.directory)
+            : path.join(ins.directory, params.path ?? ".")
+          yield* reference.ensure(requested)
+          const requestedInfo = yield* fs.stat(requested).pipe(Effect.catch(() => Effect.succeed(undefined)))
+          yield* assertExternalDirectoryEffect(ctx, requested, {
+            bypass: yield* reference.contains(requested),
+            kind: requestedInfo?.type === "Directory" ? "directory" : "file",
+          })
+
+          const search = AppFileSystem.resolve(requested)
           const info = yield* fs.stat(search).pipe(Effect.catch(() => Effect.succeed(undefined)))
           const cwd = info?.type === "Directory" ? search : path.dirname(search)
           const file = info?.type === "Directory" ? undefined : [path.relative(cwd, search)]
-          yield* assertExternalDirectoryEffect(ctx, search, {
-            bypass: yield* reference.contains(search),
-            kind: info?.type === "Directory" ? "directory" : "file",
-          })
 
           const result = yield* rg.search({
             cwd,

+ 53 - 0
packages/opencode/test/tool/grep.test.ts

@@ -1,4 +1,6 @@
 import { describe, expect } from "bun:test"
+import fs from "fs/promises"
+import os from "os"
 import path from "path"
 import { Effect, Layer } from "effect"
 import { GrepTool } from "../../src/tool/grep"
@@ -11,6 +13,8 @@ import { Ripgrep } from "../../src/file/ripgrep"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { testEffect } from "../lib/effect"
 import { Reference } from "@/reference/reference"
+import { Permission } from "../../src/permission"
+import type * as Tool from "../../src/tool/tool"
 
 const it = testEffect(
   Layer.mergeAll(
@@ -110,4 +114,53 @@ describe("tool.grep", () => {
       expect(result.output).toContain("Line 2: line2")
     }),
   )
+
+  it.instance("does not ask for external_directory when alias path is allowed", () =>
+    Effect.gen(function* () {
+      if (process.platform === "win32") return
+
+      yield* TestInstance
+      const tmp = yield* Effect.acquireRelease(
+        Effect.promise(() => fs.mkdtemp(path.join(os.tmpdir(), "opencode-grep-alias-"))),
+        (dir) => Effect.promise(() => fs.rm(dir, { recursive: true, force: true })),
+      )
+      const real = path.join(tmp, "real")
+      const alias = path.join(tmp, "alias")
+      yield* Effect.promise(() => fs.mkdir(real))
+      yield* Effect.promise(() => fs.symlink(real, alias, "dir"))
+      yield* Effect.promise(() => Bun.write(path.join(real, "test.txt"), "needle"))
+
+      const ruleset = Permission.fromConfig({
+        grep: "allow",
+        external_directory: {
+          [path.join(alias, "*")]: "allow",
+        },
+      })
+      const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
+      const next: Tool.Context = {
+        ...ctx,
+        ask: (req) =>
+          Effect.sync(() => {
+            const needsAsk = req.patterns.some(
+              (pattern) => Permission.evaluate(req.permission, pattern, ruleset).action !== "allow",
+            )
+            if (needsAsk) requests.push(req)
+          }),
+      }
+
+      const info = yield* GrepTool
+      const grep = yield* info.init()
+      const result = yield* grep.execute(
+        {
+          pattern: "needle",
+          path: alias,
+          include: "*.txt",
+        },
+        next,
+      )
+
+      expect(result.metadata.matches).toBe(1)
+      expect(requests.find((req) => req.permission === "external_directory")).toBeUndefined()
+    }),
+  )
 })