Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Auto-approve tee and allow /tmp writes#298560

Draft
sanchirico wants to merge 4 commits intomicrosoft:mainfrom
sanchirico:auto-approve-tee-tmp
Draft

Auto-approve tee and allow /tmp writes#298560
sanchirico wants to merge 4 commits intomicrosoft:mainfrom
sanchirico:auto-approve-tee-tmp

Conversation

@sanchirico
Copy link
Contributor

I thought this would be a simple first PR, but adding tee means the file write analyzer needs to know which files tee targets (redirections are already caught by tree-sitter). So it also needed a TeeFileWriteParser and shared tokenizer extraction? Happy to make some edits and resubmit if there is a better idea.

This adds tee to the auto-approve allowlist, a TeeFileWriteParser so the file write analyzer detects tee's file arguments (same pattern as SedFileWriteParser) and allows writes to /tmp and /private/tmp in the analyzer.

On macOS /tmp is a symlink to /private/tmp. If the shell resolves the symlink before the path reaches the analyzer startsWith('/tmp/') misses it. Checking both prefixes covers both cases.

I left out /var/tmp. I believe it's more for stuff that survives reboots and #288327 only asks for /tmp.

Not sure if /private/tmp is in scope for #288327. Let me know if you'd rather drop it and keep only /tmp.

#288328 covers most of the same ground but doesn't handle /private/tmp. Copilot also didn't touch the no-workspace-folders path to allow /tmp there, so I added that too.

Also moves the duplicate _tokenizeCommand from SedFileWriteParser into a shared commandParserUtils.ts.

For pre-existing tests that used /tmp/file.txt as the "outside workspace - block" example, I updated the path to /etc/file.txt to preserve the original intent rather than flipping them to "allow" like #288328 does

Tests added for /tmp, /private/tmp, and tee with various destinations.

Fixes #288327

I'm not 100% sure about this. I thought this would be a simple first PR, but adding `tee` means the file write analyzer needs to know which files `tee` targets. So it also needed a `TeeFileWriteParser` and shared tokenizer extraction?

This adds `tee` to the auto-approve allowlist, a `TeeFileWriteParser` so the file
write analyzer detects `tee`'s file arguments (same pattern as `SedFileWriteParser`),
and allows writes to `/tmp` and `/private/tmp` in the analyzer.

On macOS `/tmp` is a symlink to `/private/tmp`. If the shell resolves
the symlink before the path reaches the analyzer, `startsWith('/tmp/')` misses
it. Checking both prefixes covers both cases.

I left out `/var/tmp`. I believe it's more for stuff that survives reboots and
microsoft#288327 only asks for `/tmp`.

Not sure if `/private/tmp` is in scope for microsoft#288327. Let me know if you'd rather drop it and keep only `/tmp`.

microsoft#288328 covers most of the same ground but doesn't handle `/private/tmp`.
Copilot also didn't touch the no-workspace-folders path to allow `/tmp` there,
so I added that too.

Also extracts the duplicate `_tokenizeCommand` from `SedFileWriteParser` into a shared `commandParserUtils.ts`.

For pre-existing tests that used `/tmp/file.txt` as the "outside workspace - block" example, I updated the path to `/etc/file.txt` to preserve the original intent rather than flipping them to "allow" like microsoft#288328 does (which just makes them redundant with the new allow tests).

Tests added for `/tmp`, `/private/tmp`, and `tee` with various destinations.

Fixes microsoft#288327
Copilot AI review requested due to automatic review settings March 1, 2026 12:37
@sanchirico
Copy link
Contributor Author

@microsoft-github-policy-service agree

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the terminal tool’s auto-approval and file-write detection so common workflows like ... | tee /tmp/out.log can be auto-approved safely by (1) allowlisting tee, (2) teaching the analyzer which files tee targets, and (3) permitting writes to /tmp (and /private/tmp).

Changes:

  • Add tee to the default terminal auto-approve allowlist.
  • Allow file writes to /tmp (and /private/tmp) in the file write analyzer, including the no-workspace-folders path.
  • Introduce TeeFileWriteParser and a shared command tokenizer utility; expand/update tests accordingly.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandLineAnalyzer/commandLineFileWriteAnalyzer.test.ts Updates expectations for /tmp and adds coverage for /private/tmp + tee destinations.
