Skip to content

Commit 3cad839

Browse files
anthonykim1Copilot
andauthored
Add Agent Host PowerShell nested script-block auto-approval regressions (#329290)
Add PowerShell nested script-block auto-approval regressions Cover Measure-Command and related wrapper shapes so nested denied commands inside PowerShell script blocks cannot be auto-approved when the PowerShell grammar is selected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 102bf1fe-34f3-4b81-b22f-5677ace2be4d
1 parent 371f6fd commit 3cad839

2 files changed

Lines changed: 43 additions & 0 deletions

File tree

src/vs/platform/agentHost/test/node/commandAutoApprover.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,32 @@ suite('CommandAutoApprover', () => {
502502
], ['denied', 'denied']);
503503
});
504504

505+
// Reported PowerShell wrapper shapes: an outer allowed cmdlet must not
506+
// hide nested denied/non-allowed commands inside a script block. The Bash
507+
// grammar keeps `{ ... }` opaque, so the same rules can incorrectly
508+
// approve the line when the wrong dialect is selected.
509+
test('does not auto-approve denied commands nested in Measure-Command script blocks', () => {
510+
const rules = {
511+
...pwsh,
512+
autoApproveRules: {
513+
'Measure-Command': true,
514+
'Where-Object': true,
515+
'Set-Content': false,
516+
'Start-Process': false,
517+
'Invoke-Expression': false,
518+
},
519+
};
520+
assert.deepStrictEqual([
521+
approver.shouldAutoApprove('Measure-Command { Set-Content -Path out.txt -Value pwned }', rules),
522+
approver.shouldAutoApprove('Measure-Command { Invoke-Expression "Write-Output hi" }', rules),
523+
approver.shouldAutoApprove('Get-ChildItem | Where-Object { Start-Process notepad }', rules),
524+
// Visible separators already rejected nested denied commands.
525+
approver.shouldAutoApprove('Write-Host hi; Set-Content -Path out.txt -Value pwned', rules),
526+
// The wrong dialect demonstrates the opaque-block bypass.
527+
approver.shouldAutoApprove('Measure-Command { Set-Content -Path out.txt -Value pwned }', { language: 'bash', autoApproveRules: rules.autoApproveRules }),
528+
], ['denied', 'denied', 'denied', 'denied', 'approved']);
529+
});
530+
505531
// An unquoted `$null` discards PowerShell output; both the spaced form (a
506532
// `redirection` node) and the no-space form (a `generic_token`) must be
507533
// recognized. POSIX sinks and real file targets still require confirmation.

src/vs/platform/agentHost/test/node/sessionPermissions.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,23 @@ suite('SessionPermissionManager', () => {
313313
], [ToolCallConfirmationReason.NotNeeded, undefined, false, true]);
314314
});
315315

316+
test('PowerShell script-block payloads with nested denials require confirmation', async () => {
317+
configService.updateRootConfig({
318+
[AgentHostTerminalAutoApproveRulesConfigKey]: {
319+
'Measure-Command': true,
320+
'Set-Content': false,
321+
'Invoke-Expression': false,
322+
},
323+
});
324+
assert.deepStrictEqual([
325+
await permissions.getAutoApproval(powershellEvent('Measure-Command { Set-Content -Path out.txt -Value pwned }'), sessionUri),
326+
await permissions.getAutoApproval(powershellEvent('Measure-Command { Invoke-Expression "Write-Output hi" }'), sessionUri),
327+
await permissions.getAutoApproval(shellEvent('Write-Host hi; Set-Content -Path out.txt -Value pwned', 'powershell'), sessionUri),
328+
// Missing dialect remains fail-closed even for an otherwise allowlisted outer command.
329+
await permissions.getAutoApproval(shellEvent('Measure-Command { Get-ChildItem }', undefined), sessionUri),
330+
], [undefined, undefined, undefined, undefined]);
331+
});
332+
316333
test('PowerShell redirects require a literal approved destination', async () => {
317334
const dynamicResults = [];
318335
for (const dest of ['$HOME/outside.txt', '$env:TEMP/x.txt', '$(Get-Location)/x.txt', '`pwd`/x.txt', '${HOME}/x.txt', '%APPDATA%/x.txt']) {

0 commit comments

Comments
 (0)