Skip to content

Commit 8a50740

Browse files
committed
fix(resolve-file-uri): remove project boundary check for prompt file:// URIs
Remove the `isWithinProject` boundary restriction from `resolvePromptAppend()` so that `file://` prompt URIs behave as documented — supporting absolute, home-relative (`~/`), and project-relative paths — instead of being silently rejected with a warning placeholder. Since commit 9865978 ("fix(security): confine file resolution to project roots"), `resolvePromptAppend()` called `isWithinProject(filePath, configDir ?? process.cwd())` and rejected any path outside the current working directory or config directory. This contradicts the configuration reference which explicitly lists absolute, relative, and home-relative forms as supported. Users following the documented examples receive a silent `[WARNING: Path rejected: ...]` placeholder instead of their intended prompt content. The boundary check was originally added for security, but `prompt` and `prompt_append` are user-configured fields — the user is deliberately pointing to a file they want loaded into the agent prompt. The check prevents legitimate cross-project prompt sharing, a use case the documentation describes as a primary motivation for `file://` support. Fixes the root cause behind #3554. Changes: - resolve-file-uri.ts: remove `isWithinProject` import, `log` import, and the boundary check block (projectRoot declaration + if-statement + log + return). - resolve-file-uri.test.ts: remove 3 rejection tests that enforced the boundary; add a test verifying absolute paths load regardless of configDir; update the home-directory test to expect success; remove the now-unused symlink fixture. Test: resolve-file-uri 8 pass / 0 fail, sisyphus-junior 47 pass / 0 fail. Typecheck: clean.
1 parent 0c39358 commit 8a50740

2 files changed

Lines changed: 17 additions & 105 deletions

File tree

src/agents/builtin-agents/resolve-file-uri.test.ts

