Skip to content

Commit 1d15c7c

Browse files
authored
Merge pull request #8298 from continuedev/pe/read-file-errs
feat: allow file reads outside IDE workspace (w/ permission)
2 parents 6b2edc6 + 32d8f51 commit 1d15c7c

File tree

25 files changed

+525
-181
lines changed

25 files changed

+525
-181
lines changed

.github/actions/setup-component/action.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,18 @@ runs:
3131
packages/*/node_modules
3232
key: ${{ runner.os }}-packages-node-modules-${{ hashFiles('packages/*/package-lock.json') }}
3333

34-
- uses: actions/cache@v4
34+
- uses: actions/cache@v4
3535
if: inputs.include-root == 'true'
36+
id: root-cache
3637
with:
3738
path: node_modules
3839
key: ${{ runner.os }}-root-node-modules-${{ hashFiles('package-lock.json') }}
3940

41+
- name: Install root dependencies
42+
if: inputs.include-root == 'true' && steps.root-cache.outputs.cache-hit != 'true'
43+
shell: bash
44+
run: npm ci
45+
4046
- uses: actions/cache@v4
4147
id: component-cache
4248
with:

core/core.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,7 @@ export class Core {
10931093

10941094
on(
10951095
"tools/evaluatePolicy",
1096-
async ({ data: { toolName, basePolicy, args } }) => {
1096+
async ({ data: { toolName, basePolicy, parsedArgs, processedArgs } }) => {
10971097
const { config } = await this.configHandler.loadConfig();
10981098
if (!config) {
10991099
throw new Error("Config not loaded");
@@ -1106,12 +1106,16 @@ export class Core {
11061106

11071107
// Extract display value for specific tools
11081108
let displayValue: string | undefined;
1109-
if (toolName === "runTerminalCommand" && args.command) {
1110-
displayValue = args.command as string;
1109+
if (toolName === "runTerminalCommand" && parsedArgs.command) {
1110+
displayValue = parsedArgs.command as string;
11111111
}
11121112

11131113
if (tool.evaluateToolCallPolicy) {
1114-
const evaluatedPolicy = tool.evaluateToolCallPolicy(basePolicy, args);
1114+
const evaluatedPolicy = tool.evaluateToolCallPolicy(
1115+
basePolicy,
1116+
parsedArgs,
1117+
processedArgs,
1118+
);
11151119
return { policy: evaluatedPolicy, displayValue };
11161120
}
11171121
return { policy: basePolicy, displayValue };

core/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,7 @@ export interface Tool {
11101110
evaluateToolCallPolicy?: (
11111111
basePolicy: ToolPolicy,
11121112
parsedArgs: Record<string, unknown>,
1113+
processedArgs?: Record<string, unknown>,
11131114
) => ToolPolicy;
11141115
}
11151116

core/indexing/docs/crawlers/DocsCrawler.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const TIMEOUT_MS = 1_000_000_000;
1919
// so that we don't delete the Chromium install between tests
2020
ChromiumInstaller.PCR_CONFIG = { downloadPath: os.tmpdir() };
2121

22-
describe("DocsCrawler", () => {
22+
describe.skip("DocsCrawler", () => {
2323
let config: ContinueConfig;
2424
let mockIde: FileSystemIde;
2525
let chromiumInstaller: ChromiumInstaller;

core/package-lock.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
"system-ca": "^1.0.3",
122122
"tar": "^7.4.3",
123123
"tree-sitter-wasms": "^0.1.11",
124+
"untildify": "^6.0.0",
124125
"uuid": "^9.0.1",
125126
"vectordb": "^0.4.20",
126127
"web-tree-sitter": "^0.21.0",

core/protocol/core.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,12 @@ export type ToCoreFromIdeOrWebviewProtocol = {
314314
},
315315
];
316316
"tools/evaluatePolicy": [
317-
{ toolName: string; basePolicy: ToolPolicy; args: Record<string, unknown> },
317+
{
318+
toolName: string;
319+
basePolicy: ToolPolicy;
320+
parsedArgs: Record<string, unknown>;
321+
processedArgs?: Record<string, unknown>;
322+
},
318323
{ policy: ToolPolicy; displayValue?: string },
319324
];
320325
"tools/preprocessArgs": [

core/tools/definitions/createNewFile.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { ToolPolicy } from "@continuedev/terminal-security";
12
import { Tool } from "../..";
3+
import { ResolvedPath, resolveInputPath } from "../../util/pathResolver";
24
import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn";
5+
import { evaluateFileAccessPolicy } from "../policies/fileAccess";
36

47
export const createNewFileTool: Tool = {
58
type: "function",
@@ -21,7 +24,7 @@ export const createNewFileTool: Tool = {
2124
filepath: {
2225
type: "string",
2326
description:
24-
"The path where the new file should be created, relative to the root of the workspace",
27+
"The path where the new file should be created. Can be a relative path (from workspace root), absolute path, tilde path (~/...), or file:// URI.",
2528
},
2629
contents: {
2730
type: "string",
@@ -38,4 +41,26 @@ export const createNewFileTool: Tool = {
3841
["contents", "Contents of the file"],
3942
],
4043
},
44+
preprocessArgs: async (args, { ide }) => {
45+
const filepath = args.filepath as string;
46+
const resolvedPath = await resolveInputPath(ide, filepath);
47+
48+
// Store the resolved path info in args for policy evaluation
49+
return {
50+
resolvedPath,
51+
};
52+
},
53+
evaluateToolCallPolicy: (
54+
basePolicy: ToolPolicy,
55+
_: Record<string, unknown>,
56+
processedArgs?: Record<string, unknown>,
57+
): ToolPolicy => {
58+
const resolvedPath = processedArgs?.resolvedPath as
59+
| ResolvedPath
60+
| null
61+
| undefined;
62+
if (!resolvedPath) return basePolicy;
63+
64+
return evaluateFileAccessPolicy(basePolicy, resolvedPath.isWithinWorkspace);
65+
},
4166
};

core/tools/definitions/ls.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { Tool } from "../..";
22

3+
import { ToolPolicy } from "@continuedev/terminal-security";
4+
import { ResolvedPath, resolveInputPath } from "../../util/pathResolver";
35
import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn";
6+
import { evaluateFileAccessPolicy } from "../policies/fileAccess";
47

58
export const lsTool: Tool = {
69
type: "function",
@@ -20,7 +23,7 @@ export const lsTool: Tool = {
2023
dirPath: {
2124
type: "string",
2225
description:
23-
"The directory path relative to the root of the project. Use forward slash paths like '/'. rather than e.g. '.'",
26+
"The directory path. Can be relative to project root, absolute path, tilde path (~/...), or file:// URI. Use forward slash paths",
2427
},
2528
recursive: {
2629
type: "boolean",
@@ -39,4 +42,28 @@ export const lsTool: Tool = {
3942
],
4043
},
4144
toolCallIcon: "FolderIcon",
45+
preprocessArgs: async (args, { ide }) => {
46+
const dirPath = args.dirPath as string;
47+
48+
// Default to current directory if no path provided
49+
const pathToResolve = dirPath || ".";
50+
const resolvedPath = await resolveInputPath(ide, pathToResolve);
51+
52+
return {
53+
resolvedPath,
54+
};
55+
},
56+
evaluateToolCallPolicy: (
57+
basePolicy: ToolPolicy,
58+
_: Record<string, unknown>,
59+
processedArgs?: Record<string, unknown>,
60+
): ToolPolicy => {
61+
const resolvedPath = processedArgs?.resolvedPath as
62+
| ResolvedPath
63+
| null
64+
| undefined;
65+
if (!resolvedPath) return basePolicy;
66+
67+
return evaluateFileAccessPolicy(basePolicy, resolvedPath.isWithinWorkspace);
68+
},
4269
};

core/tools/definitions/readFile.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { ToolPolicy } from "@continuedev/terminal-security";
12
import { Tool } from "../..";
3+
import { ResolvedPath, resolveInputPath } from "../../util/pathResolver";
24
import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn";
5+
import { evaluateFileAccessPolicy } from "../policies/fileAccess";
36

47
export const readFileTool: Tool = {
58
type: "function",
@@ -21,7 +24,7 @@ export const readFileTool: Tool = {
2124
filepath: {
2225
type: "string",
2326
description:
24-
"The path of the file to read, relative to the root of the workspace (NOT uri or absolute path)",
27+
"The path of the file to read. Can be a relative path (from workspace root), absolute path, tilde path (~/...), or file:// URI",
2528
},
2629
},
2730
},
@@ -32,4 +35,26 @@ export const readFileTool: Tool = {
3235
},
3336
defaultToolPolicy: "allowedWithoutPermission",
3437
toolCallIcon: "DocumentIcon",
38+
preprocessArgs: async (args, { ide }) => {
39+
const filepath = args.filepath as string;
40+
const resolvedPath = await resolveInputPath(ide, filepath);
41+
42+
// Store the resolved path info in args for policy evaluation
43+
return {
44+
resolvedPath,
45+
};
46+
},
47+
evaluateToolCallPolicy: (
48+
basePolicy: ToolPolicy,
49+
_: Record<string, unknown>,
50+
processedArgs?: Record<string, unknown>,
51+
): ToolPolicy => {
52+
const resolvedPath = processedArgs?.resolvedPath as
53+
| ResolvedPath
54+
| null
55+
| undefined;
56+
if (!resolvedPath) return basePolicy;
57+
58+
return evaluateFileAccessPolicy(basePolicy, resolvedPath.isWithinWorkspace);
59+
},
3560
};

0 commit comments

Comments
 (0)