This commit is contained in:
Tommy D. Rossi 2025-12-23 15:42:24 +08:00 committed by GitHub
commit 7f7b89a494
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 28 additions and 8 deletions

View file

@ -84,7 +84,7 @@ export const BashTool = Tool.define("bash", async () => {
const agent = await Agent.get(ctx.agent)
const checkExternalDirectory = async (dir: string) => {
if (Filesystem.contains(Instance.directory, dir)) return
if (Filesystem.isAllowedPath(Instance.directory, dir)) return
const title = `This command references paths outside of ${Instance.directory}`
if (agent.permission.external_directory === "ask") {
await Permission.ask({

View file

@ -44,7 +44,7 @@ export const EditTool = Tool.define("edit", {
const agent = await Agent.get(ctx.agent)
const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
if (!Filesystem.contains(Instance.directory, filePath)) {
if (!Filesystem.isAllowedPath(Instance.directory, filePath)) {
const parentDir = path.dirname(filePath)
if (agent.permission.external_directory === "ask") {
await Permission.ask({

View file

@ -53,7 +53,7 @@ export const PatchTool = Tool.define("patch", {
for (const hunk of hunks) {
const filePath = path.resolve(Instance.directory, hunk.path)
if (!Filesystem.contains(Instance.directory, filePath)) {
if (!Filesystem.isAllowedPath(Instance.directory, filePath)) {
const parentDir = path.dirname(filePath)
if (agent.permission.external_directory === "ask") {
await Permission.ask({

View file

@ -30,7 +30,7 @@ export const ReadTool = Tool.define("read", {
const title = path.relative(Instance.worktree, filepath)
const agent = await Agent.get(ctx.agent)
if (!ctx.extra?.["bypassCwdCheck"] && !Filesystem.contains(Instance.directory, filepath)) {
if (!ctx.extra?.["bypassCwdCheck"] && !Filesystem.isAllowedPath(Instance.directory, filepath)) {
const parentDir = path.dirname(filepath)
if (agent.permission.external_directory === "ask") {
await Permission.ask({

View file

@ -24,7 +24,7 @@ export const WriteTool = Tool.define("write", {
const agent = await Agent.get(ctx.agent)
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
if (!Filesystem.contains(Instance.directory, filepath)) {
if (!Filesystem.isAllowedPath(Instance.directory, filepath)) {
const parentDir = path.dirname(filepath)
if (agent.permission.external_directory === "ask") {
await Permission.ask({

View file

@ -1,8 +1,28 @@
import { realpathSync } from "fs"
import { exists } from "fs/promises"
import { dirname, join, relative } from "path"
import { dirname, join, normalize, relative } from "path"
import { tmpdir } from "os"
export namespace Filesystem {
const systemTmpDir = normalize(tmpdir())
// on macOS /tmp is a symlink to /private/tmp, resolve it
const tmpDirResolved = (() => {
try {
return realpathSync("/tmp")
} catch {
return null
}
})()
export function isAllowedPath(projectDir: string, filepath: string) {
const normalized = normalize(filepath)
if (contains(projectDir, normalized)) return true
if (contains(systemTmpDir, normalized)) return true
if (contains("/tmp", normalized)) return true
if (tmpDirResolved && contains(tmpDirResolved, normalized)) return true
return false
}
/**
* On Windows, normalize a path to its canonical casing using the filesystem.
* This is needed because Windows paths are case-insensitive but LSP servers

View file

@ -376,8 +376,8 @@ describe("tool.bash permissions", () => {
bash.execute(
{
command: "ls",
workdir: "/tmp",
description: "List /tmp",
workdir: "/usr",
description: "List /usr",
},
ctx,
),