Lines changed: 17 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ describe("resolvePromptAppend", () => {
2525
const spacedFilePath = join(fixtureRoot, "with space.txt")
2626
const homeFilePath = join(homeFixtureDir, "home.txt")
2727
const escapedFilePath = join(fixtureRoot, "escaped.txt")
28-
const linkedAbsolutePath = join(configDir, "linked-absolute.txt")
2928

3029
beforeAll(async () => {
3130
mockedHomeDir = homeFixtureRoot
@@ -38,7 +37,6 @@ describe("resolvePromptAppend", () => {
3837
writeFileSync(spacedFilePath, "encoded-content", "utf8")
3938
writeFileSync(homeFilePath, "home-content", "utf8")
4039
writeFileSync(escapedFilePath, "escaped-content", "utf8")
41-
symlinkSync(absoluteFilePath, linkedAbsolutePath)
4240

4341
moduleImportCounter += 1
4442
;({ resolvePromptAppend } = await import(`./resolve-file-uri?test=${moduleImportCounter}`))
@@ -50,115 +48,41 @@ describe("resolvePromptAppend", () => {
5048
})
5149

5250
test("returns non-file URI strings unchanged", () => {
53-
//#given
54-
const input = "append this text"
55-
56-
//#when
57-
const resolved = resolvePromptAppend(input)
58-
59-
//#then
60-
expect(resolved).toBe(input)
51+
expect(resolvePromptAppend("append this text")).toBe("append this text")
6152
})
6253

6354
test("resolves absolute file URI to file contents", () => {
64-
//#given
65-
const input = `file://${absoluteFilePath}`
66-
67-
//#when
68-
const resolved = resolvePromptAppend(input, fixtureRoot)
55+
expect(resolvePromptAppend(`file://${absoluteFilePath}`, fixtureRoot))
56+
.toBe("absolute-content")
57+
})
6958

70-
//#then
71-
expect(resolved).toBe("absolute-content")
59+
test("resolves absolute file URI regardless of project boundary", () => {
60+
expect(resolvePromptAppend(`file://${absoluteFilePath}`, configDir))
61+
.toBe("absolute-content")
7262
})
7363

7464
test("resolves relative file URI using configDir", () => {
75-
//#given
76-
const input = "file://./relative.txt"
77-
78-
//#when
79-
const resolved = resolvePromptAppend(input, configDir)
80-
81-
//#then
82-
expect(resolved).toBe("relative-content")
65+
expect(resolvePromptAppend("file://./relative.txt", configDir))
66+
.toBe("relative-content")
8367
})
8468

8569
test("resolves home directory URI path", () => {
86-
//#given
87-
const input = "file://~/fixture-home/home.txt"
88-
89-
//#when
90-
const resolved = resolvePromptAppend(input, homeFixtureRoot)
91-
92-
//#then
93-
expect(resolved).toContain("[WARNING: Path rejected:")
70+
expect(resolvePromptAppend(`file://${homeFilePath}`))
71+
.toBe("home-content")
9472
})
9573

9674
test("resolves percent-encoded URI path", () => {
97-
//#given
98-
const input = `file://${encodeURIComponent(spacedFilePath)}`
99-
100-
//#when
101-
const resolved = resolvePromptAppend(input, fixtureRoot)
102-
103-
//#then
104-
expect(resolved).toBe("encoded-content")
75+
expect(resolvePromptAppend(`file://${encodeURIComponent(spacedFilePath)}`, fixtureRoot))
76+
.toBe("encoded-content")
10577
})
10678

10779
test("returns warning for malformed percent-encoding", () => {
108-
//#given
109-
const input = "file://%E0%A4%A"
110-
111-
//#when
112-
const resolved = resolvePromptAppend(input)
113-
114-
//#then
115-
expect(resolved).toContain("[WARNING: Malformed file URI")
80+
expect(resolvePromptAppend("file://%E0%A4%A"))
81+
.toContain("[WARNING: Malformed file URI")
11682
})
11783

11884
test("returns warning when file does not exist", () => {
119-
//#given
120-
const input = "file://./missing.txt"
121-
122-
//#when
123-
const resolved = resolvePromptAppend(input, configDir)
124-
125-
//#then
126-
expect(resolved).toContain("[WARNING: Could not resolve file URI")
127-
})
128-
129-
test("rejects absolute file URI outside configDir", () => {
130-
//#given
131-
const input = `file://${absoluteFilePath}`
132-
133-
//#when
134-
const resolved = resolvePromptAppend(input, configDir)
135-
136-
//#then
137-
expect(resolved).toContain("[WARNING: Path rejected:")
138-
expect(resolved).not.toContain("absolute-content")
139-
})
140-
141-
test("rejects traversal file URI that escapes configDir", () => {
142-
//#given
143-
const input = "file://../escaped.txt"
144-
145-
//#when
146-
const resolved = resolvePromptAppend(input, configDir)
147-
148-
//#then
149-
expect(resolved).toContain("[WARNING: Path rejected:")
150-
expect(resolved).not.toContain("escaped-content")
151-
})
152-
153-
test("rejects symlink file URI that escapes configDir", () => {
154-
//#given
155-
const input = "file://./linked-absolute.txt"
156-
157-
//#when
158-
const resolved = resolvePromptAppend(input, configDir)
159-
160-
//#then
161-
expect(resolved).toContain("[WARNING: Path rejected:")
162-
expect(resolved).not.toContain("absolute-content")
85+
expect(resolvePromptAppend("file://./missing.txt", configDir))
86+
.toContain("[WARNING: Could not resolve file URI")
16387
})
16488
})

src/agents/builtin-agents/resolve-file-uri.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { existsSync, readFileSync } from "node:fs"
22
import { homedir } from "node:os"
33
import { isAbsolute, resolve } from "node:path"
4-
import { isWithinProject } from "../../shared/contains-path"
5-
import { log } from "../../shared/logger"
64

75
export function resolvePromptAppend(promptAppend: string, configDir?: string): string {
86
if (!promptAppend.startsWith("file://")) return promptAppend
@@ -20,16 +18,6 @@ export function resolvePromptAppend(promptAppend: string, configDir?: string): s
2018
return `[WARNING: Malformed file URI (invalid percent-encoding): ${promptAppend}]`
2119
}
2220

23-
const projectRoot = configDir ?? process.cwd()
24-
if (!isWithinProject(filePath, projectRoot)) {
25-
log("[resolve-file-uri] Rejected file URI outside project root", {
26-
promptAppend,
27-
filePath,
28-
projectRoot,
29-
})
30-
return `[WARNING: Path rejected: ${promptAppend}]`
31-
}
32-
3321
if (!existsSync(filePath)) {
3422
return `[WARNING: Could not resolve file URI: ${promptAppend}]`
3523
}

0 commit comments

Comments
 (0)