mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Improved regex for auto-execution of chained commands (#158)
* Improved regex for auto-execution of chained commands
This commit is contained in:
5
.changeset/large-crews-hunt.md
Normal file
5
.changeset/large-crews-hunt.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"roo-cline": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Improved regex for auto-execution of chained commands
|
||||||
@@ -523,7 +523,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Split command by chaining operators
|
// Split command by chaining operators
|
||||||
const commands = command.split(/&&|\|\||;|\||\$\(|`/).map(cmd => cmd.trim())
|
const commands = command.split(/&&|\|\||;|(?<!"[^"]*)\|(?![^"]*")|\$\(|`/).map(cmd => cmd.trim())
|
||||||
|
|
||||||
// Check if all individual commands are allowed
|
// Check if all individual commands are allowed
|
||||||
return commands.every((cmd) => {
|
return commands.every((cmd) => {
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
|||||||
it('auto-approves chained commands when all parts are allowed', async () => {
|
it('auto-approves chained commands when all parts are allowed', async () => {
|
||||||
render(
|
render(
|
||||||
<ExtensionStateContextProvider>
|
<ExtensionStateContextProvider>
|
||||||
<ChatView
|
<ChatView
|
||||||
isHidden={false}
|
isHidden={false}
|
||||||
showAnnouncement={false}
|
showAnnouncement={false}
|
||||||
hideAnnouncement={() => {}}
|
hideAnnouncement={() => {}}
|
||||||
@@ -429,7 +429,12 @@ describe('ChatView - Auto Approval Tests', () => {
|
|||||||
'npm test && npm run build',
|
'npm test && npm run build',
|
||||||
'npm test; npm run build',
|
'npm test; npm run build',
|
||||||
'npm test || npm run build',
|
'npm test || npm run build',
|
||||||
'npm test | npm run build'
|
'npm test | npm run build',
|
||||||
|
// Add test for quoted pipes which should be treated as part of the command, not as a chain operator
|
||||||
|
'echo "hello | world"',
|
||||||
|
'npm test "param with | inside" && npm run build',
|
||||||
|
// PowerShell command with Select-String
|
||||||
|
'npm test 2>&1 | Select-String -NotMatch "node_modules" | Select-String "FAIL|Error"'
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const command of allowedChainedCommands) {
|
for (const command of allowedChainedCommands) {
|
||||||
@@ -438,7 +443,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
|||||||
// First hydrate state with initial task
|
// First hydrate state with initial task
|
||||||
mockPostMessage({
|
mockPostMessage({
|
||||||
alwaysAllowExecute: true,
|
alwaysAllowExecute: true,
|
||||||
allowedCommands: ['npm test', 'npm run build'],
|
allowedCommands: ['npm test', 'npm run build', 'echo', 'Select-String'],
|
||||||
clineMessages: [
|
clineMessages: [
|
||||||
{
|
{
|
||||||
type: 'say',
|
type: 'say',
|
||||||
@@ -452,7 +457,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
|||||||
// Then send the chained command ask message
|
// Then send the chained command ask message
|
||||||
mockPostMessage({
|
mockPostMessage({
|
||||||
alwaysAllowExecute: true,
|
alwaysAllowExecute: true,
|
||||||
allowedCommands: ['npm test', 'npm run build'],
|
allowedCommands: ['npm test', 'npm run build', 'echo', 'Select-String'],
|
||||||
clineMessages: [
|
clineMessages: [
|
||||||
{
|
{
|
||||||
type: 'say',
|
type: 'say',
|
||||||
@@ -483,7 +488,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
|||||||
it('does not auto-approve chained commands when any part is disallowed', () => {
|
it('does not auto-approve chained commands when any part is disallowed', () => {
|
||||||
render(
|
render(
|
||||||
<ExtensionStateContextProvider>
|
<ExtensionStateContextProvider>
|
||||||
<ChatView
|
<ChatView
|
||||||
isHidden={false}
|
isHidden={false}
|
||||||
showAnnouncement={false}
|
showAnnouncement={false}
|
||||||
hideAnnouncement={() => {}}
|
hideAnnouncement={() => {}}
|
||||||
@@ -498,15 +503,20 @@ describe('ChatView - Auto Approval Tests', () => {
|
|||||||
'npm test; rm -rf /',
|
'npm test; rm -rf /',
|
||||||
'npm test || rm -rf /',
|
'npm test || rm -rf /',
|
||||||
'npm test | rm -rf /',
|
'npm test | rm -rf /',
|
||||||
|
// Test subshell execution using $() and backticks
|
||||||
'npm test $(echo dangerous)',
|
'npm test $(echo dangerous)',
|
||||||
'npm test `echo dangerous`'
|
'npm test `echo dangerous`',
|
||||||
|
// Test unquoted pipes with disallowed commands
|
||||||
|
'npm test | rm -rf /',
|
||||||
|
// Test PowerShell command with disallowed parts
|
||||||
|
'npm test 2>&1 | Select-String -NotMatch "node_modules" | rm -rf /'
|
||||||
]
|
]
|
||||||
|
|
||||||
disallowedChainedCommands.forEach(command => {
|
disallowedChainedCommands.forEach(command => {
|
||||||
// First hydrate state with initial task
|
// First hydrate state with initial task
|
||||||
mockPostMessage({
|
mockPostMessage({
|
||||||
alwaysAllowExecute: true,
|
alwaysAllowExecute: true,
|
||||||
allowedCommands: ['npm test'],
|
allowedCommands: ['npm test', 'Select-String'],
|
||||||
clineMessages: [
|
clineMessages: [
|
||||||
{
|
{
|
||||||
type: 'say',
|
type: 'say',
|
||||||
@@ -520,7 +530,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
|||||||
// Then send the chained command ask message
|
// Then send the chained command ask message
|
||||||
mockPostMessage({
|
mockPostMessage({
|
||||||
alwaysAllowExecute: true,
|
alwaysAllowExecute: true,
|
||||||
allowedCommands: ['npm test'],
|
allowedCommands: ['npm test', 'Select-String'],
|
||||||
clineMessages: [
|
clineMessages: [
|
||||||
{
|
{
|
||||||
type: 'say',
|
type: 'say',
|
||||||
@@ -545,6 +555,121 @@ describe('ChatView - Auto Approval Tests', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('handles complex PowerShell command chains correctly', async () => {
|
||||||
|
render(
|
||||||
|
<ExtensionStateContextProvider>
|
||||||
|
<ChatView
|
||||||
|
isHidden={false}
|
||||||
|
showAnnouncement={false}
|
||||||
|
hideAnnouncement={() => {}}
|
||||||
|
showHistoryView={() => {}}
|
||||||
|
/>
|
||||||
|
</ExtensionStateContextProvider>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test PowerShell specific command chains
|
||||||
|
const powershellCommands = {
|
||||||
|
allowed: [
|
||||||
|
'npm test 2>&1 | Select-String -NotMatch "node_modules"',
|
||||||
|
'npm test 2>&1 | Select-String "FAIL|Error"',
|
||||||
|
'npm test 2>&1 | Select-String -NotMatch "node_modules" | Select-String "FAIL|Error"'
|
||||||
|
],
|
||||||
|
disallowed: [
|
||||||
|
'npm test 2>&1 | Select-String -NotMatch "node_modules" | rm -rf /',
|
||||||
|
'npm test 2>&1 | Select-String "FAIL|Error" && del /F /Q *',
|
||||||
|
'npm test 2>&1 | Select-String -NotMatch "node_modules" | Remove-Item -Recurse'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test allowed PowerShell commands
|
||||||
|
for (const command of powershellCommands.allowed) {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
|
||||||
|
mockPostMessage({
|
||||||
|
alwaysAllowExecute: true,
|
||||||
|
allowedCommands: ['npm test', 'Select-String'],
|
||||||
|
clineMessages: [
|
||||||
|
{
|
||||||
|
type: 'say',
|
||||||
|
say: 'task',
|
||||||
|
ts: Date.now() - 2000,
|
||||||
|
text: 'Initial task'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
mockPostMessage({
|
||||||
|
alwaysAllowExecute: true,
|
||||||
|
allowedCommands: ['npm test', 'Select-String'],
|
||||||
|
clineMessages: [
|
||||||
|
{
|
||||||
|
type: 'say',
|
||||||
|
say: 'task',
|
||||||
|
ts: Date.now() - 2000,
|
||||||
|
text: 'Initial task'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ask',
|
||||||
|
ask: 'command',
|
||||||
|
ts: Date.now(),
|
||||||
|
text: command,
|
||||||
|
partial: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||||
|
type: 'askResponse',
|
||||||
|
askResponse: 'yesButtonClicked'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test disallowed PowerShell commands
|
||||||
|
for (const command of powershellCommands.disallowed) {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
|
||||||
|
mockPostMessage({
|
||||||
|
alwaysAllowExecute: true,
|
||||||
|
allowedCommands: ['npm test', 'Select-String'],
|
||||||
|
clineMessages: [
|
||||||
|
{
|
||||||
|
type: 'say',
|
||||||
|
say: 'task',
|
||||||
|
ts: Date.now() - 2000,
|
||||||
|
text: 'Initial task'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
mockPostMessage({
|
||||||
|
alwaysAllowExecute: true,
|
||||||
|
allowedCommands: ['npm test', 'Select-String'],
|
||||||
|
clineMessages: [
|
||||||
|
{
|
||||||
|
type: 'say',
|
||||||
|
say: 'task',
|
||||||
|
ts: Date.now() - 2000,
|
||||||
|
text: 'Initial task'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ask',
|
||||||
|
ask: 'command',
|
||||||
|
ts: Date.now(),
|
||||||
|
text: command,
|
||||||
|
partial: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(vscode.postMessage).not.toHaveBeenCalledWith({
|
||||||
|
type: 'askResponse',
|
||||||
|
askResponse: 'yesButtonClicked'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user