Skip to content

Fix/command injection shell#24170

Open
Famous077 wants to merge 22 commits intogoogle-gemini:mainfrom
Famous077:fix/command-injection-shell
Open

Fix/command injection shell#24170
Famous077 wants to merge 22 commits intogoogle-gemini:mainfrom
Famous077:fix/command-injection-shell

Conversation

@Famous077
Copy link
Copy Markdown
Contributor

Summary

Fixes a command injection vulnerability in run_shell_command where shell
substitution syntax ($(), backticks, <()) in command arguments was being
executed by the shell instead of treated as literal strings.

Details

Added detectCommandSubstitution() in shell-utils.ts that parses the command
string respecting bash quoting rules:

  • Single quotes: everything literal, no substitution possible
  • Double quotes: blocks $() and backticks unless escaped
  • No quotes: blocks $(), backticks, and <() process substitution

If substitution is detected, the command is blocked and a safe error message
is returned instead of executing the injected command.

Related Issues

Fixes #14926

How to Validate

  1. Run the shell tool with a command containing $():
    echo $(whoami)
    Expected:Command is blocked with injection warning.

  2. Run a normal command without substitution:
    echo hello
    Expected: Command executes normally.

  3. Run tests:
    npx vitest run packages/core/src/tools/shell.test.ts
    Expected: 44/44 tests pass.

Pre-Merge Checklist

  • Added/updated tests (4 new tests in shell.test.ts)
  • Updated relevant documentation and README (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
    • Windows
      • npm run
      • npx
    • Linux
      • npm run
      • npx

@Famous077 Famous077 requested review from a team as code owners March 29, 2026 21:53
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request primarily addresses a critical security vulnerability regarding command injection in the shell tool, while also introducing accessibility enhancements for the CLI interface. The security fix ensures that shell substitution syntax is properly parsed and blocked when unsafe, while the UI updates improve the experience for users relying on screen readers.

Highlights

  • Security Fix: Implemented detectCommandSubstitution in shell-utils.ts to identify and block shell command injection attempts using $(), backticks, and <() syntax.
  • Shell Tool Integration: Updated ShellToolInvocation to utilize the new detection logic, preventing unsafe commands from executing.
  • Accessibility Improvements: Added screen reader support to RewindConfirmation and RewindViewer components using Ink's useIsScreenReaderEnabled hook.
  • Testing: Added comprehensive unit tests for command injection detection and accessibility features in shell.test.ts and RewindViewer.test.tsx.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-cli gemini-cli bot added priority/p1 Important and should be addressed in the near term. area/security Issues related to security help wanted We will accept PRs from all issues marked as "help wanted". Thanks for your support! labels Mar 29, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces accessibility enhancements for the Rewind UI and implements security measures to prevent command injection in shell tools. The UI components now offer optimized layouts for screen readers, while the core logic includes a new utility to detect and block shell command substitution. Review feedback identifies a security bypass regarding process substitution and recommends additional test cases to handle escaped characters and quoted strings within the detection logic.

return true;
}

if (char === '<' && command[i + 1] === '(') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

The detectCommandSubstitution function has a critical vulnerability. It fails to detect process substitution using the >(...) syntax, which can be exploited for command injection (e.g., echo hello > >(whoami)). This allows an attacker to bypass the security check. The provided code suggestion updates the condition to also detect and block the >(...) syntax, alongside the existing <(...) check.

Additionally, the current implementation has issues that can lead to false positives, such as incorrect escape handling for \$(...) outside double quotes and incorrectly flagging <(...) inside double quotes. While the immediate fix addresses the >(...) bypass, a more comprehensive review of the detectCommandSubstitution logic is recommended to universally handle shell escapes and ensure process substitution is only checked when not inside quotes, to improve accuracy and prevent further bypasses or usability issues. Please ensure that any changes to escape handling do not result in the sanitization of ANSI escape sequences, as preserving them is the desired behavior.

