Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/core/auto-approval/__tests__/commands.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { containsDangerousSubstitution, getCommandDecision } from "../commands"

describe("containsDangerousSubstitution", () => {
describe("zsh array assignments (should NOT be flagged)", () => {
it("should return false for files=(a b c)", () => {
expect(containsDangerousSubstitution("files=(a b c)")).toBe(false)
})

it("should return false for var=(item1 item2)", () => {
expect(containsDangerousSubstitution("var=(item1 item2)")).toBe(false)
})

it("should return false for x=(hello)", () => {
expect(containsDangerousSubstitution("x=(hello)")).toBe(false)
})
})

describe("zsh process substitution (should be flagged)", () => {
it("should return true for standalone =(whoami)", () => {
expect(containsDangerousSubstitution("=(whoami)")).toBe(true)
})

it("should return true for =(ls) with leading space", () => {
expect(containsDangerousSubstitution(" =(ls)")).toBe(true)
})

it("should return true for echo =(cat /etc/passwd)", () => {
expect(containsDangerousSubstitution("echo =(cat /etc/passwd)")).toBe(true)
})
})
})

describe("getCommandDecision", () => {
it("should auto_approve array assignment command with wildcard allowlist", () => {
const command = 'files=(a.ts b.ts); for f in "${files[@]}"; do echo "$f"; done'
const result = getCommandDecision(command, ["*"])
expect(result).toBe("auto_approve")
})
})
4 changes: 2 additions & 2 deletions src/core/auto-approval/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { parseCommand } from "../../shared/parse-command"
* - ${var=value} with escape sequences - Can embed commands via \140 (backtick), \x60, or \u0060
* - ${!var} - Indirect variable references
* - <<<$(...) or <<<`...` - Here-strings with command substitution
* - =(...) - Zsh process substitution that executes commands
* - =(...) - Zsh process substitution that executes commands (array assignments like `var=(...)` are excluded)
* - *(e:...:) or similar - Zsh glob qualifiers with code execution
*
* @param source - The command string to analyze
Expand Down Expand Up @@ -46,7 +46,7 @@ export function containsDangerousSubstitution(source: string): boolean {

// Check for zsh process substitution =(...) which executes commands
// =(...) creates a temporary file containing the output of the command, but executes it
const zshProcessSubstitution = /=\([^)]+\)/.test(source)
const zshProcessSubstitution = /(?<![a-zA-Z0-9_])=\([^)]+\)/.test(source)

// Check for zsh glob qualifiers with code execution (e:...:)
// Patterns like *(e:whoami:) or ?(e:rm -rf /:) execute commands during glob expansion
Expand Down
Loading