From 27ce2b5a8f8cd5679d06d03005bb07b001db2798 Mon Sep 17 00:00:00 2001 From: bunnysayzz Date: Wed, 22 Jul 2026 10:29:48 +0530 Subject: [PATCH 1/3] 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/3] 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/3] 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) {