Better handle nested backticks

This commit is contained in:
John Richmond 2025-09-29 15:32:40 -07:00
parent 9e0d925f95
commit 952d8af3e8
2 changed files with 23 additions and 3 deletions

View file

@ -43,6 +43,12 @@ describe("Command Validation", () => {
expect(parseCommand("diff <(sort f1) <(sort f2)")).toEqual(["diff", "sort f1", "sort f2"])
})
it("handles nested backticks with escaped inner backticks", () => {
const cmd = "echo `echo \\`whoami\\``"
// Should surface both the outer echo and the inner whoami as separate sub-commands
expect(parseCommand(cmd)).toEqual(["echo", "echo", "whoami"])
})
it("handles empty and whitespace input", () => {
expect(parseCommand("")).toEqual([])
expect(parseCommand(" ")).toEqual([])
@ -1075,6 +1081,11 @@ describe("Unified Command Decision Functions", () => {
// Main command with denied prefix should also be auto-denied
expect(getCommandDecision("npm test $(echo hello)", allowedCommands, deniedCommands)).toBe("auto_deny")
// Nested backticks with escaped inner backticks should not be auto-approved when inner command isn't allowed
expect(getCommandDecision("echo `echo \\`whoami\\``", ["echo"], [])).toBe("ask_user")
// And should be auto-denied when inner command is on the denylist
expect(getCommandDecision("echo `echo \\`whoami\\``", ["echo"], ["whoami"])).toBe("auto_deny")
})
it("properly validates subshell commands when no denylist is present", () => {

View file

@ -256,8 +256,10 @@ function parseCommandLine(command: string): string[] {
subshells.push(inner.trim())
return `__SUBSH_${subshells.length - 1}__`
})
.replace(/`(.*?)`/g, (_, inner) => {
subshells.push(inner.trim())
// Handle backticks with support for escaped backticks (e.g., \`)
.replace(/`((?:\\`|[^`])*)`/g, (_, inner) => {
const unescaped = inner.replace(/\\`/g, "`").trim()
subshells.push(unescaped)
return `__SUBSH_${subshells.length - 1}__`
})
@ -318,7 +320,14 @@ function parseCommandLine(command: string): string[] {
commands.push(currentCommand.join(" "))
currentCommand = []
}
commands.push(subshells[parseInt(subshellMatch[1])])
// Expand subshell into its constituent commands to catch nested substitutions
const subshellContent = subshells[parseInt(subshellMatch[1])]
const expanded = parseCommand(subshellContent)
if (expanded.length > 0) {
commands.push(...expanded)
} else if (subshellContent.trim()) {
commands.push(subshellContent.trim())
}
} else {
currentCommand.push(token)
}