Skip to content

Security: heredoc opener can bypass command auto-approval via operator chaining #640

Open

Description

@edelauna

Summary

A command like cat <<EOF && rm -rf /tmp\nhello\nEOF with an allowlist of ["cat"] is auto-approved, allowing arbitrary operators chained on the heredoc opener line to execute without user approval.

Root cause

In src/shared/parse-command.ts:519-525, when a top-level quote placeholder is restored and the resulting string contains embedded newlines (i.e. a heredoc span), the entire restored string is pushed as a single atomic command:

if (restoredLine.includes("\n")) {
    allCommands.push(restoredLine)
    continue
}

This means cat <<EOF && rm -rf /tmp\nhello\nEOF is treated as one command. In src/core/auto-approval/commands.ts:145-148, that single command is validated by prefix match:

return lowerPrefix === "*" || trimmedCommand.startsWith(lowerPrefix)

Since the string starts with cat, it matches the "cat" allowlist entry and is auto-approved — including the && rm -rf /tmp operator chain that follows the heredoc opener.

Fix

Split and validate operators on the heredoc opener line before treating the body as opaque. Only the heredoc body content (lines between the opener and the terminator) should be atomic. The opener line itself (cat <<EOF && rm -rf /tmp) should be split on &&, ||, ;, and | operators and each sub-command validated independently.

Add a regression test: cat <<EOF && rm -rf /tmp\nhello\nEOF with allowlist ["cat"] should not return auto_approve.

Discovered

Identified during code review of PR #250 (chore: re-enable prefer-const ESLint rule). The vulnerability is pre-existing and unrelated to that PR.

Activity

  1. kanchinadd98 commented on Nov 10, 2026

    @kanchinadd98
    Contributor

    This bug still occurs on main (18861b2) and after #2397.

    Command Allowlist Denylist parseCommand result Decision
    cat <<EOF && rm -rf x + body + EOF cat rm 1 command (the full text) auto_approve
    cat <<EOF; rm -rf x + body + EOF cat rm 1 command (the full text) malformed_command
    cat <<EOF + body + EOF + rm -rf x on the next line cat rm cat <<EOF…EOF, rm -rf x auto_deny

    The root cause in this issue is now in a different location. #2397 changes the multi-line branch of parseCommand: it splits the line with parseCommandLine before it restores the quotes. But the heredoc scanner in scanTopLevelQuotes masks the text from <<EOF to the terminator, which includes && rm -rf x on the opener line. So that text is inside one placeholder, and no split occurs.

    A possible fix: when the scanner finds a heredoc opener, mask only the body and the terminator line, not the rest of the opener line. Then parseCommandLine splits the opener line on &&, ||, ; and |.

    I can work on this after #2397 if a maintainer assigns it to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions