fix(core): authorize external read paths
This commit is contained in:
parent
0befd9b049
commit
1ac6b4bec4
2 changed files with 80 additions and 22 deletions
|
|
@ -1,12 +1,10 @@
|
|||
export * as ReadTool from "./read"
|
||||
|
||||
import { ToolFailure } from "@opencode-ai/llm"
|
||||
import path from "path"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { FileSystem } from "../filesystem"
|
||||
import { FSUtil } from "../fs-util"
|
||||
import { Image } from "../image"
|
||||
import { Location } from "../location"
|
||||
import { LocationMutation } from "../location-mutation"
|
||||
import { PermissionV2 } from "../permission"
|
||||
import { AbsolutePath } from "../schema"
|
||||
import { ReadToolFileSystem } from "./read-filesystem"
|
||||
|
|
@ -30,9 +28,8 @@ const Output = Schema.Union([FileSystem.Content, ReadToolFileSystem.TextPage, Re
|
|||
export const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const tools = yield* Tools.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
const reader = yield* ReadToolFileSystem.Service
|
||||
const location = yield* Location.Service
|
||||
const mutation = yield* LocationMutation.Service
|
||||
const image = yield* Image.Service
|
||||
const permission = yield* PermissionV2.Service
|
||||
|
||||
|
|
@ -40,7 +37,7 @@ export const layer = Layer.effectDiscard(
|
|||
.register({
|
||||
[name]: Tool.make({
|
||||
description:
|
||||
"Read a text file or supported image, page through a large UTF-8 text file by line offset, or list a directory page. Relative paths resolve from the current location; absolute paths are read directly.",
|
||||
"Read a text file or supported image, page through a large UTF-8 text file by line offset, or list a directory page. Relative paths resolve from the current location; absolute paths inside it are accepted, while external absolute paths require external_directory approval.",
|
||||
input: Input,
|
||||
output: Output,
|
||||
toModelOutput: ({ input, output }) => {
|
||||
|
|
@ -53,27 +50,33 @@ export const layer = Layer.effectDiscard(
|
|||
},
|
||||
execute: (input, context) => {
|
||||
return Effect.gen(function* () {
|
||||
const absolute = path.resolve(location.directory, input.path)
|
||||
const selected = path.isAbsolute(input.path) ? path.dirname(absolute) : location.directory
|
||||
if (!path.isAbsolute(input.path) && !FSUtil.contains(location.directory, absolute))
|
||||
return yield* Effect.die(new Error("Path escapes the allowed read root"))
|
||||
const real = yield* fs.realPath(absolute)
|
||||
const root = yield* fs.realPath(selected)
|
||||
if (!FSUtil.contains(root, real))
|
||||
return yield* Effect.die(new Error("Path escapes the allowed read root"))
|
||||
const resource = path.relative(root, real).replaceAll("\\", "/") || "."
|
||||
const target = AbsolutePath.make(real)
|
||||
const type = yield* reader.inspect(target)
|
||||
const source = {
|
||||
type: "tool" as const,
|
||||
messageID: context.assistantMessageID,
|
||||
callID: context.toolCallID,
|
||||
}
|
||||
const target = yield* mutation.resolve({ path: input.path, kind: "directory" })
|
||||
const external = target.externalDirectory
|
||||
if (external)
|
||||
yield* permission.assert({
|
||||
...LocationMutation.externalDirectoryPermission(external),
|
||||
sessionID: context.sessionID,
|
||||
agent: context.agent,
|
||||
source,
|
||||
})
|
||||
const resource = target.resource
|
||||
const absolute = AbsolutePath.make(target.canonical)
|
||||
const type = yield* reader.inspect(absolute)
|
||||
yield* permission.assert({
|
||||
action: name,
|
||||
resources: [resource],
|
||||
save: ["*"],
|
||||
sessionID: context.sessionID,
|
||||
agent: context.agent,
|
||||
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
||||
source,
|
||||
})
|
||||
if (type === "directory") return yield* reader.list(target, { offset: input.offset, limit: input.limit })
|
||||
const content = yield* reader.read(target, resource, {
|
||||
if (type === "directory") return yield* reader.list(absolute, { offset: input.offset, limit: input.limit })
|
||||
const content = yield* reader.read(absolute, resource, {
|
||||
offset: input.offset,
|
||||
limit: input.limit,
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue