Skip to content

Handle environment variable syntax in terminal command auto-approval #259205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,38 @@ export class CommandLineAutoApprover extends Disposable {
return { result: 'noMatch', reason: `Command line '${commandLine}' has no matching auto approve entries` };
}

private _removeEnvAssignments(command: string, shell: string, os: OperatingSystem): string {
const trimmedCommand = command.trimStart();

// PowerShell environment variable syntax is `$env:VAR='value';` and treated as a different
// command
if (isPowerShell(shell, os)) {
return trimmedCommand;
}

// For bash/sh/bourne shell and unknown shells (fallback to bourne shell syntax)
// Handle environment variable assignments like: VAR=value VAR2=value command
// This regex matches one or more environment variable assignments at the start
const envVarPattern = /^(\s*[A-Za-z_][A-Za-z0-9_]*=(?:[^\s'"]|'[^']*'|"[^"]*")*\s+)+/;
const match = trimmedCommand.match(envVarPattern);

if (match) {
const actualCommand = trimmedCommand.slice(match[0].length).trimStart();
return actualCommand || trimmedCommand; // Fallback to original if nothing left
}

return trimmedCommand;
}

private _commandMatchesRegex(regex: RegExp, command: string, shell: string, os: OperatingSystem): boolean {
if (regex.test(command)) {
const actualCommand = this._removeEnvAssignments(command, shell, os);

if (regex.test(actualCommand)) {
return true;
} else if (isPowerShell(shell, os) && command.startsWith('(')) {
} else if (isPowerShell(shell, os) && actualCommand.startsWith('(')) {
// Allow ignoring of the leading ( for PowerShell commands as it's a command pattern to
// operate on the output of a command. For example `(Get-Content README.md) ...`
if (regex.test(command.slice(1))) {
if (regex.test(actualCommand.slice(1))) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,6 @@ suite('CommandLineAutoApprover', () => {
});

ok(isAutoApproved('echo hello world'));
ok(!isAutoApproved(' echo hello'));
});

test('should be case-sensitive by default', () => {
Expand Down Expand Up @@ -618,4 +617,90 @@ suite('CommandLineAutoApprover', () => {
});
});
});

suite('environment variable handling', () => {
test('should handle environment variable assignments before commands in bash/sh', () => {
shell = 'bash';
os = OperatingSystem.Linux;

setAutoApprove({
"env": true,
"echo": true
});

ok(isAutoApproved('FOO=bar env'), 'Basic environment variable assignment');
ok(isAutoApproved('FOO=bar echo test'), 'Basic environment variable assignment');

ok(isAutoApproved('FOO=bar BAZ=qux env'), 'Multiple environment variables');
ok(isAutoApproved('PATH=/usr/bin HOME=/home/user echo hello'), 'Multiple environment variables');

ok(isAutoApproved('MESSAGE="hello world" echo test'), 'Environment variables with quoted values');
ok(isAutoApproved("GREETING='hello there' echo test"), 'Environment variables with quoted values');
});

test('should not match denied commands even with environment variables', () => {
shell = 'bash';
os = OperatingSystem.Linux;

setAutoApprove({
"env": true,
"rm": false
});

ok(isAutoApproved('FOO=bar env'), 'Should approve env command with environment variable');
ok(!isAutoApproved('FOO=bar rm file.txt'), 'Should deny rm command even with environment variable');
});

test('should handle environment variables with different shell types', () => {
setAutoApprove({
"echo": true
});

shell = 'bash';
os = OperatingSystem.Linux;
ok(isAutoApproved('FOO=bar echo test'));

shell = 'powershell';
os = OperatingSystem.Windows;
ok(!isAutoApproved('FOO=bar echo test'), 'This should not match since FOO=bar is not recognized as env var syntax in PowerShell');
});

test('should fallback to original command if no environment variables detected', () => {
shell = 'bash';
os = OperatingSystem.Linux;

setAutoApprove({
"echo": true
});

ok(isAutoApproved('echo hello'));
ok(isAutoApproved('echo test'));
ok(isAutoApproved('echo FOO=bar'), '// Commands that look like they might have env vars but don\'t match the pattern');
});

test('should handle edge cases in environment variable parsing', () => {
shell = 'bash';
os = OperatingSystem.Linux;

setAutoApprove({
"echo": true
});

ok(isAutoApproved('FOO= echo test'), 'Empty value');
ok(isAutoApproved('MY_VAR=test echo hello'), 'Underscore in variable name');
ok(isAutoApproved('VAR1=test echo hello'), 'Number in variable name (but not at start)');
ok(!isAutoApproved('1VAR=test echo hello'), 'Should not match if variable name starts with number (invalid)');
});

test('should handle unknown shell types by defaulting to bourne shell syntax', () => {
shell = 'unknown-shell';
os = OperatingSystem.Linux;

setAutoApprove({
"echo": true
});

ok(isAutoApproved('FOO=bar echo test'), 'Unknown shells should default to bourne shell behavior');
});
});
});
Loading