diff --git a/.github/actions/setup-component/action.yml b/.github/actions/setup-component/action.yml index d05de3bfb87..d7e529e3fb7 100644 --- a/.github/actions/setup-component/action.yml +++ b/.github/actions/setup-component/action.yml @@ -31,12 +31,18 @@ runs: packages/*/node_modules key: ${{ runner.os }}-packages-node-modules-${{ hashFiles('packages/*/package-lock.json') }} - - uses: actions/cache@v4 + - uses: actions/cache@v4 if: inputs.include-root == 'true' + id: root-cache with: path: node_modules key: ${{ runner.os }}-root-node-modules-${{ hashFiles('package-lock.json') }} + - name: Install root dependencies + if: inputs.include-root == 'true' && steps.root-cache.outputs.cache-hit != 'true' + shell: bash + run: npm ci + - uses: actions/cache@v4 id: component-cache with: diff --git a/core/core.ts b/core/core.ts index 87512ccf50c..63ac4a10d0d 100644 --- a/core/core.ts +++ b/core/core.ts @@ -1091,7 +1091,7 @@ export class Core { on( "tools/evaluatePolicy", - async ({ data: { toolName, basePolicy, args } }) => { + async ({ data: { toolName, basePolicy, parsedArgs, processedArgs } }) => { const { config } = await this.configHandler.loadConfig(); if (!config) { throw new Error("Config not loaded"); @@ -1104,12 +1104,16 @@ export class Core { // Extract display value for specific tools let displayValue: string | undefined; - if (toolName === "runTerminalCommand" && args.command) { - displayValue = args.command as string; + if (toolName === "runTerminalCommand" && parsedArgs.command) { + displayValue = parsedArgs.command as string; } if (tool.evaluateToolCallPolicy) { - const evaluatedPolicy = tool.evaluateToolCallPolicy(basePolicy, args); + const evaluatedPolicy = tool.evaluateToolCallPolicy( + basePolicy, + parsedArgs, + processedArgs, + ); return { policy: evaluatedPolicy, displayValue }; } return { policy: basePolicy, displayValue }; diff --git a/core/index.d.ts b/core/index.d.ts index 685b8ade1bd..8fef0adf1a0 100644 --- a/core/index.d.ts +++ b/core/index.d.ts @@ -1110,6 +1110,7 @@ export interface Tool { evaluateToolCallPolicy?: ( basePolicy: ToolPolicy, parsedArgs: Record, + processedArgs?: Record, ) => ToolPolicy; } diff --git a/core/indexing/docs/crawlers/DocsCrawler.test.ts b/core/indexing/docs/crawlers/DocsCrawler.test.ts index 3ea442fbef2..40b424c3c3e 100644 --- a/core/indexing/docs/crawlers/DocsCrawler.test.ts +++ b/core/indexing/docs/crawlers/DocsCrawler.test.ts @@ -19,7 +19,7 @@ const TIMEOUT_MS = 1_000_000_000; // so that we don't delete the Chromium install between tests ChromiumInstaller.PCR_CONFIG = { downloadPath: os.tmpdir() }; -describe("DocsCrawler", () => { +describe.skip("DocsCrawler", () => { let config: ContinueConfig; let mockIde: FileSystemIde; let chromiumInstaller: ChromiumInstaller; diff --git a/core/package-lock.json b/core/package-lock.json index 1c830c623ad..dcd7fd2c19a 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -56,6 +56,7 @@ "mac-ca": "^3.1.0", "node-fetch": "^3.3.2", "node-html-markdown": "^1.3.0", + "normalize-path": "^3.0.0", "ollama": "^0.4.6", "onnxruntime-node": "1.14.0", "openai": "^5.13.1", @@ -76,6 +77,7 @@ "system-ca": "^1.0.3", "tar": "^7.4.3", "tree-sitter-wasms": "^0.1.11", + "untildify": "^6.0.0", "uuid": "^9.0.1", "vectordb": "^0.4.20", "web-tree-sitter": "^0.21.0", @@ -101,6 +103,7 @@ "@types/mozilla-readability": "^0.2.1", "@types/mustache": "^4.2.5", "@types/node-fetch": "^2.6.11", + "@types/normalize-path": "^3.0.2", "@types/pg": "^8.11.6", "@types/plist": "^3.0.5", "@types/request": "^2.48.12", @@ -6858,6 +6861,12 @@ "@types/node": "*" } }, + "node_modules/@types/normalize-path": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/normalize-path/-/normalize-path-3.0.2.tgz", + "integrity": "sha512-DO++toKYPaFn0Z8hQ7Tx+3iT9t77IJo/nDiqTXilgEP+kPNIYdpS9kh3fXuc53ugqwp9pxC1PVjCpV1tQDyqMA==", + "dev": true + }, "node_modules/@types/pad-left": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@types/pad-left/-/pad-left-2.1.1.tgz", @@ -19207,6 +19216,17 @@ "webpack-virtual-modules": "^0.5.0" } }, + "node_modules/untildify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-6.0.0.tgz", + "integrity": "sha512-sA2YTBvW2F463GvSbiZtso+dpuQV+B7xX9saX30SGrR5Fyx4AUcvA/zN+ShAkABKUKVyDaHECsJrHv5ToTuHsQ==", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", diff --git a/core/package.json b/core/package.json index f20ba384bef..e86f1095f0a 100644 --- a/core/package.json +++ b/core/package.json @@ -121,6 +121,7 @@ "system-ca": "^1.0.3", "tar": "^7.4.3", "tree-sitter-wasms": "^0.1.11", + "untildify": "^6.0.0", "uuid": "^9.0.1", "vectordb": "^0.4.20", "web-tree-sitter": "^0.21.0", diff --git a/core/protocol/core.ts b/core/protocol/core.ts index f23666165bf..82542caeee5 100644 --- a/core/protocol/core.ts +++ b/core/protocol/core.ts @@ -314,7 +314,12 @@ export type ToCoreFromIdeOrWebviewProtocol = { }, ]; "tools/evaluatePolicy": [ - { toolName: string; basePolicy: ToolPolicy; args: Record }, + { + toolName: string; + basePolicy: ToolPolicy; + parsedArgs: Record; + processedArgs?: Record; + }, { policy: ToolPolicy; displayValue?: string }, ]; "tools/preprocessArgs": [ diff --git a/core/tools/definitions/createNewFile.ts b/core/tools/definitions/createNewFile.ts index 4d52cd6654f..7bfc3f8f0e4 100644 --- a/core/tools/definitions/createNewFile.ts +++ b/core/tools/definitions/createNewFile.ts @@ -1,5 +1,8 @@ +import { ToolPolicy } from "@continuedev/terminal-security"; import { Tool } from "../.."; +import { ResolvedPath, resolveInputPath } from "../../util/pathResolver"; import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn"; +import { evaluateFileAccessPolicy } from "../policies/fileAccess"; export const createNewFileTool: Tool = { type: "function", @@ -21,7 +24,7 @@ export const createNewFileTool: Tool = { filepath: { type: "string", description: - "The path where the new file should be created, relative to the root of the workspace", + "The path where the new file should be created. Can be a relative path (from workspace root), absolute path, tilde path (~/...), or file:// URI.", }, contents: { type: "string", @@ -38,4 +41,26 @@ export const createNewFileTool: Tool = { ["contents", "Contents of the file"], ], }, + preprocessArgs: async (args, { ide }) => { + const filepath = args.filepath as string; + const resolvedPath = await resolveInputPath(ide, filepath); + + // Store the resolved path info in args for policy evaluation + return { + resolvedPath, + }; + }, + evaluateToolCallPolicy: ( + basePolicy: ToolPolicy, + _: Record, + processedArgs?: Record, + ): ToolPolicy => { + const resolvedPath = processedArgs?.resolvedPath as + | ResolvedPath + | null + | undefined; + if (!resolvedPath) return basePolicy; + + return evaluateFileAccessPolicy(basePolicy, resolvedPath.isWithinWorkspace); + }, }; diff --git a/core/tools/definitions/ls.ts b/core/tools/definitions/ls.ts index 49ac5b1c6a1..4892f720b9d 100644 --- a/core/tools/definitions/ls.ts +++ b/core/tools/definitions/ls.ts @@ -1,6 +1,9 @@ import { Tool } from "../.."; +import { ToolPolicy } from "@continuedev/terminal-security"; +import { ResolvedPath, resolveInputPath } from "../../util/pathResolver"; import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn"; +import { evaluateFileAccessPolicy } from "../policies/fileAccess"; export const lsTool: Tool = { type: "function", @@ -20,7 +23,7 @@ export const lsTool: Tool = { dirPath: { type: "string", description: - "The directory path relative to the root of the project. Use forward slash paths like '/'. rather than e.g. '.'", + "The directory path. Can be relative to project root, absolute path, tilde path (~/...), or file:// URI. Use forward slash paths", }, recursive: { type: "boolean", @@ -39,4 +42,28 @@ export const lsTool: Tool = { ], }, toolCallIcon: "FolderIcon", + preprocessArgs: async (args, { ide }) => { + const dirPath = args.dirPath as string; + + // Default to current directory if no path provided + const pathToResolve = dirPath || "."; + const resolvedPath = await resolveInputPath(ide, pathToResolve); + + return { + resolvedPath, + }; + }, + evaluateToolCallPolicy: ( + basePolicy: ToolPolicy, + _: Record, + processedArgs?: Record, + ): ToolPolicy => { + const resolvedPath = processedArgs?.resolvedPath as + | ResolvedPath + | null + | undefined; + if (!resolvedPath) return basePolicy; + + return evaluateFileAccessPolicy(basePolicy, resolvedPath.isWithinWorkspace); + }, }; diff --git a/core/tools/definitions/readFile.ts b/core/tools/definitions/readFile.ts index ee6c1c91cfb..9342e791648 100644 --- a/core/tools/definitions/readFile.ts +++ b/core/tools/definitions/readFile.ts @@ -1,5 +1,8 @@ +import { ToolPolicy } from "@continuedev/terminal-security"; import { Tool } from "../.."; +import { ResolvedPath, resolveInputPath } from "../../util/pathResolver"; import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn"; +import { evaluateFileAccessPolicy } from "../policies/fileAccess"; export const readFileTool: Tool = { type: "function", @@ -21,7 +24,7 @@ export const readFileTool: Tool = { filepath: { type: "string", description: - "The path of the file to read, relative to the root of the workspace (NOT uri or absolute path)", + "The path of the file to read. Can be a relative path (from workspace root), absolute path, tilde path (~/...), or file:// URI", }, }, }, @@ -32,4 +35,26 @@ export const readFileTool: Tool = { }, defaultToolPolicy: "allowedWithoutPermission", toolCallIcon: "DocumentIcon", + preprocessArgs: async (args, { ide }) => { + const filepath = args.filepath as string; + const resolvedPath = await resolveInputPath(ide, filepath); + + // Store the resolved path info in args for policy evaluation + return { + resolvedPath, + }; + }, + evaluateToolCallPolicy: ( + basePolicy: ToolPolicy, + _: Record, + processedArgs?: Record, + ): ToolPolicy => { + const resolvedPath = processedArgs?.resolvedPath as + | ResolvedPath + | null + | undefined; + if (!resolvedPath) return basePolicy; + + return evaluateFileAccessPolicy(basePolicy, resolvedPath.isWithinWorkspace); + }, }; diff --git a/core/tools/definitions/readFileRange.ts b/core/tools/definitions/readFileRange.ts index 41a7bfcfd6b..2c573c72dbf 100644 --- a/core/tools/definitions/readFileRange.ts +++ b/core/tools/definitions/readFileRange.ts @@ -1,5 +1,8 @@ +import { ToolPolicy } from "@continuedev/terminal-security"; import { Tool } from "../.."; +import { ResolvedPath, resolveInputPath } from "../../util/pathResolver"; import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn"; +import { evaluateFileAccessPolicy } from "../policies/fileAccess"; export const readFileRangeTool: Tool = { type: "function", @@ -49,4 +52,25 @@ export const readFileRangeTool: Tool = { }, defaultToolPolicy: "allowedWithoutPermission", toolCallIcon: "DocumentIcon", + preprocessArgs: async (args, { ide }) => { + const filepath = args.filepath as string; + const resolvedPath = await resolveInputPath(ide, filepath); + + return { + resolvedPath, + }; + }, + evaluateToolCallPolicy: ( + basePolicy: ToolPolicy, + _: Record, + processedArgs?: Record, + ): ToolPolicy => { + const resolvedPath = processedArgs?.resolvedPath as + | ResolvedPath + | null + | undefined; + if (!resolvedPath) return basePolicy; + + return evaluateFileAccessPolicy(basePolicy, resolvedPath.isWithinWorkspace); + }, }; diff --git a/core/tools/definitions/viewSubdirectory.ts b/core/tools/definitions/viewSubdirectory.ts index f8776716f52..eafd35dd752 100644 --- a/core/tools/definitions/viewSubdirectory.ts +++ b/core/tools/definitions/viewSubdirectory.ts @@ -1,5 +1,8 @@ +import { ToolPolicy } from "@continuedev/terminal-security"; import { Tool } from "../.."; +import { ResolvedPath, resolveInputPath } from "../../util/pathResolver"; import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn"; +import { evaluateFileAccessPolicy } from "../policies/fileAccess"; export const viewSubdirectoryTool: Tool = { type: "function", @@ -31,4 +34,25 @@ export const viewSubdirectoryTool: Tool = { }, defaultToolPolicy: "allowedWithPermission", toolCallIcon: "FolderOpenIcon", + preprocessArgs: async (args, { ide }) => { + const directoryPath = args.directory_path as string; + const resolvedPath = await resolveInputPath(ide, directoryPath); + + return { + resolvedPath, + }; + }, + evaluateToolCallPolicy: ( + basePolicy: ToolPolicy, + _: Record, + processedArgs?: Record, + ): ToolPolicy => { + const resolvedPath = processedArgs?.resolvedPath as + | ResolvedPath + | null + | undefined; + if (!resolvedPath) return basePolicy; + + return evaluateFileAccessPolicy(basePolicy, resolvedPath.isWithinWorkspace); + }, }; diff --git a/core/tools/implementations/lsTool.ts b/core/tools/implementations/lsTool.ts index 67e0bb1718d..c8c75306092 100644 --- a/core/tools/implementations/lsTool.ts +++ b/core/tools/implementations/lsTool.ts @@ -2,14 +2,15 @@ import ignore from "ignore"; import { ToolImpl } from "."; import { walkDir } from "../../indexing/walkDir"; -import { resolveRelativePathInDir } from "../../util/ideUtils"; +import { resolveInputPath } from "../../util/pathResolver"; import { ContinueError, ContinueErrorReason } from "../../util/errors"; export function resolveLsToolDirPath(dirPath: string | undefined) { if (!dirPath || dirPath === ".") { return "/"; } - if (dirPath.startsWith(".")) { + // Don't strip leading slash from absolute paths - let the resolver handle it + if (dirPath.startsWith(".") && !dirPath.startsWith("./")) { return dirPath.slice(1); } return dirPath.replace(/\\/g, "/"); @@ -19,15 +20,15 @@ const MAX_LS_TOOL_LINES = 200; export const lsToolImpl: ToolImpl = async (args, extras) => { const dirPath = resolveLsToolDirPath(args?.dirPath); - const uri = await resolveRelativePathInDir(dirPath, extras.ide); - if (!uri) { + const resolvedPath = await resolveInputPath(extras.ide, dirPath); + if (!resolvedPath) { throw new ContinueError( ContinueErrorReason.DirectoryNotFound, - `Directory ${args.dirPath} not found. Make sure to use forward-slash paths`, + `Directory ${args.dirPath} not found or is not accessible. You can use absolute paths, relative paths, or paths starting with ~`, ); } - const entries = await walkDir(uri, extras.ide, { + const entries = await walkDir(resolvedPath.uri, extras.ide, { returnRelativeUrisPaths: true, include: "both", recursive: args?.recursive ?? false, @@ -39,12 +40,12 @@ export const lsToolImpl: ToolImpl = async (args, extras) => { let content = lines.length > 0 ? lines.join("\n") - : `No files/folders found in ${dirPath}`; + : `No files/folders found in ${resolvedPath.displayPath}`; const contextItems = [ { name: "File/folder list", - description: `Files/folders in ${dirPath}`, + description: `Files/folders in ${resolvedPath.displayPath}`, content, }, ]; diff --git a/core/tools/implementations/lsTool.vitest.ts b/core/tools/implementations/lsTool.vitest.ts index b697a90c5fa..c3c571294d8 100644 --- a/core/tools/implementations/lsTool.vitest.ts +++ b/core/tools/implementations/lsTool.vitest.ts @@ -25,7 +25,7 @@ test("resolveLsToolDirPath handles dot", () => { }); test("resolveLsToolDirPath handles dot relative", () => { - expect(resolveLsToolDirPath("./hi")).toBe("/hi"); + expect(resolveLsToolDirPath("./hi")).toBe("./hi"); }); test("resolveLsToolDirPath normalizes backslashes to forward slashes", () => { diff --git a/core/tools/implementations/readFile.ts b/core/tools/implementations/readFile.ts index 8daee746212..6c55912eef3 100644 --- a/core/tools/implementations/readFile.ts +++ b/core/tools/implementations/readFile.ts @@ -1,4 +1,4 @@ -import { resolveRelativePathInDir } from "../../util/ideUtils"; +import { resolveInputPath } from "../../util/pathResolver"; import { getUriPathBasename } from "../../util/uri"; import { ToolImpl } from "."; @@ -9,31 +9,35 @@ import { ContinueError, ContinueErrorReason } from "../../util/errors"; export const readFileImpl: ToolImpl = async (args, extras) => { const filepath = getStringArg(args, "filepath"); - throwIfFileIsSecurityConcern(filepath); - const firstUriMatch = await resolveRelativePathInDir(filepath, extras.ide); - if (!firstUriMatch) { + // Resolve the path first to get the actual path for security check + const resolvedPath = await resolveInputPath(extras.ide, filepath); + if (!resolvedPath) { throw new ContinueError( ContinueErrorReason.FileNotFound, - `File "${filepath}" does not exist. You might want to check the path and try again.`, + `File "${filepath}" does not exist or is not accessible. You might want to check the path and try again.`, ); } - const content = await extras.ide.readFile(firstUriMatch); + + // Security check on the resolved display path + throwIfFileIsSecurityConcern(resolvedPath.displayPath); + + const content = await extras.ide.readFile(resolvedPath.uri); await throwIfFileExceedsHalfOfContext( - filepath, + resolvedPath.displayPath, content, extras.config.selectedModelByRole.chat, ); return [ { - name: getUriPathBasename(firstUriMatch), - description: filepath, + name: getUriPathBasename(resolvedPath.uri), + description: resolvedPath.displayPath, content, uri: { type: "file", - value: firstUriMatch, + value: resolvedPath.uri, }, }, ]; diff --git a/core/tools/implementations/readFileRange.ts b/core/tools/implementations/readFileRange.ts index 0ff97d18991..7b5f47338e0 100644 --- a/core/tools/implementations/readFileRange.ts +++ b/core/tools/implementations/readFileRange.ts @@ -1,7 +1,8 @@ -import { resolveRelativePathInDir } from "../../util/ideUtils"; +import { resolveInputPath } from "../../util/pathResolver"; import { getUriPathBasename } from "../../util/uri"; import { ToolImpl } from "."; +import { throwIfFileIsSecurityConcern } from "../../indexing/ignore"; import { getNumberArg, getStringArg } from "../parseArgs"; import { throwIfFileExceedsHalfOfContext } from "./readFileLimit"; import { ContinueError, ContinueErrorReason } from "../../util/errors"; @@ -31,16 +32,20 @@ export const readFileRangeImpl: ToolImpl = async (args, extras) => { ); } - const firstUriMatch = await resolveRelativePathInDir(filepath, extras.ide); - if (!firstUriMatch) { + // Resolve the path first to get the actual path for security check + const resolvedPath = await resolveInputPath(extras.ide, filepath); + if (!resolvedPath) { throw new ContinueError( ContinueErrorReason.FileNotFound, - `File "${filepath}" does not exist. You might want to check the path and try again.`, + `File "${filepath}" does not exist or is not accessible. You might want to check the path and try again.`, ); } + // Security check on the resolved display path + throwIfFileIsSecurityConcern(resolvedPath.displayPath); + // Use the IDE's readRangeInFile method with 0-based range (IDE expects 0-based internally) - const content = await extras.ide.readRangeInFile(firstUriMatch, { + const content = await extras.ide.readRangeInFile(resolvedPath.uri, { start: { line: startLine - 1, // Convert from 1-based to 0-based character: 0, @@ -52,21 +57,21 @@ export const readFileRangeImpl: ToolImpl = async (args, extras) => { }); await throwIfFileExceedsHalfOfContext( - filepath, + resolvedPath.displayPath, content, extras.config.selectedModelByRole.chat, ); - const rangeDescription = `${filepath} (lines ${startLine}-${endLine})`; + const rangeDescription = `${resolvedPath.displayPath} (lines ${startLine}-${endLine})`; return [ { - name: getUriPathBasename(firstUriMatch), + name: getUriPathBasename(resolvedPath.uri), description: rangeDescription, content, uri: { type: "file", - value: firstUriMatch, + value: resolvedPath.uri, }, }, ]; diff --git a/core/tools/implementations/viewSubdirectory.ts b/core/tools/implementations/viewSubdirectory.ts index de546833852..c48ec0cd316 100644 --- a/core/tools/implementations/viewSubdirectory.ts +++ b/core/tools/implementations/viewSubdirectory.ts @@ -1,5 +1,5 @@ import generateRepoMap from "../../util/generateRepoMap"; -import { resolveRelativePathInDir } from "../../util/ideUtils"; +import { resolveInputPath } from "../../util/pathResolver"; import { ToolImpl } from "."; import { ContinueError, ContinueErrorReason } from "../../util/errors"; @@ -8,17 +8,26 @@ import { getStringArg } from "../parseArgs"; export const viewSubdirectoryImpl: ToolImpl = async (args: any, extras) => { const directory_path = getStringArg(args, "directory_path"); - const uri = await resolveRelativePathInDir(directory_path, extras.ide); + const resolvedPath = await resolveInputPath(extras.ide, directory_path); - if (!uri) { + if (!resolvedPath) { throw new ContinueError( ContinueErrorReason.DirectoryNotFound, - `Directory path "${directory_path}" does not exist.`, + `Directory path "${directory_path}" does not exist or is not accessible.`, + ); + } + + // Check if the resolved path actually exists + const exists = await extras.ide.fileExists(resolvedPath.uri); + if (!exists) { + throw new ContinueError( + ContinueErrorReason.DirectoryNotFound, + `Directory path "${directory_path}" does not exist or is not accessible.`, ); } const repoMap = await generateRepoMap(extras.llm, extras.ide, { - dirUris: [uri], + dirUris: [resolvedPath.uri], outputRelativeUriPaths: true, includeSignatures: false, }); @@ -26,7 +35,7 @@ export const viewSubdirectoryImpl: ToolImpl = async (args: any, extras) => { return [ { name: "Repo map", - description: `Map of ${directory_path}`, + description: `Map of ${resolvedPath.displayPath}`, content: repoMap, }, ]; diff --git a/core/tools/implementations/viewSubdirectory.vitest.ts b/core/tools/implementations/viewSubdirectory.vitest.ts new file mode 100644 index 00000000000..1bbd6243a36 --- /dev/null +++ b/core/tools/implementations/viewSubdirectory.vitest.ts @@ -0,0 +1,51 @@ +import { describe, expect, it, vi } from "vitest"; +import { ContinueError, ContinueErrorReason } from "../../util/errors"; +import { viewSubdirectoryImpl } from "./viewSubdirectory"; + +describe("viewSubdirectoryImpl", () => { + it("should throw DirectoryNotFound when resolveInputPath returns null", async () => { + const mockExtras = { + ide: { + fileExists: vi.fn().mockResolvedValue(false), + getWorkspaceDirs: vi.fn().mockResolvedValue(["file:///workspace"]), + }, + llm: {}, + }; + + // resolveInputPath will return null when path doesn't exist + await expect( + viewSubdirectoryImpl( + { directory_path: "/non/existent/path" }, + mockExtras as any, + ), + ).rejects.toThrow(ContinueError); + }); + + it("should throw DirectoryNotFound when path exists in resolveInputPath but not on filesystem", async () => { + const mockExtras = { + ide: { + fileExists: vi.fn().mockResolvedValue(false), // Path doesn't exist + getWorkspaceDirs: vi.fn().mockResolvedValue(["file:///workspace"]), + }, + llm: {}, + }; + + // This test verifies the fix - even if resolveInputPath returns a valid object, + // we still check if the path exists and throw if it doesn't + try { + await viewSubdirectoryImpl( + { directory_path: "/some/absolute/path" }, + mockExtras as any, + ); + expect.fail("Should have thrown DirectoryNotFound error"); + } catch (error) { + expect(error).toBeInstanceOf(ContinueError); + expect((error as ContinueError).reason).toBe( + ContinueErrorReason.DirectoryNotFound, + ); + expect((error as ContinueError).message).toContain( + "does not exist or is not accessible", + ); + } + }); +}); diff --git a/core/tools/policies/fileAccess.ts b/core/tools/policies/fileAccess.ts new file mode 100644 index 00000000000..2d6801f5438 --- /dev/null +++ b/core/tools/policies/fileAccess.ts @@ -0,0 +1,26 @@ +import { ToolPolicy } from "@continuedev/terminal-security"; + +/** + * Evaluates file access policy based on whether the file is within workspace boundaries + * + * @param basePolicy - The base policy from tool definition or user settings + * @param isWithinWorkspace - Whether the file/directory is within workspace + * @returns The evaluated policy - more restrictive for files outside workspace + */ +export function evaluateFileAccessPolicy( + basePolicy: ToolPolicy, + isWithinWorkspace: boolean, +): ToolPolicy { + // If tool is disabled, keep it disabled + if (basePolicy === "disabled") { + return "disabled"; + } + + // Files within workspace use the base policy (typically "allowedWithoutPermission") + if (isWithinWorkspace) { + return basePolicy; + } + + // Files outside workspace always require permission for security + return "allowedWithPermission"; +} diff --git a/core/util/pathResolver.ts b/core/util/pathResolver.ts new file mode 100644 index 00000000000..25fe7581688 --- /dev/null +++ b/core/util/pathResolver.ts @@ -0,0 +1,84 @@ +import { fileURLToPath, pathToFileURL } from "node:url"; +import * as path from "path"; +import untildify from "untildify"; +import { IDE } from ".."; +import { resolveRelativePathInDir } from "./ideUtils"; +import { findUriInDirs } from "./uri"; + +export interface ResolvedPath { + uri: string; + displayPath: string; + isAbsolute: boolean; + isWithinWorkspace: boolean; +} + +/** + * Checks if a URI is within any of the workspace directories + * Also verifies the file actually exists, matching the behavior of resolveRelativePathInDir + */ +async function isUriWithinWorkspace(ide: IDE, uri: string): Promise { + const workspaceDirs = await ide.getWorkspaceDirs(); + const { foundInDir } = findUriInDirs(uri, workspaceDirs); + + // Check both: within workspace path AND file exists + if (foundInDir !== null) { + return await ide.fileExists(uri); + } + + return false; +} + +export async function resolveInputPath( + ide: IDE, + inputPath: string, +): Promise { + const trimmedPath = inputPath.trim(); + + // Handle file:// URIs + if (trimmedPath.startsWith("file://")) { + const displayPath = fileURLToPath(trimmedPath); + const isWithinWorkspace = await isUriWithinWorkspace(ide, trimmedPath); + return { + uri: trimmedPath, + displayPath, + isAbsolute: true, + isWithinWorkspace, + }; + } + + // Expand tilde paths (handles ~/ and ~username/) + const expandedPath = untildify(trimmedPath); + + // Check if it's an absolute path (including Windows paths) + const isAbsolute = + path.isAbsolute(expandedPath) || + // Windows network paths + expandedPath.startsWith("\\\\") || + // Windows drive letters + /^[a-zA-Z]:/.test(expandedPath); + + if (isAbsolute) { + // Convert to file:// URI format + const uri = pathToFileURL(expandedPath).href; + const isWithinWorkspace = await isUriWithinWorkspace(ide, uri); + return { + uri, + displayPath: expandedPath, + isAbsolute: true, + isWithinWorkspace, + }; + } + + // Handle relative paths... + const workspaceUri = await resolveRelativePathInDir(expandedPath, ide); + if (workspaceUri) { + return { + uri: workspaceUri, + displayPath: expandedPath, + isAbsolute: false, + isWithinWorkspace: true, + }; + } + + return null; +} diff --git a/extensions/cli/src/util/pathResolver.ts b/extensions/cli/src/util/pathResolver.ts new file mode 100644 index 00000000000..7521dba71dd --- /dev/null +++ b/extensions/cli/src/util/pathResolver.ts @@ -0,0 +1,33 @@ +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; + +/** + * Resolves user-provided paths for CLI context. + * Handles absolute paths, tilde paths, and relative paths. + */ +export function resolveInputPath(inputPath: string): string | null { + // Trim whitespace + const trimmedPath = inputPath.trim(); + + // Expand tilde paths + let expandedPath = trimmedPath; + if (trimmedPath.startsWith("~/") || trimmedPath.startsWith("~\\")) { + expandedPath = path.join(os.homedir(), trimmedPath.slice(2)); + } else if (trimmedPath === "~") { + expandedPath = os.homedir(); + } else if (trimmedPath.startsWith("./")) { + // Keep relative paths starting with ./ as is (relative to cwd)z + expandedPath = trimmedPath; + } + + // Resolve the path (handles both absolute and relative paths) + const resolvedPath = path.resolve(expandedPath); + + // Check if the path exists + if (fs.existsSync(resolvedPath)) { + return resolvedPath; + } + + return null; +} diff --git a/gui/src/redux/thunks/evaluateToolPolicies.ts b/gui/src/redux/thunks/evaluateToolPolicies.ts index 37c009210a2..af02ce12814 100644 --- a/gui/src/redux/thunks/evaluateToolPolicies.ts +++ b/gui/src/redux/thunks/evaluateToolPolicies.ts @@ -38,14 +38,12 @@ async function evaluateToolPolicy( )?.defaultToolPolicy ?? DEFAULT_TOOL_SETTING; - // Use already parsed arguments - const parsedArgs = toolCallState.parsedArgs || {}; - const toolName = toolCallState.toolCall.function.name; const result = await ideMessenger.request("tools/evaluatePolicy", { toolName, basePolicy, - args: parsedArgs, + parsedArgs: toolCallState.parsedArgs, + processedArgs: toolCallState.processedArgs, }); // Evaluate the policy dynamically diff --git a/gui/src/redux/thunks/streamResponse_toolCalls.test.ts b/gui/src/redux/thunks/streamResponse_toolCalls.test.ts index e8a5393d8a3..f80da547327 100644 --- a/gui/src/redux/thunks/streamResponse_toolCalls.test.ts +++ b/gui/src/redux/thunks/streamResponse_toolCalls.test.ts @@ -1949,9 +1949,9 @@ describe("streamResponseThunk - tool calls", () => { data, ) => { if ( - "command" in data.args && - typeof data.args.command === "string" && - data.args.command?.toLowerCase().startsWith("echo") + "command" in data.parsedArgs && + typeof data.parsedArgs.command === "string" && + data.parsedArgs.command?.toLowerCase().startsWith("echo") ) { return { policy: "allowedWithPermission" }; } @@ -2010,7 +2010,7 @@ describe("streamResponseThunk - tool calls", () => { expect.objectContaining({ toolName: terminalName, basePolicy: "allowedWithoutPermission", - args: { command: "echo hello" }, + parsedArgs: { command: "echo hello" }, }), ); @@ -2109,7 +2109,7 @@ describe("streamResponseThunk - tool calls", () => { expect.objectContaining({ toolName: terminalName, basePolicy: "allowedWithPermission", - args: { command: "ls" }, + parsedArgs: { command: "ls" }, }), ); @@ -2268,7 +2268,7 @@ describe("streamResponseThunk - tool calls", () => { let numCalls = 0; mockTerminalIdeMessenger.responseHandlers["tools/evaluatePolicy"] = async (data) => { - const args = data.args || {}; + const args = data.parsedArgs || {}; numCalls++; if ( numCalls <= 1 && diff --git a/package-lock.json b/package-lock.json index 3989f3c0032..88c4caf915f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "continue", "devDependencies": { - "@typescript-eslint/parser": "^7.8.0", + "@typescript-eslint/parser": "^8.40.0", "concurrently": "^9.1.2", "eslint-plugin-import": "^2.29.1", "husky": "^9.1.7", @@ -229,60 +229,90 @@ "license": "MIT" }, "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.1.tgz", + "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.1.tgz", + "integrity": "sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.1", + "@typescript-eslint/types": "^8.46.1", + "debug": "^4.3.4" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz", + "integrity": "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz", + "integrity": "sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==", + "dev": true, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz", + "integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==", "dev": true, - "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -290,52 +320,62 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz", + "integrity": "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", + "@typescript-eslint/project-service": "8.46.1", + "@typescript-eslint/tsconfig-utils": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz", + "integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.46.1", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -477,16 +517,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/array.prototype.findlastindex": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", @@ -607,7 +637,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -1019,19 +1048,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1477,6 +1493,7 @@ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -1622,7 +1639,6 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1639,7 +1655,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -1997,27 +2012,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2165,6 +2159,7 @@ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -3099,7 +3094,6 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -3149,7 +3143,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -3462,16 +3455,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3891,11 +3874,10 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -4077,16 +4059,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -4339,16 +4311,15 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, - "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/tsconfig-paths": { diff --git a/package.json b/package.json index ceb907fddfb..8f5aa040ee6 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "*.{js,jsx,ts,tsx,json,css,md}": "prettier --write" }, "devDependencies": { - "@typescript-eslint/parser": "^7.8.0", + "@typescript-eslint/parser": "^8.40.0", "concurrently": "^9.1.2", "eslint-plugin-import": "^2.29.1", "husky": "^9.1.7",