Suggested change
if (char === '<' && command[i + 1] === '(') {
if ((char === '<' || char === '>') && command[i + 1] === '(') {
References
  1. Do not sanitize command names for ANSI escape sequences or other C0 control characters, as the current behavior is desired.

Comment on lines +842 to +900
describe('command injection detection', () => {
it('should block $() command substitution', async () => {
const tool = new ShellTool(mockConfig, createMockMessageBus());
const invocation = tool.build({ command: 'echo $(whoami)' });
const result = await invocation.execute(new AbortController().signal);
expect(result.returnDisplay).toContain('Blocked');
});

it('should block backtick command substitution', async () => {
const tool = new ShellTool(mockConfig, createMockMessageBus());
const invocation = tool.build({ command: 'echo `whoami`' });
const result = await invocation.execute(new AbortController().signal);
expect(result.returnDisplay).toContain('Blocked');
});

it('should allow normal commands without substitution', async () => {
mockShellExecutionService.mockImplementation((_cmd, _cwd, _callback) => ({
pid: 12345,
result: Promise.resolve({
output: 'hello',
rawOutput: Buffer.from('hello'),
exitCode: 0,
signal: null,
error: null,
aborted: false,
pid: 12345,
executionMethod: 'child_process',
backgrounded: false,
}),
}));
const tool = new ShellTool(mockConfig, createMockMessageBus());
const invocation = tool.build({ command: 'echo hello' });
const result = await invocation.execute(new AbortController().signal);
expect(result.returnDisplay).not.toContain('Blocked');
});

it('should allow single quoted strings with special chars', async () => {
mockShellExecutionService.mockImplementation((_cmd, _cwd, _callback) => ({
pid: 12345,
result: Promise.resolve({
output: '$(not substituted)',
rawOutput: Buffer.from('$(not substituted)'),
exitCode: 0,
signal: null,
error: null,
aborted: false,
pid: 12345,
executionMethod: 'child_process',
backgrounded: false,
}),
}));
const tool = new ShellTool(mockConfig, createMockMessageBus());
const invocation = tool.build({
command: "echo '$(not substituted)'",
});
const result = await invocation.execute(new AbortController().signal);
expect(result.returnDisplay).not.toContain('Blocked');
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The new tests for command injection are a great start, but they could be more comprehensive to ensure the detection logic is robust. Please consider adding tests for these edge cases:

  • Escaped substitution outside quotes: A command like echo \$(whoami) should be allowed, as the backslash escapes the substitution.
  • Process substitution inside double quotes: A command like echo "<(whoami)" should be allowed, as process substitution is not active inside double quotes.
  • Process substitution without quotes: A command like echo <(whoami) should be blocked.
  • Escaped substitution inside double quotes: A command like echo "\$(whoami)" should be allowed.

Adding these tests will help verify the correctness of the detectCommandSubstitution function and prevent both false positives and potential bypasses.

@Famous077
Copy link
Copy Markdown
Contributor Author

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a command injection detection mechanism in the ShellTool to prevent the execution of shell commands containing command or process substitution syntax. It includes a new utility function detectCommandSubstitution and comprehensive unit tests. Feedback highlights that the backslash escape logic is overly broad, potentially allowing bypasses within double quotes, and notes that process substitution should not be flagged when contained within double-quoted strings.

Comment on lines +754 to +757
if (char === '\\' && !inSingleQuote) {
i += 2;
continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The logic for handling backslash escapes is overly broad. Inside double quotes, a backslash is only an escape character for a limited set of characters ($, `, ", ). The current implementation treats a backslash as an escape for any character, which can be exploited to bypass command substitution detection (e.g., "a$(whoami)").

    if (char === '\\') {
      if (inDoubleQuote) {
        if (i + 1 < command.length && ["$", "\\", "\"", "\u0060"].includes(command[i + 1])) {
          i += 2;
          continue;
        }
      } else if (!inSingleQuote) {
        i += 2;
        continue;
      }
    }

Comment on lines +763 to +765
if ((char === '<' || char === '>') && command[i + 1] === '(') {
return true;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Process substitution (<() or >()) is not active inside double-quoted strings. The current implementation incorrectly flags these as command injection when they appear inside double quotes.

Suggested change
if ((char === '<' || char === '>') && command[i + 1] === '(') {
return true;
}
if (!inDoubleQuote && (char === '<' || char === '>') && command[i + 1] === '(') {
return true;
}

@Famous077
Copy link
Copy Markdown
Contributor Author

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a command injection detection mechanism within the ShellTool to prevent the execution of command substitution syntax, such as $(), backticks, and process substitution. The changes include the addition of a detectCommandSubstitution utility and a suite of unit tests. Review feedback identifies several security concerns related to PowerShell compatibility, specifically the need to handle PowerShell's unique escaping rules and additional subexpression syntaxes like @(...) and (...). Additionally, a suggestion was made to clarify the error message regarding process substitution for better clarity.

Comment on lines +729 to +791
export function detectCommandSubstitution(command: string): boolean {
let inSingleQuote = false;
let inDoubleQuote = false;
let i = 0;

while (i < command.length) {
const char = command[i];

if (char === "'" && !inDoubleQuote) {
inSingleQuote = !inSingleQuote;
i++;
continue;
}

if (char === '"' && !inSingleQuote) {
inDoubleQuote = !inDoubleQuote;
i++;
continue;
}

if (inSingleQuote) {
i++;
continue;
}

if (char === '\\') {
if (inSingleQuote) {
i++;
continue;
}
if (inDoubleQuote) {
const next = command[i + 1];
if (['$', '`', '"', '\\', '\n'].includes(next)) {
i += 2;
continue;
}
} else if (!inDoubleQuote) {
i += 2;
continue;
}
}

if (char === '$' && command[i + 1] === '(') {
return true;
}

if (
!inDoubleQuote &&
(char === '<' || char === '>') &&
command[i + 1] === '('
) {
return true;
}

if (char === '`') {
return true;
}

i++;
}

return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The detectCommandSubstitution function incorrectly applies Bash-style backslash escaping rules when the target shell is PowerShell on Windows. In PowerShell, the backslash (\) is a literal character, and the backtick (`) is used for escaping. This allows an attacker to bypass the command substitution check by using a backslash to "escape" a command substitution pattern (e.g., \$(whoami)). The detectCommandSubstitution function will incorrectly interpret \ as an escape for the $ and allow the command to proceed, but PowerShell will treat \ as a literal and execute the $(whoami) command substitution.

Remediation: Modify detectCommandSubstitution to correctly apply PowerShell's escaping rules when the operating system is Windows. Specifically, it should not treat \ as an escape character for $ or other special characters in PowerShell contexts.

Comment on lines +729 to +791
export function detectCommandSubstitution(command: string): boolean {
let inSingleQuote = false;
let inDoubleQuote = false;
let i = 0;

while (i < command.length) {
const char = command[i];

if (char === "'" && !inDoubleQuote) {
inSingleQuote = !inSingleQuote;
i++;
continue;
}

if (char === '"' && !inSingleQuote) {
inDoubleQuote = !inDoubleQuote;
i++;
continue;
}

if (inSingleQuote) {
i++;
continue;
}

if (char === '\\') {
if (inSingleQuote) {
i++;
continue;
}
if (inDoubleQuote) {
const next = command[i + 1];
if (['$', '`', '"', '\\', '\n'].includes(next)) {
i += 2;
continue;
}
} else if (!inDoubleQuote) {
i += 2;
continue;
}
}

if (char === '$' && command[i + 1] === '(') {
return true;
}

if (
!inDoubleQuote &&
(char === '<' || char === '>') &&
command[i + 1] === '('
) {
return true;
}

if (char === '`') {
return true;
}

i++;
}

return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The detectCommandSubstitution function fails to detect PowerShell's array subexpression syntax (@(...)). In PowerShell, @(...) can be used for command execution (e.g., echo @(whoami) will execute whoami). Since detectCommandSubstitution does not explicitly check for this pattern, an attacker can bypass the intended command injection prevention by using @(...) instead of $(...) or backticks.

Remediation: Extend detectCommandSubstitution to include checks for PowerShell's array subexpression syntax (@(...)) when the target shell is PowerShell.

Comment on lines +729 to +791
export function detectCommandSubstitution(command: string): boolean {
let inSingleQuote = false;
let inDoubleQuote = false;
let i = 0;

while (i < command.length) {
const char = command[i];

if (char === "'" && !inDoubleQuote) {
inSingleQuote = !inSingleQuote;
i++;
continue;
}

if (char === '"' && !inSingleQuote) {
inDoubleQuote = !inDoubleQuote;
i++;
continue;
}

if (inSingleQuote) {
i++;
continue;
}

if (char === '\\') {
if (inSingleQuote) {
i++;
continue;
}
if (inDoubleQuote) {
const next = command[i + 1];
if (['$', '`', '"', '\\', '\n'].includes(next)) {
i += 2;
continue;
}
} else if (!inDoubleQuote) {
i += 2;
continue;
}
}

if (char === '$' && command[i + 1] === '(') {
return true;
}

if (
!inDoubleQuote &&
(char === '<' || char === '>') &&
command[i + 1] === '('
) {
return true;
}

if (char === '`') {
return true;
}

i++;
}

return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The detectCommandSubstitution function does not detect simple PowerShell subexpressions enclosed in parentheses ((...)). In PowerShell, a command enclosed in parentheses (e.g., (whoami)) will be executed. This allows an attacker to bypass the command injection prevention by directly executing commands within parentheses, which is not caught by the current detectCommandSubstitution logic.

Remediation: Extend detectCommandSubstitution to include checks for PowerShell subexpressions ((...)) when the target shell is PowerShell.

Comment on lines +160 to +163
llmContent:
'Command injection detected: command substitution syntax ' +
'($(), backticks, <>()) found in command arguments. ' +
'This is a security risk and the command was blocked.',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The error message uses the notation <>() to refer to process substitution, which is non-standard and potentially confusing for both the user and the LLM. Since the implementation correctly blocks both input process substitution <() and output process substitution >(()), the message should be updated to be more explicit or use a clearer representation like <() or >(). This ensures the feedback provided to the LLM is accurate and actionable.

Suggested change
llmContent:
'Command injection detected: command substitution syntax ' +
'($(), backticks, <>()) found in command arguments. ' +
'This is a security risk and the command was blocked.',
llmContent:
'Command injection detected: command substitution syntax ' +
'($(), backticks, \u003c() or \u003e()) found in command arguments. ' +
'This is a security risk and the command was blocked.',
References
  1. To prevent prompt injection, avoid including user-provided input in content passed to the LLM (llmContent).
  2. To prevent prompt injection, sanitize any additional context from hooks by escaping HTML-like tag characters such as < and >.

@Famous077
Copy link
Copy Markdown
Contributor Author

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces command injection detection for the shell tool by identifying command substitution syntax such as $(), backticks, and process substitution. It implements specific detection logic for Bash and PowerShell and includes a suite of tests to verify the security boundaries. However, the PowerShell detection logic is currently too restrictive, as it blocks any opening parenthesis outside of double quotes, which will cause functional regressions by blocking standard language constructs like 'if' and 'foreach' statements. The feedback suggests refining this logic or using a more robust parsing approach.

Comment on lines +979 to +981
if (char === '(' && !inDoubleQuote) {
return true;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of detectPowerShellSubstitution is overly restrictive because it blocks any opening parenthesis ( found outside of double quotes. In PowerShell, parentheses are not only used for command subexpressions (e.g., (whoami)) but are also required for standard control flow structures such as if (condition), foreach ($item in $items), and catch (Exception).

This will cause a significant functional regression, as legitimate scripts using these common constructs will be blocked with a "Command injection detected" error.

Additionally, line 976 blocks @( even inside double quotes (e.g., echo "@()"), which is a false positive since array subexpressions are treated as literal strings when double-quoted in PowerShell.

Consider refining the detection logic to distinguish between dangerous subexpressions and valid language syntax, or leverage the existing PowerShell parser (POWERSHELL_PARSER_SCRIPT) to identify risky AST nodes (like SubExpressionAst or ParenthesizedExpressionAst) more accurately.

@Famous077
Copy link
Copy Markdown
Contributor Author

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces command injection detection for the ShellTool by identifying shell substitution syntax such as $(), backticks, and process substitution. It adds a detectCommandSubstitution utility with specialized parsing logic for Bash and PowerShell environments, along with comprehensive unit tests. Feedback was provided regarding the manual string parsing logic, specifically noting that the escape character handling in both Bash and PowerShell detection functions lacks explicit bounds checks when processing the end of a command string, which could lead to fragile behavior.

Comment on lines +919 to +930
if (char === '\\') {
if (inDoubleQuote) {
const next = command[i + 1];
if (['$', '`', '"', '\\', '\n'].includes(next)) {
i += 2;
continue;
}
} else {
i += 2;
continue;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The manual parsing logic for backslash escapes in detectBashSubstitution is potentially fragile. While it correctly skips characters, it doesn't account for the end of the string if a backslash is the final character. Although command[i + 1] returns undefined in JavaScript and won't crash, it's better to explicitly check bounds or use a more robust parsing approach to avoid skipping logic that might be bypassed by clever encoding or multi-byte characters.

Comment on lines +969 to +972
if (char === '`' && !inSingleQuote) {
i += 2;
continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In detectPowerShellSubstitution, the backtick escape logic skips two characters without checking if the backtick is at the end of the string. While this won't cause a runtime crash in Node.js, it's a best practice to ensure the index remains within bounds during manual string parsing to prevent logic errors in edge cases.

@Famous077 Famous077 requested a review from a team as a code owner April 1, 2026 13:25
@Famous077
Copy link
Copy Markdown
Contributor Author

The latest commit addresses all bot review feedback including bounds checking
for escape characters and PowerShell detection refinements.
Ready for maintainer review.

@Famous077
Copy link
Copy Markdown
Contributor Author

Hi @Adib234 , I've implemented the required changes to resolve this issue. Could you please review this PR and let me know if anything needs to be updated or improved. Thank you

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

Labels

area/security Issues related to security help wanted We will accept PRs from all issues marked as "help wanted". Thanks for your support! priority/p1 Important and should be addressed in the near term.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security Vulnerability: Command Injection in run_shell_command

1 participant