src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts Adds tee to the default auto-approve rules with rationale.
src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts Registers the new TeeFileWriteParser for command-specific file-write detection.
src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts Permits /tmp and /private/tmp targets and updates behavior when no workspace folders exist.
src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandParsers/teeFileWriteParser.ts New parser to extract tee file operands as file-write targets.
src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandParsers/sedFileWriteParser.ts Refactors to reuse shared tokenization/quote stripping helpers.
src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandParsers/commandParserUtils.ts New shared tokenizeCommand + stripQuotes utilities.

Comment on lines +154 to +158
// Allow writes to /tmp (and /private/tmp on macOS)
if (options.os !== OperatingSystem.Windows && (fileUri.path.startsWith('/tmp/') || fileUri.path.startsWith('/private/tmp/'))) {
this._log('File write to tmp directory allowed', fileUri.toString());
continue;
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

The /tmp allowlist check can be bypassed with path traversal segments (for example /tmp/../etc/config), since fileUri.path is checked without normalization. Consider normalizing the path (eg posix.normalize) before the prefix check and only allowing when the normalized path still starts with /tmp/ (and /private/tmp/).

Copilot uses AI. Check for mistakes.
Comment on lines +170 to +182
// No workspace folders, allow safe null device and tmp paths even without workspace
const hasOnlySafeTargets = fileWrites.every(fw => {
if (fw === nullDevice) {
return true;
}
if (options.os !== OperatingSystem.Windows) {
const fileUri = URI.isUri(fw) ? fw : isString(fw) ? URI.file(fw) : undefined;
if (fileUri && (fileUri.path.startsWith('/tmp/') || fileUri.path.startsWith('/private/tmp/'))) {
return true;
}
}
return false;
});
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

In the “no workspace folders” branch, /tmp targets are treated as safe without applying the same variable/sub-command heuristic used above (/[$\(\){}]/). This means something like /tmp/$FOO/out.txt` could be auto-approved here but blocked when workspace folders exist. Consider reusing the same variable/sub-command check (and path normalization) before allowing a tmp target.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +68
if (token.startsWith('--')) {
if (token === '--output-error') {
// --output-error takes an argument (warn, warn-nopipe, exit, exit-nopipe)
i += 2;
continue;
}
if (token.startsWith('--output-error=')) {
i++;
continue;
}
// Other long options like --append, --help, --version
i++;
continue;
}

// Short options
if (token.startsWith('-') && token.length > 1) {
// Flags like -a, -i, -p, or combined like -ai
i++;
continue;
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

tee supports -- as an end-of-options marker; after it, tokens starting with - are file operands. Currently -- is treated like a regular long option and subsequent -foo operands are skipped as “short options”, which can cause missed file-write detection. Consider handling -- explicitly by treating all following tokens as file targets.

Copilot uses AI. Check for mistakes.
Comment on lines 116 to +119
test('no workspace folders - block', () => t('echo hello > file.txt', 'outsideWorkspace', false, 1, []));
test('no workspace folders - /dev/null allowed', () => t('echo hello > /dev/null', 'outsideWorkspace', true, 1, []));
test('no workspace folders - /tmp allowed', () => t('echo hello > /tmp/file.txt', 'outsideWorkspace', true, 1, []));
test('no workspace folders - /private/tmp allowed', () => t('echo hello > /private/tmp/file.txt', 'outsideWorkspace', true, 1, []));
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

With /tmp now allowed under the outsideWorkspace setting, the no cwd provided suite still expects echo hello > /tmp/file.txt to be blocked. That expectation no longer matches the analyzer logic (absolute /tmp/... will be allowed even when cwd is undefined), so these tests should be updated accordingly to avoid regressions.

Copilot uses AI. Check for mistakes.
… in no workspace branch, handle `--` in tee parser, and update tests?
@sanchirico
Copy link
Contributor Author

@copilot code review[agent]

Dear Copilot, please review the updated changes. I'm not so sure

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandLineAnalyzer/commandLineFileWriteAnalyzer.test.ts:186

  • Consider adding a test for tee - (and/or tee -a -) to ensure the analyzer does not report a file write when tee is only writing to stdout. This helps prevent regressions once - is handled specially in the parser.
		suite('tee', () => {
			test('tee to /tmp - allow', () => t('echo hello | tee /tmp/file.txt', 'outsideWorkspace', true, 1));
			test('tee to /private/tmp - allow', () => t('echo hello | tee /private/tmp/file.txt', 'outsideWorkspace', true, 1));
			test('tee to workspace - allow', () => t('echo hello | tee file.txt', 'outsideWorkspace', true, 1));
			test('tee to outside workspace - block', () => t('echo hello | tee /etc/file.txt', 'outsideWorkspace', false, 1));
			test('tee append to /tmp - allow', () => t('echo hello | tee -a /tmp/file.txt', 'outsideWorkspace', true, 1));
			test('tee /tmp traversal - block', () => t('echo hello | tee /tmp/../etc/config', 'outsideWorkspace', false, 1));
			test('tee with -- treats dash-args as files', () => t('echo hello | tee -- -file.txt', 'outsideWorkspace', true, 1));
		});

src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandParsers/teeFileWriteParser.ts:25

  • canHandle should return true only when the command will actually write to a file (per the interface contract). As written, tee with no file operands (e.g. ... | tee) will return true even though it only echoes stdin to stdout; consider checking for at least one non-option file target before returning true.
	canHandle(commandText: string): boolean {
		return /^tee(\s|$)/.test(commandText);
	}

i++;
continue;
}

Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

tee treats a lone - operand as standard output, not a file target. The current logic will classify tee - as writing to a file, which can incorrectly add a file-write disclaimer or even block auto-approval when cwd is unknown. Consider explicitly ignoring - operands when collecting file targets.

Suggested change
// A lone "-" is stdout, not a file target
if (token === '-') {
i++;
continue;
}

Copilot uses AI. Check for mistakes.
Comment on lines +108 to 112
test('absolute path - /tmp - allow', () => t('echo hello > /tmp/file.txt', 'outsideWorkspace', true, 1));
test('absolute path - /private/tmp - allow', () => t('echo hello > /private/tmp/file.txt', 'outsideWorkspace', true, 1));
test('absolute path - /tmp traversal - block', () => t('echo hello > /tmp/../etc/config', 'outsideWorkspace', false, 1));
test('absolute path - /private/tmp traversal - block', () => t('echo hello > /private/tmp/../../../etc/passwd', 'outsideWorkspace', false, 1));
test('absolute path - /etc - block', () => t('echo hello > /etc/config.txt', 'outsideWorkspace', false, 1));
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

The new /private/tmp allow-tests run with options.os hard-coded to OperatingSystem.Linux. If /private/tmp support is intended only for macOS (symlink target of /tmp), consider setting options.os to OperatingSystem.Macintosh for these cases (or parameterizing the helper) so tests reflect the OS-specific behavior.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment on lines +202 to +204
// Reads stdin and writes to stdout and files. File write safety is handled by
// the file write analyzer.
tee: true,
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

Adding tee: true here will also auto-approve tee in PowerShell (it’s a built-in alias for Tee-Object, which can write to a file path argument). The file write analyzer’s command-specific parsing currently runs only for Bash (see TreeSitterCommandParser.getCommandFileWrites), so PowerShell tee writes won’t be detected/blocked by chat.tools.terminal.blockDetectedFileWrites, creating an auto-approve bypass. Consider either not auto-approving tee by default, or extending command-specific file-write parsing to PowerShell (tee/Tee-Object) and adding corresponding tests in the pwsh suite.

Suggested change
// Reads stdin and writes to stdout and files. File write safety is handled by
// the file write analyzer.
tee: true,
// Reads stdin and writes to stdout and files. This is not auto-approved by default
// because file write safety is not fully analyzed for all shells (for example, in
// PowerShell `tee` is an alias for `Tee-Object` which can write to file paths).
tee: false,

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

@sanchirico sanchirico Mar 1, 2026

Choose a reason for hiding this comment

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

@ human A few options i see
A. add a hard-coded isPwsh guard in commandMatchesRule to block tee auto approval in PowerShell since the file write analyzer only runs for bash, or
B. extend getCommandFileWrites with a Tee-Object parser so file write detection works in PowerShell too letting the existing safety check handle it?
C. Forget the whole idea

}
// /private/tmp is the real path behind the /tmp symlink on macOS.
// This path does not exist on Linux by default
if (normalized.startsWith('/private/tmp/')) {
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

_isSafeTmpPath currently allows /private/tmp/... on all non-Windows platforms. Since /private/tmp is a macOS-specific real path for the /tmp symlink (and the comment notes it doesn’t exist on Linux by default), this broadens the allowlist beyond macOS and beyond the stated intent. Consider gating /private/tmp to OperatingSystem.Macintosh (while still allowing /tmp on Linux/macOS).

Suggested change
if (normalized.startsWith('/private/tmp/')) {
if (os === OperatingSystem.Macintosh && normalized.startsWith('/private/tmp/')) {

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I add this and adjust testing? seems like it could over complicate this fix

@sanchirico sanchirico marked this pull request as draft March 1, 2026 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider auto approving of file writes to /tmp

4 participants