fix: grep external directory permission evaluation (#26958)
This commit is contained in:
@@ -54,19 +54,20 @@ export const GrepTool = Tool.define(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const ins = yield* InstanceState.context
|
const ins = yield* InstanceState.context
|
||||||
const search = AppFileSystem.resolve(
|
const requested = path.isAbsolute(params.path ?? ins.directory)
|
||||||
path.isAbsolute(params.path ?? ins.directory)
|
? (params.path ?? ins.directory)
|
||||||
? (params.path ?? ins.directory)
|
: path.join(ins.directory, params.path ?? ".")
|
||||||
: path.join(ins.directory, params.path ?? "."),
|
yield* reference.ensure(requested)
|
||||||
)
|
const requestedInfo = yield* fs.stat(requested).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||||
yield* reference.ensure(search)
|
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 info = yield* fs.stat(search).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||||
const cwd = info?.type === "Directory" ? search : path.dirname(search)
|
const cwd = info?.type === "Directory" ? search : path.dirname(search)
|
||||||
const file = info?.type === "Directory" ? undefined : [path.relative(cwd, 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({
|
const result = yield* rg.search({
|
||||||
cwd,
|
cwd,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
|
import fs from "fs/promises"
|
||||||
|
import os from "os"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Effect, Layer } from "effect"
|
import { Effect, Layer } from "effect"
|
||||||
import { GrepTool } from "../../src/tool/grep"
|
import { GrepTool } from "../../src/tool/grep"
|
||||||
@@ -11,6 +13,8 @@ import { Ripgrep } from "../../src/file/ripgrep"
|
|||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
import { Reference } from "@/reference/reference"
|
import { Reference } from "@/reference/reference"
|
||||||
|
import { Permission } from "../../src/permission"
|
||||||
|
import type * as Tool from "../../src/tool/tool"
|
||||||
|
|
||||||
const it = testEffect(
|
const it = testEffect(
|
||||||
Layer.mergeAll(
|
Layer.mergeAll(
|
||||||
@@ -110,4 +114,53 @@ describe("tool.grep", () => {
|
|||||||
expect(result.output).toContain("Line 2: line2")
|
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()
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user