From 9d434c2db9b20eb5c78b698cb2b0037cd2074534 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 23 Jul 2025 11:21:06 -0400 Subject: [PATCH 1/3] Split commands on newlines (#6121) --- .../__tests__/command-validation.spec.ts | 115 ++++++++++++++++++ webview-ui/src/utils/command-validation.ts | 26 +++- 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/webview-ui/src/utils/__tests__/command-validation.spec.ts b/webview-ui/src/utils/__tests__/command-validation.spec.ts index 661d09c802..cf19f11bfc 100644 --- a/webview-ui/src/utils/__tests__/command-validation.spec.ts +++ b/webview-ui/src/utils/__tests__/command-validation.spec.ts @@ -50,6 +50,121 @@ describe("Command Validation", () => { parseCommand('npm test | Select-String -NotMatch "node_modules" | Select-String "FAIL|Error"'), ).toEqual(["npm test", 'Select-String -NotMatch "node_modules"', 'Select-String "FAIL|Error"']) }) + + describe("newline handling", () => { + it("splits commands by Unix newlines (\\n)", () => { + expect(parseCommand("echo hello\ngit status\nnpm install")).toEqual([ + "echo hello", + "git status", + "npm install", + ]) + }) + + it("splits commands by Windows newlines (\\r\\n)", () => { + expect(parseCommand("echo hello\r\ngit status\r\nnpm install")).toEqual([ + "echo hello", + "git status", + "npm install", + ]) + }) + + it("splits commands by old Mac newlines (\\r)", () => { + expect(parseCommand("echo hello\rgit status\rnpm install")).toEqual([ + "echo hello", + "git status", + "npm install", + ]) + }) + + it("handles mixed line endings", () => { + expect(parseCommand("echo hello\ngit status\r\nnpm install\rls -la")).toEqual([ + "echo hello", + "git status", + "npm install", + "ls -la", + ]) + }) + + it("ignores empty lines", () => { + expect(parseCommand("echo hello\n\n\ngit status\r\n\r\nnpm install")).toEqual([ + "echo hello", + "git status", + "npm install", + ]) + }) + + it("handles newlines with chain operators", () => { + expect(parseCommand('npm install && npm test\ngit add .\ngit commit -m "test"')).toEqual([ + "npm install", + "npm test", + "git add .", + 'git commit -m "test"', + ]) + }) + + it("splits on actual newlines even within quotes", () => { + // Note: Since we split by newlines first, actual newlines in the input + // will split the command, even if they appear to be within quotes + // Using template literal to create actual newline + const commandWithNewlineInQuotes = `echo "Hello +World" +git status` + // The quotes get stripped because they're no longer properly paired after splitting + expect(parseCommand(commandWithNewlineInQuotes)).toEqual(["echo Hello", "World", "git status"]) + }) + + it("handles quoted strings on single line", () => { + // When quotes are on the same line, they are preserved + expect(parseCommand('echo "Hello World"\ngit status')).toEqual(['echo "Hello World"', "git status"]) + }) + + it("handles complex multi-line commands", () => { + const multiLineCommand = `npm install +npm test && npm run build +echo "Done" | tee output.log +git status; git add . +ls -la || echo "Failed"` + + expect(parseCommand(multiLineCommand)).toEqual([ + "npm install", + "npm test", + "npm run build", + 'echo "Done"', + "tee output.log", + "git status", + "git add .", + "ls -la", + 'echo "Failed"', + ]) + }) + + it("handles newlines with subshells", () => { + expect(parseCommand("echo $(date)\nnpm test\ngit status")).toEqual([ + "echo", + "date", + "npm test", + "git status", + ]) + }) + + it("handles newlines with redirections", () => { + expect(parseCommand("npm test 2>&1\necho done\nls -la > files.txt")).toEqual([ + "npm test 2>&1", + "echo done", + "ls -la > files.txt", + ]) + }) + + it("handles empty input with newlines", () => { + expect(parseCommand("\n\n\n")).toEqual([]) + expect(parseCommand("\r\n\r\n")).toEqual([]) + expect(parseCommand("\r\r\r")).toEqual([]) + }) + + it("handles whitespace-only lines", () => { + expect(parseCommand("echo hello\n \t \ngit status")).toEqual(["echo hello", "git status"]) + }) + }) }) describe("isAutoApprovedSingleCommand (legacy behavior)", () => { diff --git a/webview-ui/src/utils/command-validation.ts b/webview-ui/src/utils/command-validation.ts index 1dbc722943..69d07beb9b 100644 --- a/webview-ui/src/utils/command-validation.ts +++ b/webview-ui/src/utils/command-validation.ts @@ -60,17 +60,41 @@ type ShellToken = string | { op: string } | { command: string } /** * Split a command string into individual sub-commands by - * chaining operators (&&, ||, ;, or |). + * chaining operators (&&, ||, ;, or |) and newlines. * * Uses shell-quote to properly handle: * - Quoted strings (preserves quotes) * - Subshell commands ($(cmd) or `cmd`) * - PowerShell redirections (2>&1) * - Chain operators (&&, ||, ;, |) + * - Newlines as command separators */ export function parseCommand(command: string): string[] { if (!command?.trim()) return [] + // Split by newlines first (handle different line ending formats) + // This regex splits on \r\n (Windows), \n (Unix), or \r (old Mac) + const lines = command.split(/\r\n|\r|\n/) + const allCommands: string[] = [] + + for (const line of lines) { + // Skip empty lines + if (!line.trim()) continue + + // Process each line through the existing parsing logic + const lineCommands = parseCommandLine(line) + allCommands.push(...lineCommands) + } + + return allCommands +} + +/** + * Parse a single line of commands (internal helper function) + */ +function parseCommandLine(command: string): string[] { + if (!command?.trim()) return [] + // Storage for replaced content const redirections: string[] = [] const subshells: string[] = [] From 4042fb0fd0a576e9122b1d77372812a913a97b0b Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 23 Jul 2025 12:02:31 -0400 Subject: [PATCH 2/3] Smarter auto-deny (#6123) --- .../__tests__/command-validation.spec.ts | 295 ++++-------------- webview-ui/src/utils/command-validation.ts | 57 +--- 2 files changed, 61 insertions(+), 291 deletions(-) diff --git a/webview-ui/src/utils/__tests__/command-validation.spec.ts b/webview-ui/src/utils/__tests__/command-validation.spec.ts index cf19f11bfc..63a460ffaf 100644 --- a/webview-ui/src/utils/__tests__/command-validation.spec.ts +++ b/webview-ui/src/utils/__tests__/command-validation.spec.ts @@ -6,8 +6,6 @@ import { parseCommand, isAutoApprovedSingleCommand, isAutoDeniedSingleCommand, - isAutoApprovedCommand, - isAutoDeniedCommand, findLongestPrefixMatch, getCommandDecision, getSingleCommandDecision, @@ -167,7 +165,7 @@ ls -la || echo "Failed"` }) }) - describe("isAutoApprovedSingleCommand (legacy behavior)", () => { + describe("isAutoApprovedSingleCommand", () => { const allowedCommands = ["npm test", "npm run", "echo"] it("matches commands case-insensitively", () => { @@ -193,93 +191,6 @@ ls -la || echo "Failed"` expect(isAutoApprovedSingleCommand("npm test", [])).toBe(false) }) }) - - describe("isAutoApprovedCommand (legacy behavior)", () => { - const allowedCommands = ["npm test", "npm run", "echo", "Select-String"] - - it("validates simple commands", () => { - expect(isAutoApprovedCommand("npm test", allowedCommands)).toBe(true) - expect(isAutoApprovedCommand("npm run build", allowedCommands)).toBe(true) - expect(isAutoApprovedCommand("dangerous", allowedCommands)).toBe(false) - }) - - it("validates chained commands", () => { - expect(isAutoApprovedCommand("npm test && npm run build", allowedCommands)).toBe(true) - expect(isAutoApprovedCommand("npm test && dangerous", allowedCommands)).toBe(false) - expect(isAutoApprovedCommand('npm test | Select-String "Error"', allowedCommands)).toBe(true) - expect(isAutoApprovedCommand("npm test | rm -rf /", allowedCommands)).toBe(false) - }) - - it("handles quoted content correctly", () => { - expect(isAutoApprovedCommand('npm test "param with | inside"', allowedCommands)).toBe(true) - expect(isAutoApprovedCommand('echo "hello | world"', allowedCommands)).toBe(true) - expect(isAutoApprovedCommand('npm test "param with && inside"', allowedCommands)).toBe(true) - }) - - it("handles subshell execution attempts", () => { - // Without denylist, subshells should be allowed if all subcommands are allowed - expect(isAutoApprovedCommand("npm test $(echo hello)", allowedCommands)).toBe(true) - expect(isAutoApprovedCommand("npm test `echo world`", allowedCommands)).toBe(true) - - // With denylist, subshells should be blocked regardless of subcommands - expect(isAutoApprovedCommand("npm test $(echo hello)", allowedCommands, ["rm"])).toBe(false) - expect(isAutoApprovedCommand("npm test `echo world`", allowedCommands, ["rm"])).toBe(false) - }) - - it("handles PowerShell patterns", () => { - expect(isAutoApprovedCommand('npm test 2>&1 | Select-String "Error"', allowedCommands)).toBe(true) - expect( - isAutoApprovedCommand( - 'npm test | Select-String -NotMatch "node_modules" | Select-String "FAIL|Error"', - allowedCommands, - ), - ).toBe(true) - expect(isAutoApprovedCommand("npm test | Select-String | dangerous", allowedCommands)).toBe(false) - }) - - it("handles empty input", () => { - expect(isAutoApprovedCommand("", allowedCommands)).toBe(true) - expect(isAutoApprovedCommand(" ", allowedCommands)).toBe(true) - }) - - it("allows all commands when wildcard is present", () => { - const wildcardAllowedCommands = ["*"] - // Should allow any command, including dangerous ones - expect(isAutoApprovedCommand("rm -rf /", wildcardAllowedCommands)).toBe(true) - expect(isAutoApprovedCommand("dangerous-command", wildcardAllowedCommands)).toBe(true) - expect(isAutoApprovedCommand("npm test && rm -rf /", wildcardAllowedCommands)).toBe(true) - // Should allow subshell commands with wildcard when no denylist is present - expect(isAutoApprovedCommand("npm test $(echo dangerous)", wildcardAllowedCommands)).toBe(true) - expect(isAutoApprovedCommand("npm test `rm -rf /`", wildcardAllowedCommands)).toBe(true) - - // But should block subshells when denylist is present - expect(isAutoApprovedCommand("npm test $(echo dangerous)", wildcardAllowedCommands, ["rm"])).toBe(false) - expect(isAutoApprovedCommand("npm test `rm -rf /`", wildcardAllowedCommands, ["rm"])).toBe(false) - }) - - it("respects denylist even with wildcard in allowlist", () => { - const wildcardAllowedCommands = ["*"] - const deniedCommands = ["rm -rf", "dangerous"] - - // Wildcard should allow most commands - expect(isAutoApprovedCommand("npm test", wildcardAllowedCommands, deniedCommands)).toBe(true) - expect(isAutoApprovedCommand("echo hello", wildcardAllowedCommands, deniedCommands)).toBe(true) - expect(isAutoApprovedCommand("git status", wildcardAllowedCommands, deniedCommands)).toBe(true) - - // But denylist should still block specific commands - expect(isAutoApprovedCommand("rm -rf /", wildcardAllowedCommands, deniedCommands)).toBe(false) - expect(isAutoApprovedCommand("dangerous-command", wildcardAllowedCommands, deniedCommands)).toBe(false) - - // Chained commands with denied subcommands should be blocked - expect(isAutoApprovedCommand("npm test && rm -rf /", wildcardAllowedCommands, deniedCommands)).toBe(false) - expect( - isAutoApprovedCommand("echo hello && dangerous-command", wildcardAllowedCommands, deniedCommands), - ).toBe(false) - - // But chained commands with all allowed subcommands should work - expect(isAutoApprovedCommand("npm test && echo done", wildcardAllowedCommands, deniedCommands)).toBe(true) - }) - }) }) /** @@ -510,52 +421,6 @@ echo "Successfully converted $count .jsx files to .tsx"` }) }) - describe("isAutoApprovedCommand (legacy behavior)", () => { - it("should validate allowed commands", () => { - const result = isAutoApprovedCommand("echo hello", ["echo"]) - expect(result).toBe(true) - }) - - it("should reject disallowed commands", () => { - const result = isAutoApprovedCommand("rm -rf /", ["echo", "ls"]) - expect(result).toBe(false) - }) - - it("should not fail validation for commands with simple $RANDOM variable", () => { - const commandWithRandom = "echo $RANDOM" - - expect(() => { - isAutoApprovedCommand(commandWithRandom, ["echo"]) - }).not.toThrow() - }) - - it("should not fail validation for commands with simple array indexing using $RANDOM", () => { - const commandWithRandomIndex = "echo ${array[$RANDOM]}" - - expect(() => { - isAutoApprovedCommand(commandWithRandomIndex, ["echo"]) - }).not.toThrow() - }) - - it("should return false for the full log generator command due to subshell detection when denylist is present", () => { - // This is the exact command from the original error message - const logGeneratorCommand = `while true; do \\ - levels=(INFO WARN ERROR DEBUG); \\ - msgs=("User logged in" "Connection timeout" "Processing request" "Cache miss" "Database query"); \\ - level=\${levels[$RANDOM % \${#levels[@]}]}; \\ - msg=\${msgs[$RANDOM % \${#msgs[@]}]}; \\ - echo "\$(date '+%Y-%m-%d %H:%M:%S') [$level] $msg"; \\ - sleep 1; \\ -done` - - // Without denylist, should allow subshells if all subcommands are allowed (use wildcard) - expect(isAutoApprovedCommand(logGeneratorCommand, ["*"])).toBe(true) - - // With denylist, should return false due to subshell detection - expect(isAutoApprovedCommand(logGeneratorCommand, ["*"], ["rm"])).toBe(false) - }) - }) - describe("Denylist Command Validation", () => { describe("findLongestPrefixMatch", () => { it("finds the longest matching prefix", () => { @@ -584,7 +449,7 @@ done` }) }) - describe("Legacy isAllowedSingleCommand behavior (now using isAutoApprovedSingleCommand)", () => { + describe("isAutoApprovedSingleCommand", () => { const allowedCommands = ["npm", "echo", "git"] const deniedCommands = ["npm test", "git push"] @@ -761,71 +626,6 @@ done` }) }) }) - - describe("Command-level three-tier validation", () => { - const allowedCommands = ["npm", "echo"] - const deniedCommands = ["npm test"] - - describe("isAutoApprovedCommand", () => { - it("auto-approves commands with all sub-commands auto-approved", () => { - expect(isAutoApprovedCommand("npm install", allowedCommands, deniedCommands)).toBe(true) - expect(isAutoApprovedCommand("npm install && echo done", allowedCommands, deniedCommands)).toBe( - true, - ) - }) - - it("does not auto-approve commands with any sub-command not auto-approved", () => { - expect(isAutoApprovedCommand("npm test", allowedCommands, deniedCommands)).toBe(false) - expect(isAutoApprovedCommand("npm install && npm test", allowedCommands, deniedCommands)).toBe( - false, - ) - }) - - it("blocks subshell commands only when denylist is present", () => { - // Without denylist, should allow subshells - expect(isAutoApprovedCommand("npm install $(echo test)", allowedCommands)).toBe(true) - expect(isAutoApprovedCommand("npm install `echo test`", allowedCommands)).toBe(true) - - // With denylist, should block subshells - expect(isAutoApprovedCommand("npm install $(echo test)", allowedCommands, deniedCommands)).toBe( - false, - ) - expect(isAutoApprovedCommand("npm install `echo test`", allowedCommands, deniedCommands)).toBe( - false, - ) - }) - }) - - describe("isAutoDeniedCommand", () => { - it("auto-denies commands with any sub-command auto-denied", () => { - expect(isAutoDeniedCommand("npm test", allowedCommands, deniedCommands)).toBe(true) - expect(isAutoDeniedCommand("npm install && npm test", allowedCommands, deniedCommands)).toBe( - true, - ) - }) - - it("does not auto-deny commands with all sub-commands not auto-denied", () => { - expect(isAutoDeniedCommand("npm install", allowedCommands, deniedCommands)).toBe(false) - expect(isAutoDeniedCommand("npm install && echo done", allowedCommands, deniedCommands)).toBe( - false, - ) - }) - - it("auto-denies subshell commands only when denylist is present", () => { - // Without denylist, should not auto-deny subshells - expect(isAutoDeniedCommand("npm install $(echo test)", allowedCommands)).toBe(false) - expect(isAutoDeniedCommand("npm install `echo test`", allowedCommands)).toBe(false) - - // With denylist, should auto-deny subshells - expect(isAutoDeniedCommand("npm install $(echo test)", allowedCommands, deniedCommands)).toBe( - true, - ) - expect(isAutoDeniedCommand("npm install `echo test`", allowedCommands, deniedCommands)).toBe( - true, - ) - }) - }) - }) }) }) }) @@ -912,9 +712,19 @@ describe("Unified Command Decision Functions", () => { expect(getCommandDecision("npm install && dangerous", allowedCommands, deniedCommands)).toBe("ask_user") }) - it("returns auto_deny for subshell commands when denylist is present", () => { - expect(getCommandDecision("npm install $(echo test)", allowedCommands, deniedCommands)).toBe("auto_deny") - expect(getCommandDecision("npm install `echo test`", allowedCommands, deniedCommands)).toBe("auto_deny") + it("returns auto_deny for subshell commands only when they contain denied prefixes", () => { + // Subshells without denied prefixes should not be auto-denied + expect(getCommandDecision("npm install $(echo test)", allowedCommands, deniedCommands)).toBe("auto_approve") + expect(getCommandDecision("npm install `echo test`", allowedCommands, deniedCommands)).toBe("auto_approve") + + // Subshells with denied prefixes should be auto-denied + expect(getCommandDecision("npm install $(npm test)", allowedCommands, deniedCommands)).toBe("auto_deny") + expect(getCommandDecision("npm install `npm test --coverage`", allowedCommands, deniedCommands)).toBe( + "auto_deny", + ) + + // Main command with denied prefix should also be auto-denied + expect(getCommandDecision("npm test $(echo hello)", allowedCommands, deniedCommands)).toBe("auto_deny") }) it("allows subshell commands when no denylist is present", () => { @@ -961,39 +771,6 @@ describe("Unified Command Decision Functions", () => { }) }) - describe("Integration with existing functions", () => { - it("maintains backward compatibility with existing behavior", () => { - const allowedCommands = ["npm", "echo"] - const deniedCommands = ["npm test"] - - // Test that new unified functions produce same results as old separate functions - const testCommands = [ - "npm install", // should be auto-approved - "npm test", // should be auto-denied - "dangerous", // should ask user - "echo hello", // should be auto-approved - ] - - testCommands.forEach((cmd) => { - const decision = getCommandDecision(cmd, allowedCommands, deniedCommands) - const oldApproved = isAutoApprovedCommand(cmd, allowedCommands, deniedCommands) - const oldDenied = isAutoDeniedCommand(cmd, allowedCommands, deniedCommands) - - // Verify consistency - if (decision === "auto_approve") { - expect(oldApproved).toBe(true) - expect(oldDenied).toBe(false) - } else if (decision === "auto_deny") { - expect(oldApproved).toBe(false) - expect(oldDenied).toBe(true) - } else if (decision === "ask_user") { - expect(oldApproved).toBe(false) - expect(oldDenied).toBe(false) - } - }) - }) - }) - describe("CommandValidator Integration Tests", () => { describe("CommandValidator Class", () => { let validator: CommandValidator @@ -1067,7 +844,12 @@ describe("Unified Command Decision Functions", () => { it("detects subshells correctly", () => { const details = validator.getValidationDetails("npm install $(echo test)") expect(details.hasSubshells).toBe(true) - expect(details.decision).toBe("auto_deny") // blocked due to subshells with denylist + expect(details.decision).toBe("auto_approve") // not blocked since echo doesn't match denied prefixes + + // Test with denied prefix in subshell + const detailsWithDenied = validator.getValidationDetails("npm install $(npm test)") + expect(detailsWithDenied.hasSubshells).toBe(true) + expect(detailsWithDenied.decision).toBe("auto_deny") // blocked due to npm test in subshell }) it("handles complex command chains", () => { @@ -1162,6 +944,41 @@ describe("Unified Command Decision Functions", () => { }) }) + describe("Subshell edge cases", () => { + it("handles multiple subshells correctly", () => { + const validator = createCommandValidator(["echo", "npm"], ["rm", "sudo"]) + + // Multiple subshells, none with denied prefixes but subshell commands not in allowlist + // parseCommand extracts subshells as separate commands, so date and pwd are not allowed + expect(validator.validateCommand("echo $(date) $(pwd)")).toBe("ask_user") + + // Multiple subshells, one with denied prefix + expect(validator.validateCommand("echo $(date) $(rm file)")).toBe("auto_deny") + + // Nested subshells - inner commands are extracted and not in allowlist + expect(validator.validateCommand("echo $(echo $(date))")).toBe("ask_user") + expect(validator.validateCommand("echo $(echo $(rm file))")).toBe("auto_deny") + }) + + it("handles complex commands with subshells", () => { + const validator = createCommandValidator(["npm", "git", "echo"], ["git push", "npm publish"]) + + // Subshell with allowed command - git status is extracted as separate command + // Since "git status" starts with "git" which is allowed, it's approved + expect(validator.validateCommand("npm run $(git status)")).toBe("auto_approve") + + // Subshell with denied command + expect(validator.validateCommand("npm run $(git push origin)")).toBe("auto_deny") + + // Main command denied, subshell allowed + expect(validator.validateCommand("git push $(echo origin)")).toBe("auto_deny") + + // Complex chain with subshells - need echo in allowlist + expect(validator.validateCommand("npm install && echo $(git status) && npm test")).toBe("auto_approve") + expect(validator.validateCommand("npm install && echo $(git push) && npm test")).toBe("auto_deny") + }) + }) + describe("Real-world integration scenarios", () => { describe("Development workflow validation", () => { let devValidator: CommandValidator diff --git a/webview-ui/src/utils/command-validation.ts b/webview-ui/src/utils/command-validation.ts index 69d07beb9b..b403d41d8c 100644 --- a/webview-ui/src/utils/command-validation.ts +++ b/webview-ui/src/utils/command-validation.ts @@ -375,56 +375,6 @@ export function isAutoDeniedSingleCommand( return longestDeniedMatch.length >= longestAllowedMatch.length } -/** - * Check if a command string should be auto-approved. - * Only blocks subshell attempts if there's a denylist configured. - * Requires all sub-commands to be auto-approved. - */ -export function isAutoApprovedCommand(command: string, allowedCommands: string[], deniedCommands?: string[]): boolean { - if (!command?.trim()) return true - - // Only block subshell execution attempts if there's a denylist configured - if ((command.includes("$(") || command.includes("`")) && deniedCommands?.length) { - return false - } - - // Parse into sub-commands (split by &&, ||, ;, |) - const subCommands = parseCommand(command) - - // Ensure every sub-command is auto-approved - return subCommands.every((cmd) => { - // Remove simple PowerShell-like redirections (e.g. 2>&1) before checking - const cmdWithoutRedirection = cmd.replace(/\d*>&\d*/, "").trim() - - return isAutoApprovedSingleCommand(cmdWithoutRedirection, allowedCommands, deniedCommands) - }) -} - -/** - * Check if a command string should be auto-denied. - * Only blocks subshell attempts if there's a denylist configured. - * Auto-denies if any sub-command is auto-denied. - */ -export function isAutoDeniedCommand(command: string, allowedCommands: string[], deniedCommands?: string[]): boolean { - if (!command?.trim()) return false - - // Only block subshell execution attempts if there's a denylist configured - if ((command.includes("$(") || command.includes("`")) && deniedCommands?.length) { - return true - } - - // Parse into sub-commands (split by &&, ||, ;, |) - const subCommands = parseCommand(command) - - // Auto-deny if any sub-command is auto-denied - return subCommands.some((cmd) => { - // Remove simple PowerShell-like redirections (e.g. 2>&1) before checking - const cmdWithoutRedirection = cmd.replace(/\d*>&\d*/, "").trim() - - return isAutoDeniedSingleCommand(cmdWithoutRedirection, allowedCommands, deniedCommands) - }) -} - /** * Command approval decision types */ @@ -480,9 +430,12 @@ export function getCommandDecision( ): CommandDecision { if (!command?.trim()) return "auto_approve" - // Only block subshell execution attempts if there's a denylist configured + // Check if subshells contain denied prefixes if ((command.includes("$(") || command.includes("`")) && deniedCommands?.length) { - return "auto_deny" + const mainCommandLower = command.toLowerCase() + if (deniedCommands.some((denied) => mainCommandLower.includes(denied.toLowerCase()))) { + return "auto_deny" + } } // Parse into sub-commands (split by &&, ||, ;, |) From 4f8c9688a2ed35ae2062acdb20dff20525119520 Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Wed, 23 Jul 2025 18:28:30 +0100 Subject: [PATCH 3/3] Roo Code Cloud Waitlist CTAs (#6104) Co-authored-by: Roo Code Co-authored-by: Bruno Bergher --- apps/web-roo-code/next.config.ts | 6 + .../src/components/chromes/nav-bar.tsx | 37 +++- webview-ui/src/components/chat/ChatView.tsx | 19 +- .../chat/__tests__/ChatView.spec.tsx | 197 ++++++++++++++++++ .../src/components/welcome/RooCloudCTA.tsx | 23 ++ webview-ui/src/components/welcome/RooTips.tsx | 77 ++----- .../welcome/__tests__/RooTips.spec.tsx | 16 +- webview-ui/src/i18n/locales/ca/chat.json | 5 + webview-ui/src/i18n/locales/de/chat.json | 5 + webview-ui/src/i18n/locales/en/chat.json | 5 + webview-ui/src/i18n/locales/es/chat.json | 5 + webview-ui/src/i18n/locales/fr/chat.json | 5 + webview-ui/src/i18n/locales/hi/chat.json | 5 + webview-ui/src/i18n/locales/id/chat.json | 5 + webview-ui/src/i18n/locales/it/chat.json | 5 + webview-ui/src/i18n/locales/ja/chat.json | 5 + webview-ui/src/i18n/locales/ko/chat.json | 5 + webview-ui/src/i18n/locales/nl/chat.json | 5 + webview-ui/src/i18n/locales/pl/chat.json | 5 + webview-ui/src/i18n/locales/pt-BR/chat.json | 5 + webview-ui/src/i18n/locales/ru/chat.json | 5 + webview-ui/src/i18n/locales/tr/chat.json | 5 + webview-ui/src/i18n/locales/vi/chat.json | 5 + webview-ui/src/i18n/locales/zh-CN/chat.json | 5 + webview-ui/src/i18n/locales/zh-TW/chat.json | 5 + 25 files changed, 380 insertions(+), 85 deletions(-) create mode 100644 webview-ui/src/components/welcome/RooCloudCTA.tsx diff --git a/apps/web-roo-code/next.config.ts b/apps/web-roo-code/next.config.ts index 27f71d6b57..2bc1736483 100644 --- a/apps/web-roo-code/next.config.ts +++ b/apps/web-roo-code/next.config.ts @@ -21,6 +21,12 @@ const nextConfig: NextConfig = { destination: "https://roocode.com/:path*", permanent: true, }, + // Redirect cloud waitlist to Notion page + { + source: "/cloud-waitlist", + destination: "https://shard-dogwood-daf.notion.site/238fd1401b0a8087b858e1ad431507cf?pvs=105", + permanent: false, + }, ] }, } diff --git a/apps/web-roo-code/src/components/chromes/nav-bar.tsx b/apps/web-roo-code/src/components/chromes/nav-bar.tsx index c9b1df9f2b..336c6236e1 100644 --- a/apps/web-roo-code/src/components/chromes/nav-bar.tsx +++ b/apps/web-roo-code/src/components/chromes/nav-bar.tsx @@ -61,18 +61,11 @@ export function NavBar({ stars, downloads }: NavBarProps) { className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground"> Enterprise - - Security - - Documentation + Docs Careers +
+
+ + Roo Code Cloud is coming + + + Sign up + +
+
@@ -119,6 +125,19 @@ export function NavBar({ stars, downloads }: NavBarProps) {