diff --git a/.changeset/symlink-workspace-boundary.md b/.changeset/symlink-workspace-boundary.md new file mode 100644 index 0000000000..4caefd0b96 --- /dev/null +++ b/.changeset/symlink-workspace-boundary.md @@ -0,0 +1,5 @@ +--- +"zoo-code": patch +--- + +Resolve symlinks in the workspace-boundary check so a symlink inside the workspace that points outside is correctly treated as outside, closing a bypass of the out-of-workspace file protection (#169). Adds an opt-in `allowSymlinksOutsideWorkspace` setting (default off) for users who deliberately rely on symlinks that point outside the workspace. diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 3a5a99eb98..09b66b28c5 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -101,6 +101,7 @@ export const globalSettingsSchema = z.object({ alwaysAllowWrite: z.boolean().optional(), alwaysAllowWriteOutsideWorkspace: z.boolean().optional(), alwaysAllowWriteProtected: z.boolean().optional(), + allowSymlinksOutsideWorkspace: z.boolean().optional(), writeDelayMs: z.number().min(0).optional(), requestDelaySeconds: z.number().optional(), alwaysAllowMcp: z.boolean().optional(), diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index c09f22aed7..c2666e4560 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -252,6 +252,7 @@ export type ExtensionState = Pick< | "alwaysAllowReadOnlyOutsideWorkspace" | "alwaysAllowWrite" | "alwaysAllowWriteOutsideWorkspace" + | "allowSymlinksOutsideWorkspace" | "alwaysAllowWriteProtected" | "alwaysAllowMcp" | "alwaysAllowModeSwitch" diff --git a/src/core/tools/BaseTool.ts b/src/core/tools/BaseTool.ts index 7d574068a9..9793b5cb63 100644 --- a/src/core/tools/BaseTool.ts +++ b/src/core/tools/BaseTool.ts @@ -2,6 +2,7 @@ import type { ToolName } from "@roo-code/types" import { Task } from "../task/Task" import type { ToolUse, HandleError, PushToolResult, AskApproval, NativeToolArgs } from "../../shared/tools" +import { isPathOutsideWorkspace } from "../../utils/pathUtils" /** * Callbacks passed to tool execution @@ -98,6 +99,18 @@ export abstract class BaseTool { this.lastSeenPartialPath = undefined } + /** + * Resolve whether an absolute path is outside the workspace, honoring the + * `allowSymlinksOutsideWorkspace` setting (#169 / #241). When that setting is enabled, + * a symlink resolving outside the workspace is treated by its lexical path rather than + * being blocked; otherwise symlink targets are resolved and the check fails closed. + */ + protected async resolveIsOutsideWorkspace(task: Task, absolutePath: string): Promise { + const allowSymlinksOutsideWorkspace = + (await task.providerRef.deref()?.getState())?.allowSymlinksOutsideWorkspace ?? false + return isPathOutsideWorkspace(absolutePath, { allowSymlinksOutsideWorkspace }) + } + /** * Main entry point for tool execution. * diff --git a/src/core/tools/EditFileTool.ts b/src/core/tools/EditFileTool.ts index 2495a372bc..46cfbafbc6 100644 --- a/src/core/tools/EditFileTool.ts +++ b/src/core/tools/EditFileTool.ts @@ -4,7 +4,6 @@ import path from "path" import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { getReadablePath } from "../../utils/path" -import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { Task } from "../task/Task" import { formatResponse } from "../prompts/responses" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" @@ -157,7 +156,7 @@ export class EditFileTool extends BaseTool<"edit_file"> { } const absolutePath = path.resolve(task.cwd, relPath) - const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath) + const isOutsideWorkspace = await this.resolveIsOutsideWorkspace(task, absolutePath) const sharedMessageProps: ClineSayTool = { tool: "appliedDiff", @@ -399,7 +398,7 @@ export class EditFileTool extends BaseTool<"edit_file"> { const sanitizedDiff = sanitizeUnifiedDiff(diff || "") const diffStats = computeDiffStats(sanitizedDiff) || undefined - const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath) + const isOutsideWorkspace = await this.resolveIsOutsideWorkspace(task, absolutePath) const sharedMessageProps: ClineSayTool = { tool: isNewFile ? "newFileCreated" : "appliedDiff", @@ -512,7 +511,7 @@ export class EditFileTool extends BaseTool<"edit_file"> { this.partialToolAskRelPath = relPath const absolutePath = path.resolve(task.cwd, relPath) - const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath) + const isOutsideWorkspace = await this.resolveIsOutsideWorkspace(task, absolutePath) const sharedMessageProps: ClineSayTool = { tool: "appliedDiff", diff --git a/src/core/tools/ListFilesTool.ts b/src/core/tools/ListFilesTool.ts index 716d7ed784..5da4aee5c5 100644 --- a/src/core/tools/ListFilesTool.ts +++ b/src/core/tools/ListFilesTool.ts @@ -6,7 +6,6 @@ import { Task } from "../task/Task" import { formatResponse } from "../prompts/responses" import { listFiles } from "../../services/glob/list-files" import { getReadablePath } from "../../utils/path" -import { isPathOutsideWorkspace } from "../../utils/pathUtils" import type { ToolUse } from "../../shared/tools" import { BaseTool, ToolCallbacks } from "./BaseTool" @@ -35,7 +34,7 @@ export class ListFilesTool extends BaseTool<"list_files"> { task.consecutiveMistakeCount = 0 const absolutePath = path.resolve(task.cwd, relDirPath) - const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath) + const isOutsideWorkspace = await this.resolveIsOutsideWorkspace(task, absolutePath) const [files, didHitLimit] = await listFiles(absolutePath, recursive || false, 200) const { showRooIgnoredFiles = false } = (await task.providerRef.deref()?.getState()) ?? {} @@ -74,7 +73,7 @@ export class ListFilesTool extends BaseTool<"list_files"> { const recursive = recursiveRaw?.toLowerCase() === "true" const absolutePath = relDirPath ? path.resolve(task.cwd, relDirPath) : task.cwd - const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath) + const isOutsideWorkspace = await this.resolveIsOutsideWorkspace(task, absolutePath) const sharedMessageProps: ClineSayTool = { tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive", diff --git a/src/core/tools/SearchReplaceTool.ts b/src/core/tools/SearchReplaceTool.ts index 2d8817364f..43ad599c55 100644 --- a/src/core/tools/SearchReplaceTool.ts +++ b/src/core/tools/SearchReplaceTool.ts @@ -4,7 +4,6 @@ import path from "path" import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { getReadablePath } from "../../utils/path" -import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { Task } from "../task/Task" import { formatResponse } from "../prompts/responses" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" @@ -170,7 +169,7 @@ export class SearchReplaceTool extends BaseTool<"search_replace"> { const sanitizedDiff = sanitizeUnifiedDiff(diff) const diffStats = computeDiffStats(sanitizedDiff) || undefined - const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath) + const isOutsideWorkspace = await this.resolveIsOutsideWorkspace(task, absolutePath) const sharedMessageProps: ClineSayTool = { tool: "appliedDiff", @@ -262,7 +261,7 @@ export class SearchReplaceTool extends BaseTool<"search_replace"> { } const absolutePath = path.resolve(task.cwd, relPath) - const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath) + const isOutsideWorkspace = await this.resolveIsOutsideWorkspace(task, absolutePath) const sharedMessageProps: ClineSayTool = { tool: "appliedDiff", diff --git a/src/utils/__tests__/pathUtils.spec.ts b/src/utils/__tests__/pathUtils.spec.ts new file mode 100644 index 0000000000..e6bf5224d4 --- /dev/null +++ b/src/utils/__tests__/pathUtils.spec.ts @@ -0,0 +1,112 @@ +import * as fs from "fs" +import * as os from "os" +import * as path from "path" + +import { isPathOutsideWorkspace } from "../pathUtils" + +// Mutable workspaceFolders the mocked vscode module reads from. +const { mockWorkspace } = vi.hoisted(() => ({ + mockWorkspace: { folders: [] as Array<{ uri: { fsPath: string } }> }, +})) + +vi.mock("vscode", () => ({ + workspace: { + get workspaceFolders() { + return mockWorkspace.folders.length > 0 ? mockWorkspace.folders : undefined + }, + }, +})) + +describe("isPathOutsideWorkspace", () => { + let tmpRoot: string + let workspaceDir: string + let outsideDir: string + + beforeEach(() => { + // realpath the tmp dir because macOS resolves /var -> /private/var + tmpRoot = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "zoo-pathutils-"))) + workspaceDir = path.join(tmpRoot, "workspace") + outsideDir = path.join(tmpRoot, "outside") + fs.mkdirSync(workspaceDir) + fs.mkdirSync(outsideDir) + mockWorkspace.folders = [{ uri: { fsPath: workspaceDir } }] + }) + + afterEach(() => { + mockWorkspace.folders = [] + fs.rmSync(tmpRoot, { recursive: true, force: true }) + }) + + it("treats a real file inside the workspace as inside", () => { + const inside = path.join(workspaceDir, "file.ts") + fs.writeFileSync(inside, "x") + expect(isPathOutsideWorkspace(inside)).toBe(false) + }) + + it("treats a real file outside the workspace as outside", () => { + const outside = path.join(outsideDir, "secret.txt") + fs.writeFileSync(outside, "secret") + expect(isPathOutsideWorkspace(outside)).toBe(true) + }) + + it("treats a not-yet-existing file inside the workspace as inside", () => { + // File about to be created — realpath of the parent (workspace) still resolves. + expect(isPathOutsideWorkspace(path.join(workspaceDir, "new-file.ts"))).toBe(false) + }) + + it("treats a symlink inside the workspace that points outside as OUTSIDE (#169)", () => { + const secret = path.join(outsideDir, "secret.txt") + fs.writeFileSync(secret, "secret") + const link = path.join(workspaceDir, "link-to-secret.txt") + fs.symlinkSync(secret, link) + + // Lexically the link lives inside the workspace, but it resolves outside. + expect(isPathOutsideWorkspace(link)).toBe(true) + }) + + it("treats a symlinked directory inside the workspace that points outside as OUTSIDE (#169)", () => { + fs.writeFileSync(path.join(outsideDir, "deep.txt"), "secret") + const linkDir = path.join(workspaceDir, "linked-dir") + fs.symlinkSync(outsideDir, linkDir) + + expect(isPathOutsideWorkspace(path.join(linkDir, "deep.txt"))).toBe(true) + }) + + it("allows a symlink pointing outside when allowSymlinksOutsideWorkspace is enabled (#246)", () => { + const secret = path.join(outsideDir, "secret.txt") + fs.writeFileSync(secret, "secret") + const link = path.join(workspaceDir, "link-to-secret.txt") + fs.symlinkSync(secret, link) + + // Default (secure, #169): the link resolves outside the workspace. + expect(isPathOutsideWorkspace(link)).toBe(true) + // Opt-in (#246): symlinks are not resolved, so the link's lexical location + // (inside the workspace) wins and it is treated as inside. + expect(isPathOutsideWorkspace(link, { allowSymlinksOutsideWorkspace: true })).toBe(false) + }) + + it("fails closed when symlink resolution throws a non-ENOENT error such as EACCES (#169)", () => { + const restricted = path.join(workspaceDir, "restricted.txt") + fs.writeFileSync(restricted, "x") + + // Simulate realpath failing with EACCES (e.g. a symlink whose target has + // restricted permissions). The path lexically lives inside the workspace, but + // an unresolvable symlink must be treated as outside, not silently allowed. + const spy = vi.spyOn(fs.realpathSync, "native").mockImplementation(() => { + const err: NodeJS.ErrnoException = new Error("permission denied") + err.code = "EACCES" + throw err + }) + + try { + expect(isPathOutsideWorkspace(restricted)).toBe(true) + } finally { + spy.mockRestore() + } + }) + + it("returns true when there are no workspace folders", () => { + mockWorkspace.folders = [] + expect(isPathOutsideWorkspace(path.join(workspaceDir, "file.ts"))).toBe(true) + }) +}) diff --git a/src/utils/pathUtils.ts b/src/utils/pathUtils.ts index dae300f8f3..f5fabc3c5a 100644 --- a/src/utils/pathUtils.ts +++ b/src/utils/pathUtils.ts @@ -1,23 +1,97 @@ import * as vscode from "vscode" import * as path from "path" +import * as fs from "fs" + +/** Narrow an unknown error to a Node errno exception with the given `code`. */ +function isErrnoException(err: unknown, code: string): boolean { + return err instanceof Error && (err as NodeJS.ErrnoException).code === code +} + +/** + * Resolves a path to its canonical form, following symlinks. + * + * If the path does not exist yet (e.g. a file that is about to be created), the + * realpath of the nearest existing ancestor is resolved and the remaining + * segments are re-appended. This ensures a symlink anywhere along the path is + * still followed, while paths that don't exist yet can still be evaluated. + * + * Only `ENOENT` (a not-yet-existing segment) triggers the walk-up. Any other + * error — e.g. `EACCES` on a symlink whose target has restricted permissions — + * is re-thrown rather than swallowed: silently walking up would mask the symlink + * and could let an out-of-workspace target look "inside". Callers performing a + * security check are expected to fail closed on a thrown error. See issue #169. + */ +function realPathOrNearest(target: string): string { + let current = path.resolve(target) + const trailing: string[] = [] + + // Walk up until an existing path can be resolved, bounded by the filesystem root. + while (true) { + try { + const resolved = fs.realpathSync.native(current) + return trailing.length > 0 ? path.join(resolved, ...trailing.reverse()) : resolved + } catch (err) { + if (!isErrnoException(err, "ENOENT")) { + // Non-ENOENT (e.g. EACCES): don't mask it with a walk-up — propagate so the + // caller's security check can fail closed instead of falling through to the + // lexical path. + throw err + } + const parent = path.dirname(current) + if (parent === current) { + // Reached the root without finding an existing path; fall back to the + // lexically resolved path. + return path.resolve(target) + } + trailing.push(path.basename(current)) + current = parent + } + } +} /** * Checks if a file path is outside all workspace folders * @param filePath The file path to check * @returns true if the path is outside all workspace folders, false otherwise */ -export function isPathOutsideWorkspace(filePath: string): boolean { +export function isPathOutsideWorkspace( + filePath: string, + options: { allowSymlinksOutsideWorkspace?: boolean } = {}, +): boolean { // If there are no workspace folders, consider everything outside workspace for safety if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { return true } - // Normalize and resolve the path to handle .. and . components correctly - const absolutePath = path.resolve(filePath) + // By default we resolve symlinks (not just "." / "..") so a symlink that lives + // inside the workspace but points outside it is correctly treated as outside. + // Without this, the out-of-workspace read protection was trivially bypassed by + // symlinking to a file outside the workspace (#169). + // + // When the user opts in via `allowSymlinksOutsideWorkspace`, we compare lexical + // paths instead (path.resolve, no symlink resolution) — restoring the pre-#169 + // behavior for those who deliberately rely on symlinks pointing outside. + const resolvePath = options.allowSymlinksOutsideWorkspace ? (p: string) => path.resolve(p) : realPathOrNearest + + let absolutePath: string + try { + absolutePath = resolvePath(filePath) + } catch { + // Could not safely resolve the target (e.g. EACCES on a symlink). Fail closed: + // treat it as outside the workspace rather than risk a false "inside". + return true + } // Check if the path is within any workspace folder return !vscode.workspace.workspaceFolders.some((folder) => { - const folderPath = folder.uri.fsPath + // Resolve the workspace folder too, in case it is itself reached via a symlink. + let folderPath: string + try { + folderPath = resolvePath(folder.uri.fsPath) + } catch { + // Can't resolve this folder safely; it can't be used to prove containment. + return false + } // Path is inside a workspace if it equals the workspace path or is a subfolder return absolutePath === folderPath || absolutePath.startsWith(folderPath + path.sep) }) diff --git a/webview-ui/src/components/settings/ContextManagementSettings.tsx b/webview-ui/src/components/settings/ContextManagementSettings.tsx index 8663ea6e03..0b67f0e133 100644 --- a/webview-ui/src/components/settings/ContextManagementSettings.tsx +++ b/webview-ui/src/components/settings/ContextManagementSettings.tsx @@ -33,6 +33,7 @@ type ContextManagementSettingsProps = HTMLAttributes & { maxWorkspaceFiles: number showRooIgnoredFiles?: boolean enableSubfolderRules?: boolean + allowSymlinksOutsideWorkspace?: boolean maxImageFileSize?: number maxTotalImageSize?: number profileThresholds?: Record @@ -51,6 +52,7 @@ type ContextManagementSettingsProps = HTMLAttributes & { | "maxWorkspaceFiles" | "showRooIgnoredFiles" | "enableSubfolderRules" + | "allowSymlinksOutsideWorkspace" | "maxImageFileSize" | "maxTotalImageSize" | "profileThresholds" @@ -71,6 +73,7 @@ export const ContextManagementSettings = ({ maxWorkspaceFiles, showRooIgnoredFiles, enableSubfolderRules, + allowSymlinksOutsideWorkspace, setCachedStateField, maxImageFileSize, maxTotalImageSize, @@ -246,6 +249,23 @@ export const ContextManagementSettings = ({ + + setCachedStateField("allowSymlinksOutsideWorkspace", e.target.checked)} + data-testid="allow-symlinks-outside-workspace-checkbox"> + + +
+ {t("settings:contextManagement.allowSymlinksOutsideWorkspace.description")} +
+
+ (({ onDone, t writeDelayMs, showRooIgnoredFiles, enableSubfolderRules, + allowSymlinksOutsideWorkspace, maxImageFileSize, maxTotalImageSize, customSupportPrompts, @@ -402,6 +403,7 @@ const SettingsView = forwardRef(({ onDone, t maxWorkspaceFiles: Math.min(Math.max(0, maxWorkspaceFiles ?? 200), 500), showRooIgnoredFiles: showRooIgnoredFiles ?? true, enableSubfolderRules: enableSubfolderRules ?? false, + allowSymlinksOutsideWorkspace: allowSymlinksOutsideWorkspace ?? false, maxImageFileSize: maxImageFileSize ?? 5, maxTotalImageSize: maxTotalImageSize ?? 20, includeDiagnosticMessages: @@ -835,6 +837,7 @@ const SettingsView = forwardRef(({ onDone, t maxWorkspaceFiles={maxWorkspaceFiles ?? 200} showRooIgnoredFiles={showRooIgnoredFiles} enableSubfolderRules={enableSubfolderRules} + allowSymlinksOutsideWorkspace={allowSymlinksOutsideWorkspace} maxImageFileSize={maxImageFileSize} maxTotalImageSize={maxTotalImageSize} profileThresholds={profileThresholds} diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 92ecfa7e73..59f023515a 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Permetre enllaços simbòlics que apunten fora de l'espai de treball", + "description": "Quan està activat, un enllaç simbòlic que es troba dins de l'espai de treball però en punta fora es tracta segons la seva ubicació dins de l'espai de treball en lloc de marcar-lo com a exterior. Desactivat per defecte, de manera que les destinacions dels enllaços simbòlics es resolen i l'accés fora de l'espai de treball es marca." + }, "description": "Controleu quina informació s'inclou a la finestra de context de la IA, afectant l'ús de token i la qualitat de resposta", "autoCondenseContextPercent": { "label": "Llindar per activar la condensació intel·ligent de context", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 5154d11039..b4effd5eaf 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Symlinks erlauben, die außerhalb des Arbeitsbereichs zeigen", + "description": "Wenn aktiviert, wird ein Symlink, der sich innerhalb des Arbeitsbereichs befindet, aber nach außen verweist, anhand seines Speicherorts innerhalb des Arbeitsbereichs behandelt, anstatt als außerhalb markiert zu werden. Standardmäßig deaktiviert, sodass Symlink-Ziele aufgelöst werden und der Zugriff außerhalb des Arbeitsbereichs markiert wird." + }, "description": "Steuern Sie, welche Informationen im KI-Kontextfenster enthalten sind, was den Token-Verbrauch und die Antwortqualität beeinflusst", "autoCondenseContextPercent": { "label": "Schwellenwert für intelligente Kontextkomprimierung", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index a3c11be386..dfa17cab37 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -645,6 +645,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Allow symlinks pointing outside the workspace", + "description": "When enabled, a symlink that lives inside the workspace but points outside it is treated by its location inside the workspace instead of being flagged as outside. Off by default, so symlink targets are resolved and out-of-workspace access is flagged." + }, "description": "Control what information is included in the AI's context window, affecting token usage and response quality", "autoCondenseContextPercent": { "label": "Threshold to trigger intelligent context condensing", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 8ec3e29338..b8b89f75be 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Permitir enlaces simbólicos que apunten fuera del espacio de trabajo", + "description": "Cuando está habilitado, un enlace simbólico que se encuentra dentro del espacio de trabajo pero apunta fuera se trata según su ubicación dentro del espacio de trabajo en lugar de marcarse como externo. Desactivado de forma predeterminada, por lo que los destinos de los enlaces simbólicos se resuelven y el acceso fuera del espacio de trabajo se marca." + }, "description": "Controle qué información se incluye en la ventana de contexto de la IA, afectando el uso de token y la calidad de respuesta", "autoCondenseContextPercent": { "label": "Umbral para activar la condensación inteligente de contexto", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index d746111a0a..3933e493c4 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Autoriser les liens symboliques pointant en dehors de l'espace de travail", + "description": "Lorsque cette option est activée, un lien symbolique situé dans l'espace de travail mais pointant vers l'extérieur est traité en fonction de son emplacement dans l'espace de travail au lieu d'être signalé comme extérieur. Désactivé par défaut, les cibles des liens symboliques sont résolues et l'accès en dehors de l'espace de travail est signalé." + }, "description": "Contrôlez quelles informations sont incluses dans la fenêtre de contexte de l'IA, affectant l'utilisation de token et la qualité des réponses", "autoCondenseContextPercent": { "label": "Seuil de déclenchement de la condensation intelligente du contexte", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 9a77b69ee4..5d5d8f8be3 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "वर्कस्पेस के बाहर इंगित करने वाले सिमलिंक्स की अनुमति दें", + "description": "जब सक्षम किया जाता है, तो वर्कस्पेस के अंदर मौजूद लेकिन बाहर इंगित करने वाले सिमलिंक को वर्कस्पेस के अंदर उनके स्थान के आधार पर व्यवहार किया जाता है, न कि बाहर के रूप में चिह्नित किया जाता है। डिफ़ॉल्ट रूप से बंद, इसलिए सिमलिंक लक्ष्यों को हल किया जाता है और वर्कस्पेस के बाहर पहुँच को चिह्नित किया जाता है।" + }, "description": "AI के संदर्भ विंडो में शामिल जानकारी को नियंत्रित करें, जो token उपयोग और प्रतिक्रिया गुणवत्ता को प्रभावित करता है", "autoCondenseContextPercent": { "label": "बुद्धिमान संदर्भ संघनन को ट्रिगर करने की सीमा", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 41eae1b053..0f3a7e6ef5 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Izinkan symlink yang mengarah ke luar workspace", + "description": "Saat diaktifkan, symlink yang berada di dalam workspace tetapi mengarah ke luar diperlakukan berdasarkan lokasinya di dalam workspace alih-alih ditandai sebagai luar. Nonaktif secara default, sehingga target symlink diselesaikan dan akses di luar workspace ditandai." + }, "description": "Kontrol informasi apa yang disertakan dalam context window AI, mempengaruhi penggunaan token dan kualitas respons", "autoCondenseContextPercent": { "label": "Ambang batas untuk memicu kondensasi konteks cerdas", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 806fb44462..82132e6b04 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Consenti collegamenti simbolici che puntano all'esterno dello spazio di lavoro", + "description": "Quando abilitato, un collegamento simbolico che si trova all'interno dello spazio di lavoro ma punta all'esterno viene trattato in base alla sua posizione all'interno dello spazio di lavoro invece di essere contrassegnato come esterno. Disattivato per impostazione predefinita, le destinazioni dei collegamenti simbolici vengono risolte e l'accesso al di fuori dello spazio di lavoro viene contrassegnato." + }, "description": "Controlla quali informazioni sono incluse nella finestra di contesto dell'IA, influenzando l'utilizzo di token e la qualità delle risposte", "autoCondenseContextPercent": { "label": "Soglia per attivare la condensazione intelligente del contesto", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index b9a0976c42..8d0ad40f93 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "ワークスペース外を指すシンボリックリンクを許可", + "description": "有効にすると、ワークスペース内に存在するがワークスペース外を指すシンボリックリンクは、ワークスペース外としてフラグを立てる代わりに、ワークスペース内の場所として扱われます。デフォルトではオフのため、シンボリックリンクのターゲットは解決され、ワークスペース外へのアクセスにはフラグが立てられます。" + }, "description": "AIのコンテキストウィンドウに含まれる情報を制御し、token使用量とレスポンスの品質に影響します", "autoCondenseContextPercent": { "label": "インテリジェントなコンテキスト圧縮をトリガーするしきい値", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 3a88c9fbde..76981b275a 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "작업 영역 외부를 가리키는 심볼릭 링크 허용", + "description": "활성화하면 작업 영역 내에 있지만 외부를 가리키는 심볼릭 링크는 외부로 표시되는 대신 작업 영역 내 위치를 기준으로 처리됩니다. 기본적으로 꺼져 있어 심볼릭 링크 대상이 확인되고 작업 영역 외부 접근이 표시됩니다." + }, "description": "AI의 컨텍스트 창에 포함되는 정보를 제어하여 token 사용량과 응답 품질에 영향을 미칩니다", "autoCondenseContextPercent": { "label": "지능적 컨텍스트 압축을 트리거하는 임계값", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index a91bb87de5..5b34367210 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Symlinks toestaan die buiten de werkruimte wijzen", + "description": "Wanneer ingeschakeld, wordt een symlink die zich in de werkruimte bevindt maar naar buiten wijst behandeld op basis van de locatie in de werkruimte in plaats van als buiten de werkruimte te worden gemarkeerd. Standaard uitgeschakeld, zodat symlinkdoelen worden opgelost en toegang buiten de werkruimte wordt gemarkeerd." + }, "description": "Bepaal welke informatie wordt opgenomen in het contextvenster van de AI, wat invloed heeft op tokengebruik en antwoordkwaliteit", "autoCondenseContextPercent": { "label": "Drempelwaarde om intelligente contextcompressie te activeren", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index f3751196d8..9eca8daaff 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Zezwalaj na dowiązania symboliczne wskazujące poza obszar roboczy", + "description": "Po włączeniu dowiązanie symboliczne znajdujące się wewnątrz obszaru roboczego, ale wskazujące na zewnątrz, jest traktowane według jego lokalizacji w obszarze roboczym zamiast być oznaczane jako zewnętrzne. Wyłączone domyślnie, dlatego cele dowiązań symbolicznych są rozwiązywane, a dostęp poza obszarem roboczym jest oznaczany." + }, "description": "Kontroluj, jakie informacje są zawarte w oknie kontekstu AI, wpływając na zużycie token i jakość odpowiedzi", "autoCondenseContextPercent": { "label": "Próg wyzwalający inteligentną kondensację kontekstu", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 05d3557f76..cb7354243e 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Permitir links simbólicos que apontam para fora do espaço de trabalho", + "description": "Quando habilitado, um link simbólico que está dentro do espaço de trabalho, mas aponta para fora, é tratado pela sua localização dentro do espaço de trabalho em vez de ser sinalizado como externo. Desativado por padrão, de modo que os destinos dos links simbólicos são resolvidos e o acesso fora do espaço de trabalho é sinalizado." + }, "description": "Controle quais informações são incluídas na janela de contexto da IA, afetando o uso de token e a qualidade da resposta", "autoCondenseContextPercent": { "label": "Limite para acionar a condensação inteligente de contexto", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 1aedb8a575..4fd09d60e0 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Разрешить символические ссылки, указывающие за пределы рабочей области", + "description": "Если включено, символическая ссылка, находящаяся в рабочей области, но указывающая за её пределы, обрабатывается по её расположению внутри рабочей области вместо того, чтобы отмечаться как находящаяся снаружи. По умолчанию отключено, поэтому целевые объекты символических ссылок разрешаются, а доступ за пределы рабочей области отмечается." + }, "description": "Управляйте, какая информация включается в окно контекста ИИ, что влияет на расход токенов и качество ответов", "autoCondenseContextPercent": { "label": "Порог для запуска интеллектуального сжатия контекста", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 22bd730488..c782659223 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Çalışma alanının dışını işaret eden sembolik bağlantılara izin ver", + "description": "Etkinleştirildiğinde, çalışma alanında bulunan ancak dışını işaret eden bir sembolik bağlantı, dışarıda olarak işaretlenmek yerine çalışma alanındaki konumuna göre işlenir. Varsayılan olarak kapalıdır, bu nedenle sembolik bağlantı hedefleri çözülür ve çalışma alanı dışına erişim işaretlenir." + }, "description": "Yapay zekanın bağlam penceresine hangi bilgilerin dahil edileceğini kontrol edin, token kullanımını ve yanıt kalitesini etkiler", "autoCondenseContextPercent": { "label": "Akıllı bağlam sıkıştırmayı tetikleyecek eşik", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 0b09ae004b..229266350e 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "Cho phép symlink trỏ ra ngoài vùng làm việc", + "description": "Khi được bật, một symlink nằm trong vùng làm việc nhưng trỏ ra ngoài sẽ được xử lý theo vị trí bên trong vùng làm việc thay vì bị đánh dấu là bên ngoài. Tắt theo mặc định, do đó đích symlink được giải quyết và truy cập ngoài vùng làm việc được đánh dấu." + }, "description": "Kiểm soát thông tin nào được đưa vào cửa sổ ngữ cảnh của AI, ảnh hưởng đến việc sử dụng token và chất lượng phản hồi", "autoCondenseContextPercent": { "label": "Ngưỡng kích hoạt nén ngữ cảnh thông minh", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index f58ea87f8c..7944571622 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -582,6 +582,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "允许指向工作区外部的符号链接", + "description": "启用后,位于工作区内但指向外部的符号链接将按其在工作区内的位置处理,而不是被标记为外部。默认关闭,因此符号链接目标会被解析,工作区外的访问会被标记。" + }, "description": "管理AI上下文信息(影响token用量和回答质量)", "autoCondenseContextPercent": { "label": "触发智能上下文压缩的阈值", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index e4268280de..28e6a46b99 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -592,6 +592,10 @@ } }, "contextManagement": { + "allowSymlinksOutsideWorkspace": { + "label": "允許指向工作區外部的符號連結", + "description": "啟用後,位於工作區內但指向外部的符號連結將按其在工作區內的位置處理,而不是被標記為外部。預設關閉,因此符號連結目標會被解析,工作區外的存取會被標記。" + }, "description": "控制 AI 上下文視窗中要包含哪些資訊,會影響 Token 用量和回應品質", "autoCondenseContextPercent": { "label": "觸發智慧上下文壓縮的閾值",