From 27ce2b5a8f8cd5679d06d03005bb07b001db2798 Mon Sep 17 00:00:00 2001 From: bunnysayzz Date: Wed, 22 Jul 2026 10:29:48 +0530 Subject: [PATCH 1/6] fix: expand destructive-command blocklist to prevent security bypasses ## Summary\n\nThe existing `isCriticalCommand` blocklist was incomplete, allowing dangerous commands to auto-execute in headless/auto mode.\n\n## Changes\n\n- **Added dangerous paths:** /home, /root, /Users/mdazharuddin, /Users/mdazharuddin to both the recursive-deletion path check and the explicit `rm` critical paths list\n- **Added find -delete:** Detects destructive find commands with -delete flag\n- **Added shred/wipefs:** File-shredding and filesystem-wiping commands now blocked\n- **Added pkexec:** Privilege escalation via PolicyKit now blocked\n- **Shell expansion:** Added /Users/mdazharuddin pattern to catch variable-expansion bypasses\n\nCloses #13001 --- .../src/evaluateTerminalCommandSecurity.ts | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts b/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts index 693eef25f9c..d763f8e3b22 100644 --- a/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts +++ b/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts @@ -428,10 +428,17 @@ function isCriticalCommand(baseCommand: string, args: string[]): boolean { arg === "/etc" || arg === "/bin" || arg === "/sbin" || + arg === "/home" || + arg === "/root" || + arg === "$HOME" || arg.startsWith("/usr/") || arg.startsWith("/etc/") || arg.startsWith("/bin/") || - arg.startsWith("/sbin/"), + arg.startsWith("/sbin/") || + arg.startsWith("/home/") || + arg.startsWith("/root/") || + arg.startsWith("$HOME") || + arg.startsWith("${HOME}"), ); // If we have rm flags with dangerous paths, it's critical regardless of command @@ -457,6 +464,10 @@ function isCriticalCommand(baseCommand: string, args: string[]): boolean { "/usr/sbin/", "/lib/", "/lib64/", + "/home/", + "/root/", + "$HOME", + "${HOME}", ]; if (args.some((arg) => criticalPaths.some((path) => arg.includes(path)))) { return true; @@ -492,6 +503,24 @@ function isCriticalCommand(baseCommand: string, args: string[]): boolean { } } + // Find -delete (destructive file deletion via find) + if (baseCommand === "find" && args.some((arg) => arg === "-delete")) { + return true; + } + + // Destructive disk/file commands + if ( + baseCommand === "shred" || + baseCommand === "wipefs" + ) { + return true; + } + + // pkexec privilege escalation + if (baseCommand === "pkexec") { + return true; + } + // Privilege escalation const privEscCommands = ["sudo", "su", "doas", "runas", "gsudo", "psexec"]; if (privEscCommands.includes(baseCommand)) { From 846a2fa01e78de5dd72b6e1dd5c34dac9636b512 Mon Sep 17 00:00:00 2001 From: bunnysayzz Date: Wed, 22 Jul 2026 10:41:20 +0530 Subject: [PATCH 2/6] fix: register viewLogs command before dynamic import to prevent startup failure ## Summary\n\nWhen extension activation fails (e.g., due to module load errors), the "View Logs" button in the error dialog does nothing because `continue.viewLogs` is registered inside the dynamically imported `activate.ts`. If the dynamic import fails, the command is never registered.\n\n## Fix\n\nRegistered `continue.viewLogs` directly in `extension.ts` before the dynamic import call, ensuring the command is always available regardless of whether the full extension activation succeeds.\n\nCloses #12946 --- extensions/vscode/src/extension.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/extensions/vscode/src/extension.ts b/extensions/vscode/src/extension.ts index 4712ebc4226..f3da5121d9a 100644 --- a/extensions/vscode/src/extension.ts +++ b/extensions/vscode/src/extension.ts @@ -14,6 +14,14 @@ async function dynamicImportAndActivate(context: vscode.ExtensionContext) { } export function activate(context: vscode.ExtensionContext) { + // Register viewLogs command early so it's available even if + // the dynamic import/activation fails. + context.subscriptions.push( + vscode.commands.registerCommand("continue.viewLogs", () => { + vscode.commands.executeCommand("workbench.action.toggleDevTools"); + }), + ); + return dynamicImportAndActivate(context).catch((e) => { console.log("Error activating extension: ", e); vscode.window From 23c9e268e6db1c15727b53eab90fc26cce3167f0 Mon Sep 17 00:00:00 2001 From: bunnysayzz Date: Sun, 6 Sep 2026 11:06:50 +0530 Subject: [PATCH 3/6] fix(cli): keep id-less Ollama tool-call deltas instead of dropping them --- .../stream/streamChatResponse.helpers.test.ts | 79 +++++++++++++++++++ .../src/stream/streamChatResponse.helpers.ts | 8 ++ 2 files changed, 87 insertions(+) create mode 100644 extensions/cli/src/stream/streamChatResponse.helpers.test.ts diff --git a/extensions/cli/src/stream/streamChatResponse.helpers.test.ts b/extensions/cli/src/stream/streamChatResponse.helpers.test.ts new file mode 100644 index 00000000000..3532a4abb06 --- /dev/null +++ b/extensions/cli/src/stream/streamChatResponse.helpers.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from "vitest"; + +import type { ToolCall } from "../tools/types.js"; + +import { processToolCallDelta } from "./streamChatResponse.helpers.js"; + +function freshMaps() { + return { + toolCallsMap: new Map(), + indexToIdMap: new Map(), + }; +} + +describe("processToolCallDelta", () => { + it("creates an entry for an id-less delta carrying only an index (Ollama)", () => { + const { toolCallsMap, indexToIdMap } = freshMaps(); + + processToolCallDelta( + { index: 0, function: { name: "read_file", arguments: '{"filepath":' } }, + toolCallsMap, + indexToIdMap, + ); + + const entry = toolCallsMap.get("call_0"); + expect(entry).toBeDefined(); + expect(entry?.name).toBe("read_file"); + expect(indexToIdMap.get(0)).toBe("call_0"); + }); + + it("accumulates later id-less fragments for the same index", () => { + const { toolCallsMap, indexToIdMap } = freshMaps(); + + processToolCallDelta( + { index: 1, function: { name: "read_file", arguments: '{"filepath":' } }, + toolCallsMap, + indexToIdMap, + ); + processToolCallDelta( + { index: 1, function: { arguments: '"convex/schema.ts"}' } }, + toolCallsMap, + indexToIdMap, + ); + + expect(toolCallsMap.size).toBe(1); + expect(toolCallsMap.get("call_1")?.arguments).toEqual({ + filepath: "convex/schema.ts", + }); + }); + + it("keeps provider-supplied IDs untouched", () => { + const { toolCallsMap, indexToIdMap } = freshMaps(); + + processToolCallDelta( + { + id: "call_abc123", + index: 0, + function: { name: "read_file", arguments: '{"filepath":"x"}' }, + }, + toolCallsMap, + indexToIdMap, + ); + + expect(toolCallsMap.get("call_abc123")?.name).toBe("read_file"); + expect(toolCallsMap.has("call_0")).toBe(false); + expect(indexToIdMap.get(0)).toBe("call_abc123"); + }); + + it("still drops deltas with neither id nor index", () => { + const { toolCallsMap, indexToIdMap } = freshMaps(); + + processToolCallDelta( + { function: { name: "x" } }, + toolCallsMap, + indexToIdMap, + ); + + expect(toolCallsMap.size).toBe(0); + }); +}); diff --git a/extensions/cli/src/stream/streamChatResponse.helpers.ts b/extensions/cli/src/stream/streamChatResponse.helpers.ts index 776af67f554..ca5ce94262c 100644 --- a/extensions/cli/src/stream/streamChatResponse.helpers.ts +++ b/extensions/cli/src/stream/streamChatResponse.helpers.ts @@ -197,6 +197,14 @@ export function processToolCallDelta( } else if (toolCallDelta.index !== undefined) { // No ID, but we have an index - look up the ID from our map toolCallId = indexToIdMap.get(toolCallDelta.index); + if (!toolCallId) { + // Some providers (e.g. Ollama) stream tool-call deltas with an index + // but no id. Generate a stable synthetic ID so the entry is created on + // the first delta and later fragments accumulate instead of being + // dropped as plain assistant text. + toolCallId = `call_${toolCallDelta.index}`; + indexToIdMap.set(toolCallDelta.index, toolCallId); + } } if (!toolCallId) { From d491993baa563573d7f2c908cc1a3728b41aedb3 Mon Sep 17 00:00:00 2001 From: Azhar Date: Tue, 29 Sep 2026 04:42:35 +0530 Subject: [PATCH 4/6] fix(terminal-security): require permission for find -delete and shred instead of disabling --- .../src/evaluateTerminalCommandSecurity.ts | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts b/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts index d763f8e3b22..3c0377da916 100644 --- a/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts +++ b/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts @@ -1,5 +1,5 @@ import { parse } from "shell-quote"; -import { ToolPolicy } from "./types.js"; +type ToolPolicy = "disabled" | "allowedWithPermission" | "allowedWithoutPermission"; /** * Token types from shell-quote @@ -503,19 +503,6 @@ function isCriticalCommand(baseCommand: string, args: string[]): boolean { } } - // Find -delete (destructive file deletion via find) - if (baseCommand === "find" && args.some((arg) => arg === "-delete")) { - return true; - } - - // Destructive disk/file commands - if ( - baseCommand === "shred" || - baseCommand === "wipefs" - ) { - return true; - } - // pkexec privilege escalation if (baseCommand === "pkexec") { return true; @@ -770,6 +757,14 @@ function isHighRiskSystemService(baseCommand: string): boolean { * Checks if command is file operation to sensitive location */ function isHighRiskFileOperation(baseCommand: string, args: string[]): boolean { + // Destructive deletion via find -delete: require permission, don't hard-block + if (baseCommand === "find" && args.some((arg) => arg === "-delete")) { + return true; + } + // Destructive disk/file commands: require permission, don't hard-block + if (baseCommand === "shred" || baseCommand === "wipefs") { + return true; + } if (baseCommand === "mv" || baseCommand === "cp" || baseCommand === "copy") { const sensitiveLocations = [ "/etc/", From 7a7b6c3eebea5f54264f8af2639264ecd6b3d792 Mon Sep 17 00:00:00 2001 From: bunnysayzz Date: Tue, 29 Sep 2026 10:12:27 +0530 Subject: [PATCH 5/6] style: wrap ToolPolicy type alias for prettier --- .../terminal-security/src/evaluateTerminalCommandSecurity.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts b/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts index 3c0377da916..bc955f0329d 100644 --- a/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts +++ b/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts @@ -1,5 +1,6 @@ import { parse } from "shell-quote"; -type ToolPolicy = "disabled" | "allowedWithPermission" | "allowedWithoutPermission"; +type ToolPolicy = + "disabled" | "allowedWithPermission" | "allowedWithoutPermission"; /** * Token types from shell-quote From 495a9fb193ccec60435f573c3963d17d283c4b68 Mon Sep 17 00:00:00 2001 From: bunnysayzz Date: Sat, 10 Oct 2026 14:12:15 +0530 Subject: [PATCH 6/6] style: fix ToolPolicy type alias formatting for prettier --- .../terminal-security/src/evaluateTerminalCommandSecurity.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts b/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts index bc955f0329d..ff0bf03a13b 100644 --- a/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts +++ b/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts @@ -1,6 +1,8 @@ import { parse } from "shell-quote"; type ToolPolicy = - "disabled" | "allowedWithPermission" | "allowedWithoutPermission"; + | "disabled" + | "allowedWithPermission" + | "allowedWithoutPermission"; /** * Token types from shell-quote