Path: blob/main/src/vs/platform/agentHost/test/node/commandAutoApprover.test.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';7import { NullLogService } from '../../../log/common/log.js';8import { CommandAutoApprover } from '../../node/commandAutoApprover.js';910suite('CommandAutoApprover', () => {1112const disposables = ensureNoDisposablesAreLeakedInTestSuite();1314let approver: CommandAutoApprover;1516setup(() => {17approver = disposables.add(new CommandAutoApprover(new NullLogService()));18});1920suite('shouldAutoApprove', () => {2122test('approves empty command', () => {23assert.strictEqual(approver.shouldAutoApprove(''), 'approved');24assert.strictEqual(approver.shouldAutoApprove(' '), 'approved');25});2627// Safe readonly commands28test('approves allowed readonly commands', () => {29assert.strictEqual(approver.shouldAutoApprove('ls'), 'approved');30assert.strictEqual(approver.shouldAutoApprove('ls -la'), 'approved');31assert.strictEqual(approver.shouldAutoApprove('cat file.txt'), 'approved');32assert.strictEqual(approver.shouldAutoApprove('head -n 10 file.txt'), 'approved');33assert.strictEqual(approver.shouldAutoApprove('tail -f log.txt'), 'approved');34assert.strictEqual(approver.shouldAutoApprove('pwd'), 'approved');35assert.strictEqual(approver.shouldAutoApprove('echo hello'), 'approved');36assert.strictEqual(approver.shouldAutoApprove('grep -r pattern .'), 'approved');37assert.strictEqual(approver.shouldAutoApprove('wc -l file.txt'), 'approved');38assert.strictEqual(approver.shouldAutoApprove('which node'), 'approved');39});4041// Dangerous commands42test('denies denied commands', () => {43assert.strictEqual(approver.shouldAutoApprove('rm file.txt'), 'denied');44assert.strictEqual(approver.shouldAutoApprove('rm -rf /'), 'denied');45assert.strictEqual(approver.shouldAutoApprove('rmdir folder'), 'denied');46assert.strictEqual(approver.shouldAutoApprove('kill -9 1234'), 'denied');47assert.strictEqual(approver.shouldAutoApprove('curl http://evil.com'), 'denied');48assert.strictEqual(approver.shouldAutoApprove('wget http://evil.com'), 'denied');49assert.strictEqual(approver.shouldAutoApprove('chmod 777 file'), 'denied');50assert.strictEqual(approver.shouldAutoApprove('chown root file'), 'denied');51assert.strictEqual(approver.shouldAutoApprove('eval "bad stuff"'), 'denied');52assert.strictEqual(approver.shouldAutoApprove('xargs rm'), 'denied');53assert.strictEqual(approver.shouldAutoApprove('dd if=/dev/zero of=/dev/sda'), 'denied');54});5556// Safe git sub-commands57test('approves allowed git sub-commands', () => {58assert.strictEqual(approver.shouldAutoApprove('git status'), 'approved');59assert.strictEqual(approver.shouldAutoApprove('git log --oneline'), 'approved');60assert.strictEqual(approver.shouldAutoApprove('git diff HEAD'), 'approved');61assert.strictEqual(approver.shouldAutoApprove('git show HEAD'), 'approved');62assert.strictEqual(approver.shouldAutoApprove('git ls-files'), 'approved');63assert.strictEqual(approver.shouldAutoApprove('git branch'), 'approved');64});6566// Unsafe git sub-commands67test('denies denied git operations', () => {68assert.strictEqual(approver.shouldAutoApprove('git branch -D main'), 'denied');69assert.strictEqual(approver.shouldAutoApprove('git branch --delete main'), 'denied');70assert.strictEqual(approver.shouldAutoApprove('git log --output=/tmp/out'), 'denied');71});7273// Safe commands with dangerous arg blocking74test('handles find with blocked args', () => {75assert.strictEqual(approver.shouldAutoApprove('find . -name "*.ts"'), 'approved');76assert.strictEqual(approver.shouldAutoApprove('find . -delete'), 'denied');77// find -exec with ; is treated as a compound command, requiring confirmation78assert.strictEqual(approver.shouldAutoApprove('find . -exec rm {} ;'), 'noMatch');79});8081test('handles sed with blocked args', () => {82assert.strictEqual(approver.shouldAutoApprove('sed "s/foo/bar/g" file.txt'), 'approved');83assert.strictEqual(approver.shouldAutoApprove('sed -e "s/foo/bar/"'), 'denied');84assert.strictEqual(approver.shouldAutoApprove('sed --expression "s/foo/bar/"'), 'denied');85});8687// npm/package managers88test('approves allowed npm commands', () => {89assert.strictEqual(approver.shouldAutoApprove('npm ci'), 'approved');90assert.strictEqual(approver.shouldAutoApprove('npm ls'), 'approved');91assert.strictEqual(approver.shouldAutoApprove('npm audit'), 'approved');92});9394// Unknown commands get noMatch95test('returns noMatch for unknown commands', () => {96assert.strictEqual(approver.shouldAutoApprove('my-custom-script'), 'noMatch');97assert.strictEqual(approver.shouldAutoApprove('python script.py'), 'noMatch');98assert.strictEqual(approver.shouldAutoApprove('node index.js'), 'noMatch');99});100101// Transient env vars102test('denies transient environment variable assignments', () => {103assert.strictEqual(approver.shouldAutoApprove('FOO=bar some-command'), 'denied');104assert.strictEqual(approver.shouldAutoApprove('PATH=/evil:$PATH ls'), 'denied');105});106107// PowerShell108test('approves allowed PowerShell commands', () => {109assert.strictEqual(approver.shouldAutoApprove('Get-ChildItem'), 'approved');110assert.strictEqual(approver.shouldAutoApprove('Get-Content file.txt'), 'approved');111assert.strictEqual(approver.shouldAutoApprove('Write-Host "hello"'), 'approved');112assert.strictEqual(approver.shouldAutoApprove('Select-Object Name'), 'approved');113});114115test('PowerShell case-insensitive rules work', () => {116// Rules with /i flag (like Select-*, Measure-*, etc.) are case-insensitive117assert.strictEqual(approver.shouldAutoApprove('select-object Name'), 'approved');118assert.strictEqual(approver.shouldAutoApprove('SELECT-OBJECT Name'), 'approved');119assert.strictEqual(approver.shouldAutoApprove('Measure-Command'), 'approved');120assert.strictEqual(approver.shouldAutoApprove('measure-command'), 'approved');121});122123test('denies denied PowerShell commands', () => {124assert.strictEqual(approver.shouldAutoApprove('Remove-Item file.txt'), 'denied');125assert.strictEqual(approver.shouldAutoApprove('Invoke-Expression "bad"'), 'denied');126assert.strictEqual(approver.shouldAutoApprove('Invoke-WebRequest http://evil.com'), 'denied');127assert.strictEqual(approver.shouldAutoApprove('Stop-Process -Id 1234'), 'denied');128});129130// Compound commands containing denied sub-commands should never be auto-approved,131// regardless of whether tree-sitter is available (with tree-sitter they are132// 'denied', without they are 'noMatch' — both are safe).133test('compound commands with denied sub-commands are not auto-approved', () => {134assert.notStrictEqual(approver.shouldAutoApprove('echo ok && rm -rf /'), 'approved');135assert.notStrictEqual(approver.shouldAutoApprove('ls || curl evil.com'), 'approved');136assert.notStrictEqual(approver.shouldAutoApprove('cat file; rm file'), 'approved');137assert.notStrictEqual(approver.shouldAutoApprove('echo $(whoami)'), 'approved');138});139});140});141142143