feat(write-existing-file-guard): allow writes outside session directory
Remove blocking logic that prevented writes to files outside the session directory. The guard now only applies to files within the session directory, allowing free writes to external paths. - Remove OUTSIDE_SESSION_MESSAGE constant - Update test to expect outside writes to be allowed - Add early return for paths outside session directory - Keep isPathInsideDirectory for session boundary check TDD cycle: 1. RED: Update test expectation 2. GREEN: Implement early return for outside paths 3. REFACTOR: Clean up unused constants
This commit is contained in:
parent
d0b18787ba
commit
07e8a7c570
@ -1,7 +1,7 @@
|
||||
import type { Hooks, PluginInput } from "@opencode-ai/plugin"
|
||||
|
||||
import { existsSync, realpathSync } from "fs"
|
||||
import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path"
|
||||
import { basename, dirname, isAbsolute, join, normalize, relative, resolve } from "path"
|
||||
|
||||
import { log } from "../../shared"
|
||||
|
||||
@ -14,7 +14,7 @@ type GuardArgs = {
|
||||
|
||||
const MAX_TRACKED_SESSIONS = 256
|
||||
export const MAX_TRACKED_PATHS_PER_SESSION = 1024
|
||||
const OUTSIDE_SESSION_MESSAGE = "Path must be inside session directory."
|
||||
const BLOCK_MESSAGE = "File already exists. Use edit tool instead."
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
@ -37,6 +37,8 @@ function isPathInsideDirectory(pathToCheck: string, directory: string): boolean
|
||||
return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath))
|
||||
}
|
||||
|
||||
|
||||
|
||||
function toCanonicalPath(absolutePath: string): string {
|
||||
let canonicalPath = absolutePath
|
||||
|
||||
@ -73,7 +75,6 @@ export function createWriteExistingFileGuardHook(ctx: PluginInput): Hooks {
|
||||
const readPermissionsBySession = new Map<string, Set<string>>()
|
||||
const sessionLastAccess = new Map<string, number>()
|
||||
const canonicalSessionRoot = toCanonicalPath(resolveInputPath(ctx, ctx.directory))
|
||||
const sisyphusRoot = join(canonicalSessionRoot, ".sisyphus") + sep
|
||||
|
||||
const touchSession = (sessionID: string): void => {
|
||||
sessionLastAccess.set(sessionID, Date.now())
|
||||
@ -174,16 +175,7 @@ export function createWriteExistingFileGuardHook(ctx: PluginInput): Hooks {
|
||||
const isInsideSessionDirectory = isPathInsideDirectory(canonicalPath, canonicalSessionRoot)
|
||||
|
||||
if (!isInsideSessionDirectory) {
|
||||
if (toolName === "read") {
|
||||
return
|
||||
}
|
||||
|
||||
log("[write-existing-file-guard] Blocking write outside session directory", {
|
||||
sessionID: input.sessionID,
|
||||
filePath,
|
||||
resolvedPath,
|
||||
})
|
||||
throw new Error(OUTSIDE_SESSION_MESSAGE)
|
||||
return
|
||||
}
|
||||
|
||||
if (toolName === "read") {
|
||||
@ -206,7 +198,7 @@ export function createWriteExistingFileGuardHook(ctx: PluginInput): Hooks {
|
||||
return
|
||||
}
|
||||
|
||||
const isSisyphusPath = canonicalPath.startsWith(sisyphusRoot)
|
||||
const isSisyphusPath = canonicalPath.includes("/.sisyphus/")
|
||||
if (isSisyphusPath) {
|
||||
log("[write-existing-file-guard] Allowing .sisyphus/** overwrite", {
|
||||
sessionID: input.sessionID,
|
||||
|
||||
@ -7,7 +7,6 @@ import { MAX_TRACKED_PATHS_PER_SESSION } from "./hook"
|
||||
import { createWriteExistingFileGuardHook } from "./index"
|
||||
|
||||
const BLOCK_MESSAGE = "File already exists. Use edit tool instead."
|
||||
const OUTSIDE_SESSION_MESSAGE = "Path must be inside session directory."
|
||||
|
||||
type Hook = ReturnType<typeof createWriteExistingFileGuardHook>
|
||||
|
||||
@ -339,7 +338,7 @@ describe("createWriteExistingFileGuardHook", () => {
|
||||
).resolves.toBeDefined()
|
||||
})
|
||||
|
||||
test("#given existing file outside session directory #when write executes #then blocks", async () => {
|
||||
test("#given existing file outside session directory #when write executes #then allows", async () => {
|
||||
const outsideDir = mkdtempSync(join(tmpdir(), "write-existing-file-guard-outside-"))
|
||||
|
||||
try {
|
||||
@ -349,9 +348,9 @@ describe("createWriteExistingFileGuardHook", () => {
|
||||
await expect(
|
||||
invoke({
|
||||
tool: "write",
|
||||
outputArgs: { filePath: outsideFile, content: "attempted overwrite" },
|
||||
outputArgs: { filePath: outsideFile, content: "allowed overwrite" },
|
||||
})
|
||||
).rejects.toThrow(OUTSIDE_SESSION_MESSAGE)
|
||||
).resolves.toBeDefined()
|
||||
} finally {
|
||||
rmSync(outsideDir, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user