mirror of
https://github.com/microsoft/vscode.git
synced 2026-10-28 13:26:52 +08:00
Remove MXC sandbox support for local harness (#340446)
* Remove MXC sandbox support Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove remaining MXC build artifacts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix chat component fixture services Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: register remote agent service in inline chat fixture Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
1 parent
0f5a4b2685
commit
c7282ad382
48 files changed
+189
-1518
No files matched your search
@@ -85,12 +85,6 @@ async function main(buildDir?: string) {
|
||||
crossCopyPlatformDir(x64AppPath, arm64AppPath, path.join(copilotExtensionNodeModules, '@github', 'copilot', 'tgrep', 'bin', plat));
|
||||
}
|
||||
|
||||
for (const base of nodeModulesBases) {
|
||||
for (const mxcArch of ['x64', 'arm64']) {
|
||||
crossCopyPlatformDir(x64AppPath, arm64AppPath, path.join(base, '@microsoft', 'mxc-sdk', 'bin', mxcArch));
|
||||
}
|
||||
}
|
||||
|
||||
const filesToSkip = [
|
||||
'**/CodeResources',
|
||||
'**/Credits.rtf',
|
||||
@@ -127,10 +121,6 @@ async function main(buildDir?: string) {
|
||||
'**/node_modules/@vscode/ripgrep-universal/bin/darwin-arm64/**',
|
||||
'**/node_modules.asar.unpacked/@vscode/ripgrep-universal/bin/darwin-x64/**',
|
||||
'**/node_modules.asar.unpacked/@vscode/ripgrep-universal/bin/darwin-arm64/**',
|
||||
// @microsoft/mxc-sdk ships per-arch native binaries under bin/<arch>;
|
||||
// the package includes both arm64 and x64 trees regardless of host arch.
|
||||
'**/node_modules/@microsoft/mxc-sdk/bin/**',
|
||||
'**/node_modules.asar.unpacked/@microsoft/mxc-sdk/bin/**',
|
||||
];
|
||||
|
||||
await makeUniversalApp({
|
||||
@@ -146,8 +136,8 @@ async function main(buildDir?: string) {
|
||||
// them as arch-unique. Paths here are ASAR-internal (top level, no `node_modules`
|
||||
// prefix). Over-covering is harmless: the allowlist is only consulted for files
|
||||
// that are actually unique to one arch.
|
||||
singleArchFiles: '{**/@github/copilot-darwin-*,**/@github/copilot-darwin-*/**,**/@github/copilot-sdk-darwin-*,**/@github/copilot-sdk-darwin-*/**,**/@github/copilot/prebuilds/darwin-*,**/@github/copilot/prebuilds/darwin-*/**,**/@github/copilot/tgrep/bin/darwin-*,**/@github/copilot/tgrep/bin/darwin-*/**,**/@github/copilot/sdk/tgrep/bin/darwin-*,**/@github/copilot/sdk/tgrep/bin/darwin-*/**,**/@github/copilot/sdk/prebuilds/darwin-*,**/@github/copilot/sdk/prebuilds/darwin-*/**,**/@github/copilot/sdk/ripgrep/bin/darwin-*,**/@github/copilot/sdk/ripgrep/bin/darwin-*/**,**/@vscode/ripgrep-universal/bin/darwin-*,**/@vscode/ripgrep-universal/bin/darwin-*/**,**/@vscode/os-proxy-resolver-darwin-*,**/@vscode/os-proxy-resolver-darwin-*/**,**/@microsoft/mxc-sdk/bin/*,**/@microsoft/mxc-sdk/bin/*/**,**/foundry-local-sdk/prebuilds,**/foundry-local-sdk/prebuilds/darwin-arm64,**/foundry-local-sdk/prebuilds/darwin-arm64/**}',
|
||||
x64ArchFiles: '{*/kerberos.node,**/extensions/microsoft-authentication/dist/libmsalruntime.dylib,**/extensions/microsoft-authentication/dist/msal-node-runtime.node,**/node_modules/@github/copilot-darwin-*/**,**/node_modules/@github/copilot-sdk-darwin-*/**,**/node_modules/@github/copilot/prebuilds/darwin-*/*,**/node_modules/@github/copilot/tgrep/bin/darwin-*/*,**/node_modules/@github/copilot/sdk/tgrep/bin/darwin-*/*,**/node_modules.asar.unpacked/@github/copilot-darwin-*/**,**/node_modules.asar.unpacked/@github/copilot-sdk-darwin-*/**,**/node_modules.asar.unpacked/@github/copilot/prebuilds/darwin-*/*,**/node_modules.asar.unpacked/@github/copilot/tgrep/bin/darwin-*/*,**/node_modules.asar.unpacked/@github/copilot/sdk/tgrep/bin/darwin-*/*,**/extensions/copilot/node_modules/@github/copilot/sdk/prebuilds/darwin-*/*,**/extensions/copilot/node_modules/@github/copilot/sdk/ripgrep/bin/darwin-*/*,**/extensions/copilot/node_modules/@github/copilot/sdk/tgrep/bin/darwin-*/*,**/extensions/copilot/node_modules/@github/copilot/tgrep/bin/darwin-*/*,**/node_modules/@vscode/ripgrep-universal/bin/darwin-*/*,**/node_modules.asar.unpacked/@vscode/ripgrep-universal/bin/darwin-*/*,**/node_modules/@vscode/os-proxy-resolver-darwin-*/**,**/node_modules.asar.unpacked/@vscode/os-proxy-resolver-darwin-*/**,**/node_modules/@microsoft/mxc-sdk/bin/**,**/node_modules.asar.unpacked/@microsoft/mxc-sdk/bin/**}',
|
||||
singleArchFiles: '{**/@github/copilot-darwin-*,**/@github/copilot-darwin-*/**,**/@github/copilot-sdk-darwin-*,**/@github/copilot-sdk-darwin-*/**,**/@github/copilot/prebuilds/darwin-*,**/@github/copilot/prebuilds/darwin-*/**,**/@github/copilot/tgrep/bin/darwin-*,**/@github/copilot/tgrep/bin/darwin-*/**,**/@github/copilot/sdk/tgrep/bin/darwin-*,**/@github/copilot/sdk/tgrep/bin/darwin-*/**,**/@github/copilot/sdk/prebuilds/darwin-*,**/@github/copilot/sdk/prebuilds/darwin-*/**,**/@github/copilot/sdk/ripgrep/bin/darwin-*,**/@github/copilot/sdk/ripgrep/bin/darwin-*/**,**/@vscode/ripgrep-universal/bin/darwin-*,**/@vscode/ripgrep-universal/bin/darwin-*/**,**/@vscode/os-proxy-resolver-darwin-*,**/@vscode/os-proxy-resolver-darwin-*/**,**/foundry-local-sdk/prebuilds,**/foundry-local-sdk/prebuilds/darwin-arm64,**/foundry-local-sdk/prebuilds/darwin-arm64/**}',
|
||||
x64ArchFiles: '{*/kerberos.node,**/extensions/microsoft-authentication/dist/libmsalruntime.dylib,**/extensions/microsoft-authentication/dist/msal-node-runtime.node,**/node_modules/@github/copilot-darwin-*/**,**/node_modules/@github/copilot-sdk-darwin-*/**,**/node_modules/@github/copilot/prebuilds/darwin-*/*,**/node_modules/@github/copilot/tgrep/bin/darwin-*/*,**/node_modules/@github/copilot/sdk/tgrep/bin/darwin-*/*,**/node_modules.asar.unpacked/@github/copilot-darwin-*/**,**/node_modules.asar.unpacked/@github/copilot-sdk-darwin-*/**,**/node_modules.asar.unpacked/@github/copilot/prebuilds/darwin-*/*,**/node_modules.asar.unpacked/@github/copilot/tgrep/bin/darwin-*/*,**/node_modules.asar.unpacked/@github/copilot/sdk/tgrep/bin/darwin-*/*,**/extensions/copilot/node_modules/@github/copilot/sdk/prebuilds/darwin-*/*,**/extensions/copilot/node_modules/@github/copilot/sdk/ripgrep/bin/darwin-*/*,**/extensions/copilot/node_modules/@github/copilot/sdk/tgrep/bin/darwin-*/*,**/extensions/copilot/node_modules/@github/copilot/tgrep/bin/darwin-*/*,**/node_modules/@vscode/ripgrep-universal/bin/darwin-*/*,**/node_modules.asar.unpacked/@vscode/ripgrep-universal/bin/darwin-*/*,**/node_modules/@vscode/os-proxy-resolver-darwin-*/**,**/node_modules.asar.unpacked/@vscode/os-proxy-resolver-darwin-*/**}',
|
||||
filesToSkipComparison: (file: string) => {
|
||||
for (const expected of filesToSkip) {
|
||||
if (minimatch(file, expected)) {
|
||||
|
||||
@@ -47,10 +47,6 @@ const FILES_TO_SKIP = [
|
||||
// OS proxy resolver: universal app has separate x64 and arm64 packages
|
||||
'**/node_modules/@vscode/os-proxy-resolver-darwin-*/**',
|
||||
'**/node_modules.asar.unpacked/@vscode/os-proxy-resolver-darwin-*/**',
|
||||
// MXC SDK ships per-arch native binaries under bin/<arch>; the package
|
||||
// includes both arm64 and x64 trees regardless of host arch.
|
||||
'**/node_modules/@microsoft/mxc-sdk/bin/**',
|
||||
'**/node_modules.asar.unpacked/@microsoft/mxc-sdk/bin/**',
|
||||
// Copilot SDK tgrep prebuilds: single-arch binaries in per-platform directories
|
||||
'**/node_modules/@github/copilot/tgrep/bin/darwin-*/**',
|
||||
'**/node_modules.asar.unpacked/@github/copilot/tgrep/bin/darwin-*/**',
|
||||
|
||||
@@ -30,7 +30,7 @@ import log from 'fancy-log';
|
||||
import { runEsbuildBundle, getBootstrapEntryPointsForTarget } from './lib/esbuild.ts';
|
||||
import { fetchUrls } from './lib/fetch.ts';
|
||||
import { downloadFeedPackage } from './lib/azureFeed.ts';
|
||||
import { ensureCopilotPlatformPackage, getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getCopilotTgrepExcludeFilter, getMxcExcludeFilter, getRipgrepExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts';
|
||||
import { ensureCopilotPlatformPackage, getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getCopilotTgrepExcludeFilter, getRipgrepExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts';
|
||||
import { readAgentSdkResults } from './agent-sdk/common.ts';
|
||||
|
||||
|
||||
@@ -365,7 +365,6 @@ function packageTask(type: string, platform: string, arch: string, sourceFolderN
|
||||
.pipe(filter(getCopilotExcludeFilter(platform, arch)))
|
||||
.pipe(filter(getCopilotTgrepExcludeFilter(platform, arch)))
|
||||
.pipe(filter(getRipgrepExcludeFilter(platform, arch)))
|
||||
.pipe(filter(getMxcExcludeFilter(arch)))
|
||||
.pipe(jsFilter)
|
||||
.pipe(util.stripSourceMappingURL())
|
||||
.pipe(jsFilter.restore);
|
||||
@@ -492,9 +491,6 @@ function patchWin32DependenciesTask(destinationFolderName: string) {
|
||||
promisify(glob)('**/tgrep.exe', { cwd }),
|
||||
// TODO@anthonykim1 Remove once @github/copilot ships OneAuthInterop.dll with complete version information.
|
||||
promisify(glob)('**/OneAuthInterop.dll', { cwd }),
|
||||
// The @microsoft/mxc-sdk nested node-pty ships winpty binaries without a ProductName
|
||||
// VersionInfo resource, so stamp them here to satisfy artifact sanity validation.
|
||||
promisify(glob)('**/@microsoft/mxc-sdk/node_modules/node-pty/build/Release/winpty{,-agent}.{dll,exe}', { cwd }),
|
||||
])).flatMap(o => o);
|
||||
const packageJsonContents = JSON.parse(await fs.promises.readFile(path.join(cwd, 'package.json'), 'utf8'));
|
||||
const productContents = JSON.parse(await fs.promises.readFile(path.join(cwd, 'product.json'), 'utf8'));
|
||||
|
||||
@@ -26,7 +26,7 @@ import { createAsar } from './lib/asar.ts';
|
||||
import minimist from 'minimist';
|
||||
import { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask, compileCopilotExtensionBuildTask } from './gulpfile.extensions.ts';
|
||||
import { checkApiProposalNamesTask, copyCodiconsTask } from './lib/compilation.ts';
|
||||
import { ensureCopilotPlatformPackage, getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getCopilotRuntimeVersion, getCopilotTgrepExcludeFilter, getMxcExcludeFilter, getRipgrepExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts';
|
||||
import { ensureCopilotPlatformPackage, getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getCopilotRuntimeVersion, getCopilotTgrepExcludeFilter, getRipgrepExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts';
|
||||
import { ensureOSProxyResolverPlatformPackage, getOSProxyResolverExcludeFilter, getOSProxyResolverPlatformFiles } from './lib/osProxyResolver.ts';
|
||||
import { readAgentSdkResults } from './agent-sdk/common.ts';
|
||||
import { readDictationRuntimeResults } from './dictation-runtime/common.ts';
|
||||
@@ -252,7 +252,6 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d
|
||||
.pipe(filter(getCopilotExcludeFilter(platform, arch)))
|
||||
.pipe(filter(getCopilotTgrepExcludeFilter(platform, arch)))
|
||||
.pipe(filter(getRipgrepExcludeFilter(platform, arch)))
|
||||
.pipe(filter(getMxcExcludeFilter(arch)))
|
||||
.pipe(filter(getFoundryLocalExcludeFilter()))
|
||||
.pipe(filter(getOSProxyResolverExcludeFilter(platform, arch)))
|
||||
.pipe(jsFilter)
|
||||
@@ -266,7 +265,6 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d
|
||||
// The Dev Container CLI is spawned as an external Node process,
|
||||
// so its bundled entrypoint must be available outside the ASAR.
|
||||
'**/@devcontainers/cli/**',
|
||||
'**/@microsoft/mxc-sdk/bin/**',
|
||||
'**/node-pty/build/Release/*',
|
||||
'**/node-pty/build/Release/conpty/*',
|
||||
'**/node-pty/lib/worker/conoutSocketWorker.js',
|
||||
@@ -504,9 +502,6 @@ function patchWin32DependenciesTask(destinationFolderName: string) {
|
||||
glob('**/*explorer_command*.dll', { cwd }),
|
||||
// TODO@anthonykim1 Remove once @github/copilot ships OneAuthInterop.dll with complete version information.
|
||||
glob('**/OneAuthInterop.dll', { cwd }),
|
||||
// The @microsoft/mxc-sdk nested node-pty ships winpty binaries without a ProductName
|
||||
// VersionInfo resource, so stamp them here to satisfy artifact sanity validation.
|
||||
glob('**/@microsoft/mxc-sdk/node_modules/node-pty/build/Release/winpty{,-agent}.{dll,exe}', { cwd }),
|
||||
])).flatMap(o => o);
|
||||
const packageJson = JSON.parse(await fs.promises.readFile(path.join(cwd, versionedResourcesFolder, 'resources', 'app', 'package.json'), 'utf8'));
|
||||
const product = JSON.parse(await fs.promises.readFile(path.join(cwd, versionedResourcesFolder, 'resources', 'app', 'product.json'), 'utf8'));
|
||||
|
||||
@@ -84,8 +84,6 @@ const copilotTgrepPlatforms = [
|
||||
'win32-arm64', 'win32-x64',
|
||||
];
|
||||
|
||||
const mxcArchitectures = ['x64', 'arm64'];
|
||||
|
||||
const copilotOutOfProcessRuntimeExecutables = [
|
||||
'copilot-runtime',
|
||||
'copilot-runtime-bin',
|
||||
@@ -122,7 +120,6 @@ const copilotOptionalNativePayloadDirs = [
|
||||
'builtin-skills',
|
||||
'clipboard',
|
||||
'foundry-local-sdk',
|
||||
'mxc-bin',
|
||||
'pvrecorder',
|
||||
'webview',
|
||||
];
|
||||
@@ -139,23 +136,6 @@ const copilotOptionalNativePayloadFiles = [
|
||||
'prebuilds/*/mediaremote-adapter/**',
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns a glob filter that strips @microsoft/mxc-sdk `bin/<arch>` payload for
|
||||
* architectures other than the build target. `@microsoft/mxc-sdk` ships a full
|
||||
* set of sandbox binaries for every architecture under `bin/<arch>/`; only the
|
||||
* build target's architecture is needed. Architectures that mxc-sdk does not
|
||||
* ship (e.g. armhf) strip every `bin/<arch>` directory.
|
||||
*/
|
||||
export function getMxcExcludeFilter(arch: string): string[] {
|
||||
const target = mxcArchitectures.includes(arch) ? arch : undefined;
|
||||
const nonTargetArchitectures = mxcArchitectures.filter(a => a !== target);
|
||||
|
||||
return [
|
||||
'**',
|
||||
...nonTargetArchitectures.map(a => `!**/node_modules/@microsoft/mxc-sdk/bin/${a}/**`),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a glob filter that strips @vscode/ripgrep-universal bin directories
|
||||
* for architectures other than the build target.
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { suite, test } from 'node:test';
|
||||
import { create } from 'tar';
|
||||
import { copilotPlatforms, ensureCopilotPlatformPackage, getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getCopilotRuntimeVersion, getMxcExcludeFilter, prepareBuiltInCopilotRipgrepShim } from '../copilot.ts';
|
||||
import { copilotPlatforms, ensureCopilotPlatformPackage, getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getCopilotRuntimeVersion, prepareBuiltInCopilotRipgrepShim } from '../copilot.ts';
|
||||
|
||||
/**
|
||||
* Builds a fake `@github/copilot-win32-x64@1.0.73` tarball on disk and returns
|
||||
@@ -54,7 +54,6 @@ suite('copilot', () => {
|
||||
'!node_modules/@github/copilot-sdk-linux-x64/builtin-skills/**',
|
||||
'!node_modules/@github/copilot-sdk-linux-x64/clipboard/**',
|
||||
'!node_modules/@github/copilot-sdk-linux-x64/foundry-local-sdk/**',
|
||||
'!node_modules/@github/copilot-sdk-linux-x64/mxc-bin/**',
|
||||
'!node_modules/@github/copilot-sdk-linux-x64/pvrecorder/**',
|
||||
'!node_modules/@github/copilot-sdk-linux-x64/webview/**',
|
||||
'!node_modules/@github/copilot-sdk-linux-x64/plugins/computer-use/**',
|
||||
@@ -69,6 +68,7 @@ suite('copilot', () => {
|
||||
'definitions/task.agent.yaml',
|
||||
'ripgrep/bin/linux-x64/rg',
|
||||
'tgrep/bin/linux-x64/tgrep',
|
||||
'mxc-bin/x64/mxc-exec',
|
||||
]);
|
||||
assertOptionalCopilotNativeDependenciesExcluded(files, 'node_modules/@github/copilot-sdk-linux-x64');
|
||||
});
|
||||
@@ -82,7 +82,6 @@ suite('copilot', () => {
|
||||
'!node_modules/@github/copilot-sdk-linuxmusl-x64/builtin-skills/**',
|
||||
'!node_modules/@github/copilot-sdk-linuxmusl-x64/clipboard/**',
|
||||
'!node_modules/@github/copilot-sdk-linuxmusl-x64/foundry-local-sdk/**',
|
||||
'!node_modules/@github/copilot-sdk-linuxmusl-x64/mxc-bin/**',
|
||||
'!node_modules/@github/copilot-sdk-linuxmusl-x64/pvrecorder/**',
|
||||
'!node_modules/@github/copilot-sdk-linuxmusl-x64/webview/**',
|
||||
'!node_modules/@github/copilot-sdk-linuxmusl-x64/plugins/computer-use/**',
|
||||
@@ -105,7 +104,6 @@ suite('copilot', () => {
|
||||
'!node_modules/@github/copilot-sdk-win32-x64/builtin-skills/**',
|
||||
'!node_modules/@github/copilot-sdk-win32-x64/clipboard/**',
|
||||
'!node_modules/@github/copilot-sdk-win32-x64/foundry-local-sdk/**',
|
||||
'!node_modules/@github/copilot-sdk-win32-x64/mxc-bin/**',
|
||||
'!node_modules/@github/copilot-sdk-win32-x64/pvrecorder/**',
|
||||
'!node_modules/@github/copilot-sdk-win32-x64/webview/**',
|
||||
'!node_modules/@github/copilot-sdk-win32-x64/plugins/computer-use/**',
|
||||
@@ -387,34 +385,6 @@ suite('copilot', () => {
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps only the target architecture of @microsoft/mxc-sdk', () => {
|
||||
assert.deepStrictEqual(
|
||||
getMxcExcludeFilter('x64'),
|
||||
[
|
||||
'**',
|
||||
'!**/node_modules/@microsoft/mxc-sdk/bin/arm64/**',
|
||||
]
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
getMxcExcludeFilter('arm64'),
|
||||
[
|
||||
'**',
|
||||
'!**/node_modules/@microsoft/mxc-sdk/bin/x64/**',
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('strips every @microsoft/mxc-sdk architecture for unsupported armhf builds', () => {
|
||||
assert.deepStrictEqual(
|
||||
getMxcExcludeFilter('armhf'),
|
||||
[
|
||||
'**',
|
||||
'!**/node_modules/@microsoft/mxc-sdk/bin/x64/**',
|
||||
'!**/node_modules/@microsoft/mxc-sdk/bin/arm64/**',
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function assertCopilotPlatformPackageIncludes(patterns: string[], packageDir: string, relativeFiles: string[]): void {
|
||||
@@ -425,7 +395,7 @@ function assertCopilotPlatformPackageIncludes(patterns: string[], packageDir: st
|
||||
}
|
||||
|
||||
function assertOptionalCopilotNativeDependenciesExcluded(patterns: string[], packageDir: string): void {
|
||||
for (const dir of ['clipboard', 'foundry-local-sdk', 'mxc-bin', 'pvrecorder', 'webview']) {
|
||||
for (const dir of ['clipboard', 'foundry-local-sdk', 'pvrecorder', 'webview']) {
|
||||
assert(patterns.includes(`!${packageDir}/${dir}/**`), dir);
|
||||
assert(!matchesGlob(`${packageDir}/${dir}/index.js`, patterns), dir);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@ const desktopEntryPoints = [
|
||||
'vs/platform/files/node/watcher/watcherMain',
|
||||
'vs/platform/localTranscription/node/localTranscriptionMain',
|
||||
'vs/platform/terminal/node/ptyHostMain',
|
||||
'vs/platform/sandbox/node/mxcMain',
|
||||
'vs/platform/agentHost/node/agentHostMain',
|
||||
'vs/platform/agentHost/node/diffWorkerMain',
|
||||
'vs/workbench/api/node/extensionHostProcess',
|
||||
@@ -136,7 +135,6 @@ const serverEntryPoints = [
|
||||
'vs/workbench/api/node/extensionHostProcess',
|
||||
'vs/platform/files/node/watcher/watcherMain',
|
||||
'vs/platform/terminal/node/ptyHostMain',
|
||||
'vs/platform/sandbox/node/mxcMain',
|
||||
'vs/platform/agentHost/node/agentHostMain',
|
||||
'vs/platform/agentHost/node/diffWorkerMain',
|
||||
];
|
||||
|
||||
@@ -1023,35 +1023,6 @@
|
||||
"Copyright (c) GitHub, Inc. All rights reserved."
|
||||
]
|
||||
},
|
||||
{
|
||||
// Reason: NPM package does not include a repository URL, so the OSS tool cannot
|
||||
// auto-resolve a license source. The package tarball ships a standard MIT LICENSE.md
|
||||
// authored by Microsoft Corporation.
|
||||
"name": "@microsoft/mxc-sdk",
|
||||
"fullLicenseText": [
|
||||
"MIT License",
|
||||
"",
|
||||
"Copyright (c) Microsoft Corporation.",
|
||||
"",
|
||||
"Permission is hereby granted, free of charge, to any person obtaining a copy",
|
||||
"of this software and associated documentation files (the \"Software\"), to deal",
|
||||
"in the Software without restriction, including without limitation the rights",
|
||||
"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell",
|
||||
"copies of the Software, and to permit persons to whom the Software is",
|
||||
"furnished to do so, subject to the following conditions:",
|
||||
"",
|
||||
"The above copyright notice and this permission notice shall be included in all",
|
||||
"copies or substantial portions of the Software.",
|
||||
"",
|
||||
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR",
|
||||
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
|
||||
"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE",
|
||||
"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
|
||||
"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,",
|
||||
"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE",
|
||||
"SOFTWARE"
|
||||
]
|
||||
},
|
||||
{
|
||||
// Reason: NPM package's repository URL points to microsoft/vscode-packages, which is
|
||||
// a private/internal Microsoft repository (returns 404 publicly). The package tarball
|
||||
|
||||
@@ -1636,7 +1636,6 @@ export default defineConfig(
|
||||
'zod',
|
||||
'@microsoft/dev-tunnels-contracts',
|
||||
'@microsoft/dev-tunnels-management',
|
||||
'@microsoft/mxc-sdk/v1',
|
||||
'@parcel/watcher',
|
||||
'@vscode/sqlite3',
|
||||
'@vscode/vscode-languagedetection',
|
||||
|
||||
@@ -25,7 +25,6 @@ import { URI } from '../../../../util/vs/base/common/uri';
|
||||
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
|
||||
import { ensureNodePtyShim } from './nodePtyShim';
|
||||
import { ensureRipgrepShim } from './ripgrepShim';
|
||||
import { resolveAppModulePath } from './appNodeModules';
|
||||
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
|
||||
import { formatTokenCount, getAutoModelDescription, getModelCapabilitiesDescription, getReasoningEffortDescription, normalizeTokenPrices, pickDefaultReasoningEffort } from '../../../conversation/common/languageModelAccess';
|
||||
|
||||
@@ -576,14 +575,6 @@ export class CopilotCLISDK implements ICopilotCLISDK {
|
||||
try {
|
||||
// Ensure the node-pty and ripgrep shims exist before importing the SDK (required for CLI sessions)
|
||||
await this._ensureShimsPromise;
|
||||
// The SDK's sandbox auto-detection looks for `mxc-bin/<arch>/wxc-exec.exe` (and the
|
||||
// Linux/macOS equivalents) under `MXC_BIN_DIR`. VS Code core ships the MXC
|
||||
// sandbox binaries at `<appRoot>/node_modules/@microsoft/mxc-sdk/bin/<arch>/`
|
||||
// (or `node_modules.asar.unpacked/...` in a packaged build), so point
|
||||
// `MXC_BIN_DIR` there. The @github/copilot package's own `mxc-bin/` is excluded
|
||||
// from the product build (see build/.moduleignore).
|
||||
process.env['MXC_BIN_DIR'] = await resolveAppModulePath(this.envService.appRoot, '@microsoft', 'mxc-sdk', 'bin');
|
||||
|
||||
// On Linux the MXC bubblewrap sandbox backend does not forward a PTY into
|
||||
// the container, so the CLI's default PTY-backed interactive shell can
|
||||
// never start bash under the sandbox: the inner shell sees a non-tty
|
||||
|
||||
Generated
-31
@@ -21,7 +21,6 @@
|
||||
"@microsoft/dev-tunnels-management": "^1.3.41",
|
||||
"@microsoft/dev-tunnels-ssh": "^3.12.22",
|
||||
"@microsoft/dev-tunnels-ssh-tcp": "^3.12.22",
|
||||
"@microsoft/mxc-sdk": "1.0.0",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vscode/codicons": "^0.0.46-43",
|
||||
@@ -2312,36 +2311,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz",
|
||||
"integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ=="
|
||||
},
|
||||
"node_modules/@microsoft/mxc-sdk": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/mxc-sdk/-/mxc-sdk-1.0.0.tgz",
|
||||
"integrity": "sha512-7aVR+GHVKveIknZmUtkAEFwUBp61qgEmhJRe1ZyKHJ274yWlKWB/ZfDP/u/whfmDyck0wRNKZN49atlF+SZt2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"koffi": "^3.2.1",
|
||||
"node-pty": "^1.1.0",
|
||||
"semver": "^7.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/mxc-sdk/node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@microsoft/mxc-sdk/node_modules/node-pty": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz",
|
||||
"integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
|
||||
|
||||
+1
-3
@@ -111,7 +111,6 @@
|
||||
"@microsoft/dev-tunnels-management": "^1.3.41",
|
||||
"@microsoft/dev-tunnels-ssh": "^3.12.22",
|
||||
"@microsoft/dev-tunnels-ssh-tcp": "^3.12.22",
|
||||
"@microsoft/mxc-sdk": "1.0.0",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vscode/codicons": "^0.0.46-43",
|
||||
@@ -325,8 +324,7 @@
|
||||
"es5-ext": false,
|
||||
"husky": false,
|
||||
"@vscode/windows-ca-certs@0.3.4": true,
|
||||
"@vscode/windows-process-tree@0.8.0": true,
|
||||
"node-pty@1.1.0": true
|
||||
"@vscode/windows-process-tree@0.8.0": true
|
||||
},
|
||||
"copilotRuntimeVersion": "1.0.94-4"
|
||||
}
|
||||
Generated
-31
@@ -13,7 +13,6 @@
|
||||
"@hpke/core": "^1.9.0",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@microsoft/mxc-sdk": "1.0.0",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@vscode/copilot-api": "^0.5.2",
|
||||
"@vscode/deviceid": "^0.1.1",
|
||||
@@ -586,36 +585,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz",
|
||||
"integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ=="
|
||||
},
|
||||
"node_modules/@microsoft/mxc-sdk": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/mxc-sdk/-/mxc-sdk-1.0.0.tgz",
|
||||
"integrity": "sha512-7aVR+GHVKveIknZmUtkAEFwUBp61qgEmhJRe1ZyKHJ274yWlKWB/ZfDP/u/whfmDyck0wRNKZN49atlF+SZt2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"koffi": "^3.2.1",
|
||||
"node-pty": "^1.1.0",
|
||||
"semver": "^7.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/mxc-sdk/node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@microsoft/mxc-sdk/node_modules/node-pty": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz",
|
||||
"integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||
|
||||
+1
-3
@@ -8,7 +8,6 @@
|
||||
"@hpke/core": "^1.9.0",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@microsoft/mxc-sdk": "1.0.0",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@vscode/copilot-api": "^0.5.2",
|
||||
"@vscode/deviceid": "^0.1.1",
|
||||
@@ -78,8 +77,7 @@
|
||||
"@vscode/sqlite3@5.1.12-vscode": true,
|
||||
"cpu-features": false,
|
||||
"@vscode/windows-ca-certs@0.3.4": true,
|
||||
"@vscode/windows-process-tree@0.8.0": true,
|
||||
"node-pty@1.1.0": true
|
||||
"@vscode/windows-process-tree@0.8.0": true
|
||||
},
|
||||
"copilotRuntimeVersion": "1.0.94-4"
|
||||
}
|
||||
@@ -177,7 +177,7 @@ export const copilotCliConfigSchema = createSchema({
|
||||
[CopilotCliConfigKey.EnableCustomTerminalTool]: schemaProperty<boolean>({
|
||||
type: 'boolean',
|
||||
title: localize('agentHost.config.enableCustomTerminalTool.title', "Use Agent Host Terminal Tool"),
|
||||
description: localize('agentHost.config.enableCustomTerminalTool.description', "When enabled, Copilot SDK sessions use Agent Host's terminal tool override instead of the SDK's default terminal behavior."),
|
||||
description: localize('agentHost.config.enableCustomTerminalTool.description', "When enabled, Copilot SDK sessions use Agent Host's terminal tool override instead of the SDK's default terminal behavior. Not supported on Windows, where sessions use the SDK's terminal and sandbox runtime."),
|
||||
default: false,
|
||||
}),
|
||||
[CopilotCliConfigKey.EnableShellInitScript]: schemaProperty<boolean>({
|
||||
|
||||
@@ -190,7 +190,6 @@ export const sandboxSettingIdToAgentHostKey: Readonly<Record<string, AgentHostSa
|
||||
[AgentSandboxSettingId.AgentSandboxUserConfiguredPaths]: AgentHostSandboxKey.UserConfiguredPaths,
|
||||
[AgentSandboxSettingId.AgentSandboxLinuxFileSystem]: AgentHostSandboxKey.LinuxFileSystem,
|
||||
[AgentSandboxSettingId.AgentSandboxMacFileSystem]: AgentHostSandboxKey.MacFileSystem,
|
||||
[AgentSandboxSettingId.AgentSandboxWindowsFileSystem]: AgentHostSandboxKey.WindowsFileSystem,
|
||||
[AgentSandboxSettingId.AgentSandboxAdvancedRuntime]: AgentHostSandboxKey.AdvancedRuntime,
|
||||
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: AgentHostSandboxKey.AllowedNetworkDomains,
|
||||
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: AgentHostSandboxKey.DeniedNetworkDomains,
|
||||
|
||||
@@ -9,7 +9,6 @@ import { AgentHostGitHubService, IAgentHostGitHubService } from './agentHostGitH
|
||||
import type { GitHubServiceOptions } from '../../github/common/githubTypes.js';
|
||||
import { ISandboxHelperService } from '../../sandbox/common/sandboxHelperService.js';
|
||||
import { SandboxHelperService } from '../../sandbox/node/sandboxHelper.js';
|
||||
import { IWindowsMxcTerminalSandboxRuntime, WindowsMxcTerminalSandboxRuntime } from '../../sandbox/common/terminalSandboxMxcRuntime.js';
|
||||
import { URI } from '../../../base/common/uri.js';
|
||||
import { dirname, joinPath } from '../../../base/common/resources.js';
|
||||
import { IAgentPluginManager } from '../common/agentPluginManager.js';
|
||||
@@ -138,7 +137,6 @@ export interface IAgentHostHostServiceInputs {
|
||||
|
||||
export function registerAgentHostHostServices(services: ServiceCollection, inputs: IAgentHostHostServiceInputs): void {
|
||||
services.set(IDevContainerAgentHostMainService, new SyncDescriptor(RemoteDevContainerAgentHostService));
|
||||
services.set(IWindowsMxcTerminalSandboxRuntime, new SyncDescriptor(WindowsMxcTerminalSandboxRuntime));
|
||||
services.set(ISandboxHelperService, new SyncDescriptor(SandboxHelperService));
|
||||
services.set(IAgentHostGitService, new SyncDescriptor(AgentHostGitService));
|
||||
services.set(IAgentPluginManager, new SyncDescriptor(AgentPluginManager, [inputs.userDataPath]));
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createHash } from 'crypto';
|
||||
import { IEnvironmentService, INativeEnvironmentService } from '../../../environment/common/environment.js';
|
||||
import { IInstantiationService } from '../../../instantiation/common/instantiation.js';
|
||||
import { IProductService } from '../../../product/common/productService.js';
|
||||
import { ISandboxHelperService, type ISandboxDependencyStatus, type IWindowsMxcPolicyContainment, type IWindowsMxcSandboxPolicy } from '../../../sandbox/common/sandboxHelperService.js';
|
||||
import { ISandboxHelperService, type ISandboxDependencyStatus } from '../../../sandbox/common/sandboxHelperService.js';
|
||||
import { ITerminalSandboxEngineHost, ITerminalSandboxRuntimeInfo, TerminalSandboxEngine } from '../../../sandbox/common/terminalSandboxEngine.js';
|
||||
import { IAgentConfigurationService } from '../agentConfigurationService.js';
|
||||
import { getAppNodeModulesUri } from '../appNodeModules.js';
|
||||
@@ -111,18 +111,6 @@ class AgentHostTerminalSandboxHost extends Disposable implements ITerminalSandbo
|
||||
return this._sandboxHelper.checkSandboxDependencies();
|
||||
}
|
||||
|
||||
async getWindowsMxcFilesystemPolicy() {
|
||||
return this._sandboxHelper.getWindowsMxcFilesystemPolicy();
|
||||
}
|
||||
|
||||
async getWindowsMxcEnvironment() {
|
||||
return this._sandboxHelper.getWindowsMxcEnvironment();
|
||||
}
|
||||
|
||||
async buildWindowsMxcSandboxPayload(commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment) {
|
||||
return this._sandboxHelper.buildWindowsMxcSandboxPayload(commandLine, policy, workingDirectory, containerName, containment);
|
||||
}
|
||||
|
||||
getSandboxSetting<T>(settingId: string): T | undefined {
|
||||
// The agent host stores sandbox settings nested under a single
|
||||
// top-level `sandbox` object with prefix-free sub-keys (e.g.
|
||||
|
||||
@@ -2806,13 +2806,6 @@ export class CopilotAgent extends Disposable implements IAgent {
|
||||
this._logService.info(`[Copilot] Using bundled runtime path: ${runtimePath}`);
|
||||
}
|
||||
|
||||
// The SDK's sandbox auto-detection looks for `<MXC_BIN_DIR>/<arch>/wxc-exec.exe`
|
||||
// (and the Linux/macOS equivalents). VS Code core ships the MXC sandbox binaries
|
||||
// at `<nodeModules>/@microsoft/mxc-sdk/bin/<arch>/`, so point `MXC_BIN_DIR` there.
|
||||
// The @github/copilot package's own `mxc-bin/` is excluded from the product build
|
||||
// (see build/.moduleignore), mirroring `CopilotCLISDK.getPackage` in the extension.
|
||||
env['MXC_BIN_DIR'] = URI.joinPath(nodeModulesUri, '@microsoft', 'mxc-sdk', 'bin').fsPath;
|
||||
|
||||
// Add VS Code's built-in ripgrep to PATH so the CLI subprocess can find it.
|
||||
const rgDir = dirname(resolvedRgDiskPath);
|
||||
// On Windows the env key is typically "Path" (not "PATH"). Since we copied
|
||||
|
||||
@@ -5779,7 +5779,7 @@ export class CopilotAgentSession extends Disposable {
|
||||
|
||||
/** Whether the Agent Host's own shell tools replace the SDK's built-in shell. */
|
||||
private _isCustomTerminalToolEnabled(): boolean {
|
||||
return this._configurationService.getRootValue(copilotCliConfigSchema, CopilotCliConfigKey.EnableCustomTerminalTool) === true;
|
||||
return this._platform !== 'win32' && this._configurationService.getRootValue(copilotCliConfigSchema, CopilotCliConfigKey.EnableCustomTerminalTool) === true;
|
||||
}
|
||||
|
||||
/** The effective SDK sandbox policy, or `undefined` when sandboxing is disabled. */
|
||||
|
||||
@@ -1001,7 +1001,7 @@ export class CopilotSessionLauncher implements ICopilotSessionLauncher {
|
||||
...(stabilityOrderedPromptEnabled ? { STABILITY_ORDERED_SYSTEM_PROMPT_V2: true } : {}),
|
||||
...(hydraFusionV2Enabled ? { HYDRAFUSION_PLAN_V2: true } : {}),
|
||||
};
|
||||
const enableCustomTerminalTool = this._configurationService.getRootValue(copilotCliConfigSchema, CopilotCliConfigKey.EnableCustomTerminalTool) === true;
|
||||
const enableCustomTerminalTool = process.platform !== 'win32' && this._configurationService.getRootValue(copilotCliConfigSchema, CopilotCliConfigKey.EnableCustomTerminalTool) === true;
|
||||
let shellToolsPromise: ReturnType<typeof createShellTools> | Promise<[]> = Promise.resolve([]);
|
||||
if (enableCustomTerminalTool) {
|
||||
if (!plan.shellManager) {
|
||||
|
||||
@@ -24614,6 +24614,18 @@ Use the attached image as context.
|
||||
assert.deepStrictEqual(mockSession.shellInitScriptUpdates, []);
|
||||
});
|
||||
|
||||
test('Windows retains SDK shell init scripts when the custom terminal override is configured', async () => {
|
||||
const { session, mockSession, setConfigValue } = await createEnabledSession({
|
||||
platform: 'win32',
|
||||
rootValues: { [CopilotCliConfigKey.EnableCustomTerminalTool]: true },
|
||||
});
|
||||
setConfigValue(SessionConfigKey.ShellInitScripts, [initScript]);
|
||||
|
||||
await session.send('go', undefined, 'turn-1', 'interactive');
|
||||
|
||||
assert.deepStrictEqual(mockSession.shellInitScriptUpdates.map(update => (update as unknown[]).length), [1]);
|
||||
});
|
||||
|
||||
test('removes its own script on dispose and leaves a successor instance script intact', async () => {
|
||||
const successorScript = '/mock-userdata/agentHost/shellInit/test-session-1/successor-instance/init.sh';
|
||||
const { session, storedFileContents, setConfigValue } = await createEnabledSession({
|
||||
|
||||
@@ -439,6 +439,9 @@ suite('CopilotSessionLauncher sandbox policy', () => {
|
||||
store.add(await fixture.launcher.launch({ ...fixture.plan, shellManager }, testRuntime));
|
||||
|
||||
assert.deepStrictEqual(fixture.updates.filter(update => update.sandboxConfig).map(update => update.sandboxConfig?.enabled), [selection === 'on']);
|
||||
if (process.platform === 'win32') {
|
||||
assert.deepStrictEqual(fixture.captured?.tools?.filter(tool => tool.name === 'bash' || tool.name === 'powershell'), []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import { ServiceCollection } from '../../../instantiation/common/serviceCollecti
|
||||
import { ILogService, NullLogService } from '../../../log/common/log.js';
|
||||
import { IProductService } from '../../../product/common/productService.js';
|
||||
import { ISandboxHelperService } from '../../../sandbox/common/sandboxHelperService.js';
|
||||
import { IWindowsMxcTerminalSandboxRuntime, WindowsMxcTerminalSandboxRuntime } from '../../../sandbox/common/terminalSandboxMxcRuntime.js';
|
||||
import { AgentHostSandboxConfigKey, AgentHostSandboxKey } from '../../common/sandboxConfigSchema.js';
|
||||
import { AgentSandboxEnabledValue } from '../../../sandbox/common/settings.js';
|
||||
import { VSBuffer } from '../../../../base/common/buffer.js';
|
||||
@@ -168,29 +167,9 @@ suite('CopilotShellTools', () => {
|
||||
}
|
||||
|
||||
function createStubSandboxHelperService(): ISandboxHelperService {
|
||||
// Stub used by every test that constructs a `ShellManager`. Avoids loading
|
||||
// the real node-only `SandboxHelperService`, which dynamically imports
|
||||
// `@microsoft/mxc-sdk` and fails to resolve in the electron renderer test
|
||||
// runner used by `scripts/test.bat`.
|
||||
return {
|
||||
_serviceBrand: undefined,
|
||||
checkSandboxDependencies: async () => undefined,
|
||||
getWindowsMxcFilesystemPolicy: async () => ({ readonlyPaths: [], readwritePaths: [] }),
|
||||
getWindowsMxcEnvironment: async () => [],
|
||||
buildWindowsMxcSandboxPayload: async (commandLine, policy, workingDirectory, containerName = 'vscode-terminal-sandbox', containment = 'process') => ({
|
||||
command: commandLine,
|
||||
containerName,
|
||||
containment: { type: containment },
|
||||
workingDirectory,
|
||||
timeoutMs: policy.timeoutMs ?? 0,
|
||||
filesystem: {
|
||||
readwritePaths: [...(policy.filesystem?.readwritePaths ?? [])],
|
||||
readonlyPaths: [...(policy.filesystem?.readonlyPaths ?? [])],
|
||||
deniedPaths: [...(policy.filesystem?.deniedPaths ?? [])],
|
||||
},
|
||||
network: policy.network,
|
||||
ui: policy.ui,
|
||||
}),
|
||||
} satisfies ISandboxHelperService;
|
||||
}
|
||||
|
||||
@@ -221,13 +200,9 @@ suite('CopilotShellTools', () => {
|
||||
userHome: URI.file('/home/test-user'),
|
||||
} as Partial<IEnvironmentService> & { userHome: URI } as IEnvironmentService);
|
||||
services.set(IProductService, { dataFolderName: '.test-data' } as Partial<IProductService> as IProductService);
|
||||
// Stub the sandbox helper so the engine never imports `@microsoft/mxc-sdk`
|
||||
// (a node-only dynamic import that fails to resolve in the electron
|
||||
// renderer test runner used by `scripts/test.bat` on Windows CI).
|
||||
services.set(ISandboxHelperService, createStubSandboxHelperService());
|
||||
const instantiationService: IInstantiationService = disposables.add(new InstantiationService(services));
|
||||
services.set(IInstantiationService, instantiationService);
|
||||
services.set(IWindowsMxcTerminalSandboxRuntime, instantiationService.createInstance(WindowsMxcTerminalSandboxRuntime));
|
||||
return { instantiationService, terminalManager, agentConfigurationService };
|
||||
}
|
||||
|
||||
@@ -827,7 +802,10 @@ suite('CopilotShellTools', () => {
|
||||
assert.strictEqual(engineA, engineB, 'Sandbox engine should be cached across calls');
|
||||
});
|
||||
|
||||
test('custom terminal sandbox follows its owner selection and live managed floor', async () => {
|
||||
test('custom terminal sandbox follows its owner selection and live managed floor', async function () {
|
||||
if (platform.isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
const { instantiationService, agentConfigurationService } = createServices({ sandboxEnabled: true });
|
||||
const owner = 'copilot:/session-1';
|
||||
const peer = URI.parse(buildDefaultChatUri(owner));
|
||||
@@ -846,7 +824,10 @@ suite('CopilotShellTools', () => {
|
||||
}, { before: true, disabled: false, afterGlobalChange: false, governed: true, other: true });
|
||||
});
|
||||
|
||||
test('custom terminal reads effective network and bypass settings across managed policy changes', async () => {
|
||||
test('custom terminal reads effective network and bypass settings across managed policy changes', async function () {
|
||||
if (platform.isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
const { instantiationService, agentConfigurationService } = createServices({ sandboxEnabled: true });
|
||||
const owner = 'copilot:/session-1';
|
||||
agentConfigurationService.setSandboxValue(AgentHostSandboxKey.AllowNetwork, true);
|
||||
@@ -868,7 +849,10 @@ suite('CopilotShellTools', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('setWorkingDirectory invalidates the captured sandbox engine roots', async () => {
|
||||
test('setWorkingDirectory invalidates the captured sandbox engine roots', async function () {
|
||||
if (platform.isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
const createdFiles = new Map<string, string>();
|
||||
const initialWorkingDirectory = URI.file('/workspace/initial');
|
||||
const newWorkingDirectory = URI.file('/workspace/reanchored');
|
||||
@@ -883,10 +867,10 @@ suite('CopilotShellTools', () => {
|
||||
shellManager.setWorkingDirectory(newWorkingDirectory);
|
||||
await engine.wrapCommand('echo reanchored');
|
||||
const reanchoredConfig = JSON.parse(createdFiles.get(sandboxConfigPath)!);
|
||||
const initialWritablePaths: string[] = platform.isWindows ? initialConfig.filesystem.readwritePaths : initialConfig.filesystem.allowWrite;
|
||||
const reanchoredWritablePaths: string[] = platform.isWindows ? reanchoredConfig.filesystem.readwritePaths : reanchoredConfig.filesystem.allowWrite;
|
||||
const initialPath = platform.isWindows ? '\\workspace\\initial' : '/workspace/initial';
|
||||
const reanchoredPath = platform.isWindows ? '\\workspace\\reanchored' : '/workspace/reanchored';
|
||||
const initialWritablePaths: string[] = initialConfig.filesystem.allowWrite;
|
||||
const reanchoredWritablePaths: string[] = reanchoredConfig.filesystem.allowWrite;
|
||||
const initialPath = '/workspace/initial';
|
||||
const reanchoredPath = '/workspace/reanchored';
|
||||
|
||||
assert.deepStrictEqual({
|
||||
enginePreserved: shellManager.getOrCreateSandboxEngine() === engine,
|
||||
@@ -909,8 +893,10 @@ suite('CopilotShellTools', () => {
|
||||
const enabledSchema = enabledPrimary.parameters as { properties: Record<string, unknown> };
|
||||
const enabledPropertyNames = Object.keys(enabledSchema.properties);
|
||||
|
||||
assert.ok(enabledPropertyNames.includes('requestUnsandboxedExecution'), 'Sandbox-enabled schema should expose requestUnsandboxedExecution');
|
||||
assert.ok(enabledPropertyNames.includes('requestUnsandboxedExecutionReason'), 'Sandbox-enabled schema should expose requestUnsandboxedExecutionReason');
|
||||
assert.deepStrictEqual({
|
||||
request: enabledPropertyNames.includes('requestUnsandboxedExecution'),
|
||||
reason: enabledPropertyNames.includes('requestUnsandboxedExecutionReason'),
|
||||
}, { request: !platform.isWindows, reason: !platform.isWindows });
|
||||
|
||||
const disabled = createServices();
|
||||
const disabledShell = disposables.add(disabled.instantiationService.createInstance(ShellManager, URI.parse('copilot:/session-disabled'), undefined));
|
||||
@@ -944,6 +930,9 @@ suite('CopilotShellTools', () => {
|
||||
});
|
||||
|
||||
test('primary shell tool wraps commands through the sandbox engine when the sandbox is enabled', async function () {
|
||||
if (platform.isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
const { instantiationService, terminalManager } = createServices({ sandboxEnabled: true });
|
||||
const shellManager = disposables.add(instantiationService.createInstance(ShellManager, URI.parse('copilot:/session-1'), undefined));
|
||||
const tools = await createShellTools(shellManager, TEST_CHAT_URI, terminalManager, new NullLogService());
|
||||
@@ -959,22 +948,14 @@ suite('CopilotShellTools', () => {
|
||||
await bashTool.handler!({ command: 'echo hello', timeout: 1 }, invocation);
|
||||
|
||||
const sentCommand = terminalManager.sentTexts[0]?.data ?? '';
|
||||
// POSIX wraps via `sandbox-runtime` and embeds the user command;
|
||||
// Windows wraps via the MXC executable and carries the user command
|
||||
// in the JSON config file referenced by the wrapper.
|
||||
if (platform.isWindows) {
|
||||
assert.ok(sentCommand.includes('mxcMain.js'), `Expected the command to be wrapped by the MXC SDK runner. Sent: ${sentCommand}`);
|
||||
} else {
|
||||
assert.ok(sentCommand.includes('sandbox-runtime'), `Expected the command to be wrapped by the sandbox runtime. Sent: ${sentCommand}`);
|
||||
assert.ok(sentCommand.includes('echo hello'), `Wrapped command should still contain the user command. Sent: ${sentCommand}`);
|
||||
}
|
||||
assert.ok(sentCommand.includes('sandbox-runtime'), `Expected the command to be wrapped by the sandbox runtime. Sent: ${sentCommand}`);
|
||||
assert.ok(sentCommand.includes('echo hello'), `Wrapped command should still contain the user command. Sent: ${sentCommand}`);
|
||||
});
|
||||
|
||||
test('primary shell tool writes a sandbox config exposing the working directory as writable', async () => {
|
||||
// Cross-platform smoke test: enabling the sandbox should result in a sandbox config file
|
||||
// being written, and the session's working directory should be a writable path in that
|
||||
// config. The JSON shape differs between POSIX (`filesystem.allowWrite`) and the Windows
|
||||
// MXC runtime (`filesystem.readwritePaths`).
|
||||
test('primary shell tool writes a sandbox config exposing the working directory as writable', async function () {
|
||||
if (platform.isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
const createdFiles = new Map<string, string>();
|
||||
const workingDirectory = URI.file('/workspace/test-workspace');
|
||||
const { instantiationService, terminalManager } = createServices({ sandboxEnabled: true, createdFiles });
|
||||
@@ -994,21 +975,19 @@ suite('CopilotShellTools', () => {
|
||||
const sandboxConfigEntry = [...createdFiles.entries()].find(([path]) => /vscode-sandbox-settings-.*\.json$/.test(path));
|
||||
assert.ok(sandboxConfigEntry, `Expected a sandbox config file to be written. Files: ${[...createdFiles.keys()].join(', ')}`);
|
||||
const config = JSON.parse(sandboxConfigEntry[1]);
|
||||
const writablePaths: string[] = platform.isWindows ? config.filesystem.readwritePaths : config.filesystem.allowWrite;
|
||||
const writablePaths: string[] = config.filesystem.allowWrite;
|
||||
assert.ok(Array.isArray(writablePaths), `Expected writable paths array. Got: ${JSON.stringify(config.filesystem)}`);
|
||||
const expectedPath = platform.isWindows ? '\\workspace\\test-workspace' : '/workspace/test-workspace';
|
||||
const expectedPath = '/workspace/test-workspace';
|
||||
assert.ok(writablePaths.includes(expectedPath), `Expected working directory in writable paths. Got: ${JSON.stringify(writablePaths)}`);
|
||||
});
|
||||
|
||||
test('primary shell tool merges configured filesystem allowRead paths into the sandbox config', async () => {
|
||||
// Cross-platform: pick the OS-specific filesystem setting key and verify the configured
|
||||
// allowRead path lands in the rendered sandbox config (POSIX `filesystem.allowRead` /
|
||||
// Windows MXC `filesystem.readonlyPaths`).
|
||||
test('primary shell tool merges configured filesystem allowRead paths into the sandbox config', async function () {
|
||||
if (platform.isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
const createdFiles = new Map<string, string>();
|
||||
const configuredReadPath = platform.isWindows ? 'C:\\tools\\custom' : '/tools/custom';
|
||||
const fileSystemKey = platform.isWindows
|
||||
? AgentHostSandboxKey.WindowsFileSystem
|
||||
: platform.isMacintosh ? AgentHostSandboxKey.MacFileSystem : AgentHostSandboxKey.LinuxFileSystem;
|
||||
const configuredReadPath = '/tools/custom';
|
||||
const fileSystemKey = platform.isMacintosh ? AgentHostSandboxKey.MacFileSystem : AgentHostSandboxKey.LinuxFileSystem;
|
||||
const { instantiationService, terminalManager, agentConfigurationService } = createServices({ sandboxEnabled: true, createdFiles });
|
||||
agentConfigurationService.setSandboxValue(fileSystemKey, { allowRead: [configuredReadPath] });
|
||||
const shellManager = disposables.add(instantiationService.createInstance(ShellManager, URI.parse('copilot:/session-1'), URI.file('/workspace/test-workspace')));
|
||||
@@ -1027,7 +1006,7 @@ suite('CopilotShellTools', () => {
|
||||
const sandboxConfigEntry = [...createdFiles.entries()].find(([path]) => /vscode-sandbox-settings-.*\.json$/.test(path));
|
||||
assert.ok(sandboxConfigEntry, `Expected a sandbox config file to be written. Files: ${[...createdFiles.keys()].join(', ')}`);
|
||||
const config = JSON.parse(sandboxConfigEntry[1]);
|
||||
const readablePaths: string[] = platform.isWindows ? config.filesystem.readonlyPaths : config.filesystem.allowRead;
|
||||
const readablePaths: string[] = config.filesystem.allowRead;
|
||||
assert.ok(Array.isArray(readablePaths), `Expected readable paths array. Got: ${JSON.stringify(config.filesystem)}`);
|
||||
assert.ok(readablePaths.includes(configuredReadPath), `Expected configured read path in readable paths. Got: ${JSON.stringify(readablePaths)}`);
|
||||
const expectedAttachmentPath = URI.from({ scheme: 'inmemory', path: '/session-data/session-1/attachments' }).fsPath;
|
||||
@@ -1035,8 +1014,6 @@ suite('CopilotShellTools', () => {
|
||||
});
|
||||
|
||||
test('primary shell tool requests confirmation before rerunning outside the sandbox', async function () {
|
||||
// The Windows sandbox only exposes Off/AllowNetwork — there is no "enabled but network-blocked"
|
||||
// state, so `requiresUnsandboxConfirmation` is unreachable on Windows.
|
||||
if (platform.isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -1078,8 +1055,6 @@ suite('CopilotShellTools', () => {
|
||||
});
|
||||
|
||||
test('primary shell tool returns sandbox_blocked when user declines unsandboxed rerun', async function () {
|
||||
// See above: the Windows sandbox never reports blocked domains, so this confirmation flow
|
||||
// is unreachable on Windows.
|
||||
if (platform.isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -1105,6 +1080,9 @@ suite('CopilotShellTools', () => {
|
||||
});
|
||||
|
||||
test('primary shell tool asks for confirmation when requestUnsandboxedExecution is explicitly set', async function () {
|
||||
if (platform.isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
const { instantiationService, terminalManager, agentConfigurationService } = createServices({ sandboxEnabled: true });
|
||||
agentConfigurationService.setSandboxValue(AgentHostSandboxKey.AllowUnsandboxedCommands, true);
|
||||
const shellManager = disposables.add(instantiationService.createInstance(ShellManager, URI.parse('copilot:/session-1'), undefined));
|
||||
@@ -1141,6 +1119,9 @@ suite('CopilotShellTools', () => {
|
||||
});
|
||||
|
||||
test('primary shell tool returns unsandboxed_disabled when allowUnsandboxedCommands is off', async function () {
|
||||
if (platform.isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
const { instantiationService, terminalManager } = createServices({ sandboxEnabled: true });
|
||||
// `chat.agent.sandbox.allowUnsandboxedCommands` is intentionally not set,
|
||||
// so the engine would silently re-sandbox the command. The shell tool
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
|
||||
import { ISandboxDependencyStatus, ISandboxHelperService, type IWindowsMxcConfig, IWindowsMxcFilesystemPolicy, type IWindowsMxcPolicyContainment, type IWindowsMxcSandboxPolicy } from '../common/sandboxHelperService.js';
|
||||
import { ISandboxDependencyStatus, ISandboxHelperService } from '../common/sandboxHelperService.js';
|
||||
|
||||
class NullSandboxHelperService implements ISandboxHelperService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
@@ -19,18 +19,6 @@ class NullSandboxHelperService implements ISandboxHelperService {
|
||||
socatInstalled: true,
|
||||
};
|
||||
}
|
||||
|
||||
async getWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async getWindowsMxcEnvironment(): Promise<string[] | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async buildWindowsMxcSandboxPayload(_commandLine: string, _policy: IWindowsMxcSandboxPolicy, _workingDirectory?: string, _containerName?: string, _containment?: IWindowsMxcPolicyContainment): Promise<IWindowsMxcConfig | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(ISandboxHelperService, NullSandboxHelperService, InstantiationType.Delayed);
|
||||
@@ -6,15 +6,7 @@
|
||||
import { Event } from '../../../base/common/event.js';
|
||||
import { CancellationToken } from '../../../base/common/cancellation.js';
|
||||
import { IChannel, IServerChannel } from '../../../base/parts/ipc/common/ipc.js';
|
||||
import { ISandboxDependencyStatus, ISandboxHelperService, type IWindowsMxcConfig, IWindowsMxcFilesystemPolicy, type IWindowsMxcPolicyContainment, type IWindowsMxcSandboxPolicy } from './sandboxHelperService.js';
|
||||
|
||||
interface IBuildWindowsMxcSandboxPayloadArgs {
|
||||
commandLine: string;
|
||||
policy: IWindowsMxcSandboxPolicy;
|
||||
workingDirectory?: string;
|
||||
containerName?: string;
|
||||
containment?: IWindowsMxcPolicyContainment;
|
||||
}
|
||||
import { ISandboxDependencyStatus, ISandboxHelperService } from './sandboxHelperService.js';
|
||||
|
||||
export const SANDBOX_HELPER_CHANNEL_NAME = 'sandboxHelper';
|
||||
|
||||
@@ -30,14 +22,6 @@ export class SandboxHelperChannel implements IServerChannel {
|
||||
switch (command) {
|
||||
case 'checkSandboxDependencies':
|
||||
return this.service.checkSandboxDependencies() as Promise<T>;
|
||||
case 'getWindowsMxcFilesystemPolicy':
|
||||
return this.service.getWindowsMxcFilesystemPolicy() as Promise<T>;
|
||||
case 'getWindowsMxcEnvironment':
|
||||
return this.service.getWindowsMxcEnvironment() as Promise<T>;
|
||||
case 'buildWindowsMxcSandboxPayload': {
|
||||
const { commandLine, policy, workingDirectory, containerName, containment } = _arg as IBuildWindowsMxcSandboxPayloadArgs;
|
||||
return this.service.buildWindowsMxcSandboxPayload(commandLine, policy, workingDirectory, containerName, containment) as Promise<T>;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Invalid call');
|
||||
@@ -52,16 +36,4 @@ export class SandboxHelperChannelClient implements ISandboxHelperService {
|
||||
checkSandboxDependencies(): Promise<ISandboxDependencyStatus | undefined> {
|
||||
return this.channel.call<ISandboxDependencyStatus | undefined>('checkSandboxDependencies');
|
||||
}
|
||||
|
||||
getWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy | undefined> {
|
||||
return this.channel.call<IWindowsMxcFilesystemPolicy | undefined>('getWindowsMxcFilesystemPolicy');
|
||||
}
|
||||
|
||||
getWindowsMxcEnvironment(): Promise<string[] | undefined> {
|
||||
return this.channel.call<string[] | undefined>('getWindowsMxcEnvironment');
|
||||
}
|
||||
|
||||
buildWindowsMxcSandboxPayload(commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment): Promise<IWindowsMxcConfig | undefined> {
|
||||
return this.channel.call<IWindowsMxcConfig | undefined>('buildWindowsMxcSandboxPayload', { commandLine, policy, workingDirectory, containerName, containment } satisfies IBuildWindowsMxcSandboxPayloadArgs);
|
||||
}
|
||||
}
|
||||
@@ -16,46 +16,7 @@ export interface ISandboxDependencyStatus {
|
||||
readonly apparmorRestrictsUnprivilegedUserNamespaces?: boolean;
|
||||
}
|
||||
|
||||
export interface IWindowsMxcFilesystemPolicy {
|
||||
readonly readonlyPaths: string[];
|
||||
readonly readwritePaths: string[];
|
||||
}
|
||||
|
||||
/** Sandbox policy passed to the Windows MXC SDK. */
|
||||
export interface IWindowsMxcSandboxPolicy {
|
||||
filesystem?: {
|
||||
readwritePaths?: string[];
|
||||
readonlyPaths?: string[];
|
||||
deniedPaths?: string[];
|
||||
clearPolicyOnExit?: boolean;
|
||||
};
|
||||
network?: {
|
||||
egress?: { default: 'allow' | 'deny' };
|
||||
ingress?: { default: 'allow' | 'deny' };
|
||||
};
|
||||
ui?: {
|
||||
disable: boolean;
|
||||
clipboard?: 'none' | 'read' | 'write' | 'all';
|
||||
allowInputInjection?: boolean;
|
||||
};
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
/** Serializable V1 container request returned by the Windows sandbox helper. */
|
||||
export interface IWindowsMxcConfig extends IWindowsMxcSandboxPolicy {
|
||||
command: string;
|
||||
workingDirectory?: string;
|
||||
containerName?: string;
|
||||
containment?: { type: IWindowsMxcPolicyContainment };
|
||||
environment?: Record<string, string>;
|
||||
}
|
||||
|
||||
export type IWindowsMxcPolicyContainment = 'process' | 'processcontainer' | 'wslc' | 'lxc' | 'seatbelt' | 'isolation_session' | 'bubblewrap';
|
||||
|
||||
export interface ISandboxHelperService {
|
||||
readonly _serviceBrand: undefined;
|
||||
checkSandboxDependencies(): Promise<ISandboxDependencyStatus | undefined>;
|
||||
getWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy | undefined>;
|
||||
getWindowsMxcEnvironment(): Promise<string[] | undefined>;
|
||||
buildWindowsMxcSandboxPayload(commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment): Promise<IWindowsMxcConfig | undefined>;
|
||||
}
|
||||
@@ -24,6 +24,11 @@ export function normalizeSandboxFileSystemPath(path: string, os: OperatingSystem
|
||||
return os === OperatingSystem.Windows ? path.replace(/\//g, '\\') : path;
|
||||
}
|
||||
|
||||
/** Whether the Local harness can sandbox terminal commands on the execution OS. */
|
||||
export function isTerminalSandboxSupported(os: OperatingSystem): boolean {
|
||||
return os !== OperatingSystem.Windows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting IDs for agent sandboxing.
|
||||
*/
|
||||
@@ -41,7 +46,6 @@ export const enum AgentSandboxSettingId {
|
||||
AgentSandboxRetryWithAllowNetworkRequests = 'chat.agent.sandbox.retryWithAllowNetworkRequests',
|
||||
AgentSandboxLinuxFileSystem = 'chat.agent.sandbox.fileSystem.linux',
|
||||
AgentSandboxMacFileSystem = 'chat.agent.sandbox.fileSystem.mac',
|
||||
AgentSandboxWindowsFileSystem = 'chat.agent.sandbox.fileSystem.windows',
|
||||
AgentSandboxAdvancedRuntime = 'chat.agent.sandbox.advanced.runtime',
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,8 @@ import { IFileService } from '../../files/common/files.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { extractDomainFromUri, matchesDomainPattern, normalizeDomain, normalizeDomainPattern } from '../../networkFilter/common/domainMatcher.js';
|
||||
import { AgentNetworkDomainSettingId } from '../../networkFilter/common/settings.js';
|
||||
import { ISandboxDependencyStatus, type IWindowsMxcConfig, IWindowsMxcFilesystemPolicy, type IWindowsMxcPolicyContainment, type IWindowsMxcSandboxPolicy } from './sandboxHelperService.js';
|
||||
import { AgentSandboxEnabledValue, AgentSandboxSettingId, isAgentSandboxEnabledValue } from './settings.js';
|
||||
import { IWindowsMxcTerminalSandboxRuntime } from './terminalSandboxMxcRuntime.js';
|
||||
import { ISandboxDependencyStatus } from './sandboxHelperService.js';
|
||||
import { AgentSandboxEnabledValue, AgentSandboxSettingId, isAgentSandboxEnabledValue, isTerminalSandboxSupported } from './settings.js';
|
||||
import { getTerminalSandboxReadAllowListForCommands } from './terminalSandboxReadAllowList.js';
|
||||
import { getTerminalSandboxRuntimeConfigurationForCommands } from './terminalSandboxRuntimeConfigurationPerOperation.js';
|
||||
import { ITerminalSandboxCommand, ITerminalSandboxFileAccessCheckResult, ITerminalSandboxPrecheckInputs, ITerminalSandboxPrerequisiteCheckResult, ITerminalSandboxResolvedNetworkDomains, ITerminalSandboxWrapResult, TerminalSandboxFileAccessPermission, TerminalSandboxPrerequisiteCheck, TerminalSandboxPreCheckRemediation } from './terminalSandboxService.js';
|
||||
@@ -44,7 +43,7 @@ export interface ITerminalSandboxRuntimeInfo {
|
||||
appRoot: string;
|
||||
/**
|
||||
* Name of the directory (relative to {@link appRoot}) that holds the native
|
||||
* binaries `ripgrep-universal` and `@microsoft/mxc-sdk`. In a packaged desktop
|
||||
* binaries `ripgrep-universal`. In a packaged desktop
|
||||
* build these are unpacked from the archive into `node_modules.asar.unpacked`;
|
||||
* in dev and on remote they live in plain `node_modules`. Defaults to
|
||||
* `node_modules`. Note the sandbox-runtime CLI itself is always resolved from
|
||||
@@ -61,8 +60,6 @@ export interface ITerminalSandboxRuntimeInfo {
|
||||
* `execPath` already points at a real `node` binary (remote, agent host).
|
||||
*/
|
||||
runAsNode?: boolean;
|
||||
/** CPU architecture of the environment that runs the sandbox runtime. */
|
||||
arch?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,12 +92,6 @@ export interface ITerminalSandboxEngineHost {
|
||||
readonly onDidChangeRoots: Event<void>;
|
||||
/** Resolves the installed sandbox-dependency status (bubblewrap, socat). */
|
||||
checkSandboxDependencies(): Promise<ISandboxDependencyStatus | undefined>;
|
||||
/** Resolves host filesystem policy fragments needed by the Windows MXC process container. */
|
||||
getWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy | undefined>;
|
||||
/** Resolves host environment variables needed by the Windows MXC process container. */
|
||||
getWindowsMxcEnvironment(): Promise<string[] | undefined>;
|
||||
/** Builds a Windows MXC payload from a target-environment MXC sandbox policy. */
|
||||
buildWindowsMxcSandboxPayload(commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment): Promise<IWindowsMxcConfig | undefined>;
|
||||
/**
|
||||
* Returns the effective value of a sandbox-related configuration setting,
|
||||
* or `undefined` when the setting is not configured.
|
||||
@@ -137,10 +128,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
private _userHome: URI | undefined;
|
||||
private _srtPath: string | undefined;
|
||||
private _rgPath: string | undefined;
|
||||
private _mxcPath: string | undefined;
|
||||
private _mxcRunnerPath: string | undefined;
|
||||
private _windowsMxcFilesystemPolicy: IWindowsMxcFilesystemPolicy | undefined;
|
||||
private _windowsMxcEnvironment: string[] | undefined;
|
||||
private _sandboxConfigPath: string | undefined;
|
||||
private _sandboxDependencyStatus: ISandboxDependencyStatus | undefined;
|
||||
private _enableWeakerNestedSandbox = false;
|
||||
@@ -150,8 +137,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
private _commandAllowListKeywords: readonly string[] = [];
|
||||
private _commandAllowListCommandDetails: readonly ITerminalSandboxCommand[] = [];
|
||||
private _commandCwd: URI | undefined;
|
||||
private _commandLine: string | undefined;
|
||||
private _commandShell: string | undefined;
|
||||
private _commandAllowNetwork = false;
|
||||
private _os: OperatingSystem = OS;
|
||||
private readonly _defaultWritePaths: string[] = [];
|
||||
@@ -161,7 +146,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
private readonly _host: ITerminalSandboxEngineHost,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IWindowsMxcTerminalSandboxRuntime private readonly _windowsMxcRuntime: IWindowsMxcTerminalSandboxRuntime,
|
||||
) {
|
||||
super();
|
||||
this._register(Event.runAndSubscribe(this._host.onDidChangeSandboxSettings, () => {
|
||||
@@ -212,6 +196,9 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
}
|
||||
|
||||
async wrapCommand(command: string, requestUnsandboxedExecution?: boolean, shell?: string, cwd?: URI, commandDetails?: readonly ITerminalSandboxCommand[], requestAllowNetwork?: boolean): Promise<ITerminalSandboxWrapResult> {
|
||||
if (!(await this.isEnabled())) {
|
||||
return { command, isSandboxWrapped: false };
|
||||
}
|
||||
const allowUnsandboxedCommands = this._areUnsandboxedCommandsAllowed();
|
||||
const retryWithAllowNetworkRequests = this._areRetryWithAllowNetworkRequestsAllowed();
|
||||
const shouldInspectBlockedDomains = requestUnsandboxedExecution !== true && requestAllowNetwork !== true && (retryWithAllowNetworkRequests || allowUnsandboxedCommands);
|
||||
@@ -230,14 +217,11 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
|| !this._areStringArraysEqual(currentReadAllowListPaths, nextReadAllowListPaths)
|
||||
|| !this._areObjectsEqual(currentRuntimeConfiguration, nextRuntimeConfiguration)
|
||||
|| this._commandCwd?.toString() !== cwd?.toString()
|
||||
|| this._commandAllowNetwork !== allowNetworkForCommand
|
||||
|| (this._os === OperatingSystem.Windows && (this._commandLine !== command || this._commandShell !== shell));
|
||||
|| this._commandAllowNetwork !== allowNetworkForCommand;
|
||||
if (shouldRefreshConfig) {
|
||||
this._commandAllowListKeywords = normalizedCommandKeywords;
|
||||
this._commandAllowListCommandDetails = normalizedCommandDetails;
|
||||
this._commandCwd = cwd;
|
||||
this._commandLine = command;
|
||||
this._commandShell = shell;
|
||||
this._commandAllowNetwork = allowNetworkForCommand;
|
||||
await this.getSandboxConfigPath(true);
|
||||
}
|
||||
@@ -271,18 +255,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
deniedDomains: blockedDomainResult.deniedDomains,
|
||||
} : undefined;
|
||||
|
||||
if (this._os === OperatingSystem.Windows) {
|
||||
if (!this._mxcPath || !this._execPath || !this._mxcRunnerPath) {
|
||||
throw new Error('MXC SDK runner paths not resolved');
|
||||
}
|
||||
return {
|
||||
command: this._windowsMxcRuntime.wrapCommand(this._mxcPath, this._sandboxConfigPath, this._execPath, this._mxcRunnerPath, this._runAsNode),
|
||||
isSandboxWrapped: true,
|
||||
requiresAllowNetworkConfirmation: allowNetworkForCommand && !this._isSandboxAllowNetworkConfigured() ? true : undefined,
|
||||
...allowNetworkConfirmationMetadata,
|
||||
};
|
||||
}
|
||||
|
||||
if (!this._execPath) {
|
||||
throw new Error('Executable path not set to run sandbox commands');
|
||||
}
|
||||
@@ -496,7 +468,7 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
|
||||
private _wrapUnsandboxedCommand(command: string, shell?: string, cwd?: URI): string {
|
||||
if (this._os === OperatingSystem.Windows) {
|
||||
return this._windowsMxcRuntime.wrapUnsandboxedCommand(command);
|
||||
return command;
|
||||
}
|
||||
if (!this._tempDir?.path) {
|
||||
return command;
|
||||
@@ -624,7 +596,9 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
if (!this._isSandboxAllowedByPrecheckInputs(precheckInputs)) {
|
||||
return false;
|
||||
}
|
||||
await this.getOS();
|
||||
if (!isTerminalSandboxSupported(await this.getOS())) {
|
||||
return false;
|
||||
}
|
||||
const value = this._getSandboxConfiguredEnabledValue();
|
||||
return isAgentSandboxEnabledValue(value);
|
||||
}
|
||||
@@ -644,8 +618,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
const rgPlatform = this._os === OperatingSystem.Windows ? 'win32' : this._os === OperatingSystem.Macintosh ? 'darwin' : 'linux';
|
||||
const rgBinary = this._os === OperatingSystem.Windows ? 'rg.exe' : 'rg';
|
||||
this._rgPath = this._pathJoin(this._appRoot, nativeModulesDir, '@vscode', 'ripgrep-universal', 'bin', `${rgPlatform}-${arch}`, rgBinary);
|
||||
this._mxcPath = this._windowsMxcRuntime.getExecutablePath(this._appRoot, nativeModulesDir, runtimeInfo.arch);
|
||||
this._mxcRunnerPath = this._pathJoin(this._appRoot, 'out', 'vs', 'platform', 'sandbox', 'node', 'mxcMain.js');
|
||||
}
|
||||
|
||||
private async _createSandboxConfig(): Promise<string | undefined> {
|
||||
@@ -663,9 +635,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
const macFileSystemSetting = this._os === OperatingSystem.Macintosh
|
||||
? this._host.getSandboxSetting<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxMacFileSystem) ?? {}
|
||||
: {};
|
||||
const windowsFileSystemSetting = this._os === OperatingSystem.Windows
|
||||
? this._host.getSandboxSetting<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxWindowsFileSystem) ?? {}
|
||||
: {};
|
||||
const runtimeSetting = {
|
||||
...this._host.getSandboxSetting<Record<string, unknown>>(AgentSandboxSettingId.AgentSandboxAdvancedRuntime),
|
||||
...(this._enableWeakerNestedSandbox ? { enableWeakerNestedSandbox: true } : undefined),
|
||||
@@ -679,17 +648,7 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
let allowReadPaths: string[] = [];
|
||||
let denyReadPaths: string[] = [];
|
||||
let denyWritePaths: string[] | undefined;
|
||||
if (this._os === OperatingSystem.Windows) {
|
||||
const filesystemPolicy = await this._getWindowsMxcFilesystemPolicy();
|
||||
const env = await this._getWindowsMxcEnvironment();
|
||||
allowWritePaths = await this._resolveFileSystemPaths([
|
||||
...await this._updateAllowWritePathsWithWorkspaceFolders(windowsFileSystemSetting.allowWrite),
|
||||
...filesystemPolicy.readwritePaths
|
||||
]);
|
||||
allowReadPaths = await this._resolveFileSystemPaths([...(windowsFileSystemSetting.allowRead ?? []), ...filesystemPolicy.readonlyPaths, ...this._getHostReadPaths()]);
|
||||
denyReadPaths = await this._resolveFileSystemPaths(windowsFileSystemSetting.denyRead ?? []);
|
||||
this._windowsMxcEnvironment = env;
|
||||
} else if (this._os === OperatingSystem.Macintosh) {
|
||||
if (this._os === OperatingSystem.Macintosh) {
|
||||
allowWritePaths = (await this._resolveFileSystemPaths(await this._updateAllowWritePathsWithWorkspaceFolders(macFileSystemSetting.allowWrite, commandRuntimeAllowWritePaths))).filter(path => path !== configFilePath);
|
||||
allowReadPaths = await this._resolveFileSystemPaths(await this._updateAllowReadPathsWithAllowWrite(macFileSystemSetting.allowRead, allowWritePaths, commandRuntimeAllowReadPaths));
|
||||
denyReadPaths = await this._resolveFileSystemPaths(this._updateDenyReadPathsWithHome([...(macFileSystemSetting.denyRead ?? []), configFilePath]));
|
||||
@@ -700,17 +659,7 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
denyReadPaths = await this._resolveFileSystemPaths(this._updateDenyReadPathsWithHome([...(linuxFileSystemSetting.denyRead ?? []), configFilePath]));
|
||||
denyWritePaths = await this._resolveFileSystemPaths(linuxFileSystemSetting.denyWrite);
|
||||
}
|
||||
const sandboxSettings = this._os === OperatingSystem.Windows ? await this._windowsMxcRuntime.createConfig({
|
||||
command: this._commandLine ?? '',
|
||||
shell: this._commandShell,
|
||||
cwd: this._commandCwd ?? this._getDefaultWindowsMxcCwd(),
|
||||
tempDir: this._tempDir,
|
||||
allowNetwork,
|
||||
allowReadPaths,
|
||||
allowWritePaths,
|
||||
denyReadPaths,
|
||||
env: this._windowsMxcEnvironment ?? [],
|
||||
}, this._buildSandboxPayload) : {
|
||||
const sandboxSettings: Record<string, unknown> = {
|
||||
network: allowNetwork ? { allowedDomains: [], deniedDomains: [], enabled: false } : this.getResolvedNetworkDomains(),
|
||||
filesystem: {
|
||||
denyRead: denyReadPaths,
|
||||
@@ -719,13 +668,10 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
denyWrite: denyWritePaths,
|
||||
},
|
||||
};
|
||||
if (this._os !== OperatingSystem.Windows) {
|
||||
const sandboxRuntimeSettings = sandboxSettings as Record<string, unknown>;
|
||||
this._mergeAdditionalSandboxConfigProperties(sandboxRuntimeSettings, runtimeSetting);
|
||||
this._mergeAdditionalSandboxConfigProperties(sandboxRuntimeSettings, commandRuntimeSetting);
|
||||
if (this._os === OperatingSystem.Macintosh) {
|
||||
sandboxRuntimeSettings.allowPty ??= true;
|
||||
}
|
||||
this._mergeAdditionalSandboxConfigProperties(sandboxSettings, runtimeSetting);
|
||||
this._mergeAdditionalSandboxConfigProperties(sandboxSettings, commandRuntimeSetting);
|
||||
if (this._os === OperatingSystem.Macintosh) {
|
||||
sandboxSettings.allowPty ??= true;
|
||||
}
|
||||
this._sandboxConfigPath = configFilePath;
|
||||
await this._fileService.createFile(configFileUri, VSBuffer.fromString(JSON.stringify(sandboxSettings, null, '\t')), { overwrite: true });
|
||||
@@ -739,9 +685,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
const macFileSystemSetting = this._os === OperatingSystem.Macintosh
|
||||
? this._host.getSandboxSetting<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxMacFileSystem) ?? {}
|
||||
: {};
|
||||
const windowsFileSystemSetting = this._os === OperatingSystem.Windows
|
||||
? this._host.getSandboxSetting<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxWindowsFileSystem) ?? {}
|
||||
: {};
|
||||
const commandRuntimeSetting = getTerminalSandboxRuntimeConfigurationForCommands(this._os, this._commandAllowListCommandDetails);
|
||||
const commandRuntimeAllowReadPaths = this._getCommandRuntimeFileSystemPaths(commandRuntimeSetting, 'allowRead');
|
||||
const commandRuntimeAllowWritePaths = this._getCommandRuntimeFileSystemPaths(commandRuntimeSetting, 'allowWrite');
|
||||
@@ -749,15 +692,7 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
let allowReadPaths: string[] = [];
|
||||
let denyReadPaths: string[] = [];
|
||||
let denyWritePaths: string[] | undefined;
|
||||
if (this._os === OperatingSystem.Windows) {
|
||||
const filesystemPolicy = await this._getWindowsMxcFilesystemPolicy();
|
||||
allowWritePaths = await this._resolveFileSystemPaths([
|
||||
...await this._updateAllowWritePathsWithWorkspaceFolders(windowsFileSystemSetting.allowWrite),
|
||||
...filesystemPolicy.readwritePaths
|
||||
]);
|
||||
allowReadPaths = await this._resolveFileSystemPaths([...(windowsFileSystemSetting.allowRead ?? []), ...filesystemPolicy.readonlyPaths, ...this._getHostReadPaths()]);
|
||||
denyReadPaths = await this._resolveFileSystemPaths(windowsFileSystemSetting.denyRead ?? []);
|
||||
} else if (this._os === OperatingSystem.Macintosh) {
|
||||
if (this._os === OperatingSystem.Macintosh) {
|
||||
allowWritePaths = (await this._resolveFileSystemPaths(await this._updateAllowWritePathsWithWorkspaceFolders(macFileSystemSetting.allowWrite, commandRuntimeAllowWritePaths))).filter(path => path !== configFilePath);
|
||||
allowReadPaths = await this._resolveFileSystemPaths(await this._updateAllowReadPathsWithAllowWrite(macFileSystemSetting.allowRead, allowWritePaths, commandRuntimeAllowReadPaths));
|
||||
denyReadPaths = await this._resolveFileSystemPaths(this._updateDenyReadPathsWithHome([...(macFileSystemSetting.denyRead ?? []), ...(configFilePath ? [configFilePath] : [])]));
|
||||
@@ -865,10 +800,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
return /[*?{\[]/.test(path);
|
||||
}
|
||||
|
||||
private readonly _buildSandboxPayload = (commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment): Promise<IWindowsMxcConfig | undefined> => {
|
||||
return this._host.buildWindowsMxcSandboxPayload(commandLine, policy, workingDirectory, containerName, containment);
|
||||
};
|
||||
|
||||
private _getCommandRuntimeFileSystemPaths(runtimeSetting: Record<string, unknown>, key: 'allowRead' | 'allowWrite'): string[] {
|
||||
const filesystem = runtimeSetting.filesystem;
|
||||
if (!this._isObjectForSandboxConfigMerge(filesystem)) {
|
||||
@@ -901,20 +832,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
private async _getWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy> {
|
||||
if (!this._windowsMxcFilesystemPolicy) {
|
||||
this._windowsMxcFilesystemPolicy = await this._host.getWindowsMxcFilesystemPolicy() ?? { readonlyPaths: [], readwritePaths: [] };
|
||||
}
|
||||
return this._windowsMxcFilesystemPolicy;
|
||||
}
|
||||
|
||||
private async _getWindowsMxcEnvironment(): Promise<string[]> {
|
||||
if (!this._windowsMxcEnvironment) {
|
||||
this._windowsMxcEnvironment = await this._host.getWindowsMxcEnvironment() ?? [];
|
||||
}
|
||||
return this._windowsMxcEnvironment;
|
||||
}
|
||||
|
||||
private _pathJoin = (...segments: string[]) => {
|
||||
const path = this._os === OperatingSystem.Windows ? win32 : posix;
|
||||
return path.join(...segments);
|
||||
@@ -925,7 +842,7 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
}
|
||||
|
||||
private _getUriPath(uri: URI): string {
|
||||
return this._os === OperatingSystem.Windows ? this._windowsMxcRuntime.toWindowsPath(uri) : uri.path;
|
||||
return uri.path;
|
||||
}
|
||||
|
||||
private async _initTempDir(): Promise<void> {
|
||||
@@ -1049,9 +966,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
if (!this._appRoot) {
|
||||
return [];
|
||||
}
|
||||
if (this._os === OperatingSystem.Windows) {
|
||||
return this._windowsMxcRuntime.getRuntimeReadPaths(this._appRoot, this._mxcPath);
|
||||
}
|
||||
const paths: string[] = [this._appRoot];
|
||||
if (this._execPath) {
|
||||
for (const path of [this._execPath, this._pathDirname(this._execPath)]) {
|
||||
@@ -1075,10 +989,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
return root ? [this._getUriPath(root)] : [];
|
||||
}
|
||||
|
||||
private _getDefaultWindowsMxcCwd(): URI | undefined {
|
||||
return this._host.getWriteRoots()[0];
|
||||
}
|
||||
|
||||
private _getSandboxConfiguredEnabledValue(): AgentSandboxEnabledValue {
|
||||
return this._host.getSandboxSetting<AgentSandboxEnabledValue>(AgentSandboxSettingId.AgentSandboxEnabled) ?? AgentSandboxEnabledValue.Off;
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { win32 } from '../../../base/common/path.js';
|
||||
import { OperatingSystem } from '../../../base/common/platform.js';
|
||||
import { URI } from '../../../base/common/uri.js';
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
import type { IWindowsMxcConfig, IWindowsMxcPolicyContainment, IWindowsMxcSandboxPolicy } from './sandboxHelperService.js';
|
||||
import { normalizeSandboxFileSystemPath } from './settings.js';
|
||||
|
||||
export interface IWindowsMxcConfigOptions {
|
||||
command: string;
|
||||
shell?: string;
|
||||
cwd: URI | undefined;
|
||||
tempDir: URI;
|
||||
allowNetwork: boolean;
|
||||
allowReadPaths: string[];
|
||||
allowWritePaths: string[];
|
||||
denyReadPaths: string[];
|
||||
env: string[];
|
||||
}
|
||||
|
||||
export type IWindowsMxcBuildSandboxPayload = (commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment) => Promise<IWindowsMxcConfig | undefined>;
|
||||
|
||||
export const IWindowsMxcTerminalSandboxRuntime = createDecorator<IWindowsMxcTerminalSandboxRuntime>('windowsMxcTerminalSandboxRuntime');
|
||||
|
||||
export interface IWindowsMxcTerminalSandboxRuntime {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
getExecutablePath(appRoot: string, nativeModulesDir: string, arch: string | undefined): string;
|
||||
getRuntimeReadPaths(appRoot: string | undefined, executablePath: string | undefined): string[];
|
||||
createConfig(options: IWindowsMxcConfigOptions, buildSandboxPayload: IWindowsMxcBuildSandboxPayload): Promise<IWindowsMxcConfig>;
|
||||
wrapCommand(executablePath: string, configPath: string, nodePath: string, runnerPath: string, runAsNode: boolean): string;
|
||||
wrapUnsandboxedCommand(command: string): string;
|
||||
toWindowsPath(uri: URI): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows-only MXC integration for terminal sandboxing.
|
||||
*
|
||||
* This class is intentionally isolated from the SRT-backed runtime so it can be
|
||||
* removed once SRT supports Windows sandboxing.
|
||||
*/
|
||||
export class WindowsMxcTerminalSandboxRuntime implements IWindowsMxcTerminalSandboxRuntime {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
getExecutablePath(appRoot: string, nativeModulesDir: string, arch: string | undefined): string {
|
||||
const binArch = arch === 'arm64' ? 'arm64' : 'x64';
|
||||
return win32.join(appRoot, nativeModulesDir, '@microsoft', 'mxc-sdk', 'bin', binArch, 'wxc-exec.exe');
|
||||
}
|
||||
|
||||
getRuntimeReadPaths(appRoot: string | undefined, executablePath: string | undefined): string[] {
|
||||
const paths: string[] = [];
|
||||
if (appRoot) {
|
||||
paths.push(appRoot);
|
||||
}
|
||||
if (executablePath) {
|
||||
paths.push(executablePath, win32.dirname(executablePath));
|
||||
}
|
||||
return [...new Set(paths)];
|
||||
}
|
||||
|
||||
async createConfig(options: IWindowsMxcConfigOptions, buildSandboxPayload: IWindowsMxcBuildSandboxPayload): Promise<IWindowsMxcConfig> {
|
||||
const tempDirPath = this.toWindowsPath(options.tempDir);
|
||||
const shell = options.shell
|
||||
? this._quoteWindowsCommandLineArgument(options.shell)
|
||||
: 'pwsh.exe';
|
||||
const commandLine = `${shell} -NoProfile -Command ${this._quoteWindowsCommandLineArgument(options.command)}`;
|
||||
const cwd = options.cwd ? this.toWindowsPath(options.cwd) : tempDirPath;
|
||||
const policy: IWindowsMxcSandboxPolicy = {
|
||||
timeoutMs: 0,
|
||||
filesystem: {
|
||||
readwritePaths: options.allowWritePaths.map(path => this._normalizeWindowsPath(path)),
|
||||
readonlyPaths: [tempDirPath, ...(options.shell && win32.isAbsolute(options.shell) ? [win32.dirname(options.shell)] : []), ...options.allowReadPaths].map(path => this._normalizeWindowsPath(path)),
|
||||
deniedPaths: options.denyReadPaths.map(path => this._normalizeWindowsPath(path)),
|
||||
},
|
||||
network: this._createNetworkPolicy(options.allowNetwork),
|
||||
ui: {
|
||||
disable: false,
|
||||
clipboard: 'none',
|
||||
allowInputInjection: false,
|
||||
},
|
||||
};
|
||||
|
||||
const config = await buildSandboxPayload(commandLine, policy, cwd);
|
||||
if (!config?.command) {
|
||||
throw new Error('Unable to build Windows MXC sandbox payload');
|
||||
}
|
||||
|
||||
config.environment = Object.fromEntries(options.env.map(variable => {
|
||||
const separator = variable.indexOf('=');
|
||||
if (separator < 1) {
|
||||
throw new Error(`Invalid Windows MXC environment variable: ${variable}`);
|
||||
}
|
||||
return [variable.slice(0, separator), variable.slice(separator + 1)];
|
||||
}));
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
wrapCommand(executablePath: string, configPath: string, nodePath: string, runnerPath: string, runAsNode: boolean): string {
|
||||
const binPath = win32.dirname(win32.dirname(executablePath));
|
||||
const command = `& ${this._quotePowerShellArgument(nodePath)} ${this._quotePowerShellArgument(runnerPath)} ${this._quotePowerShellArgument(configPath)} ${this._quotePowerShellArgument(binPath)}`;
|
||||
return runAsNode
|
||||
? `& { $vscodeSandboxRunAsNode = $env:ELECTRON_RUN_AS_NODE; try { $env:ELECTRON_RUN_AS_NODE = '1'; ${command} } finally { $env:ELECTRON_RUN_AS_NODE = $vscodeSandboxRunAsNode } }`
|
||||
: command;
|
||||
}
|
||||
|
||||
wrapUnsandboxedCommand(command: string): string {
|
||||
return command;
|
||||
}
|
||||
|
||||
toWindowsPath(uri: URI): string {
|
||||
let value: string;
|
||||
if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') {
|
||||
value = `\\\\${uri.authority}${uri.path}`;
|
||||
} else if (/^\/[a-zA-Z]:/.test(uri.path)) {
|
||||
value = uri.path.slice(1);
|
||||
} else {
|
||||
value = uri.fsPath;
|
||||
}
|
||||
return this._normalizeWindowsPath(value);
|
||||
}
|
||||
|
||||
private _normalizeWindowsPath(path: string): string {
|
||||
return normalizeSandboxFileSystemPath(path, OperatingSystem.Windows);
|
||||
}
|
||||
|
||||
private _createNetworkPolicy(allowNetwork: boolean): NonNullable<IWindowsMxcSandboxPolicy['network']> {
|
||||
return { egress: { default: allowNetwork ? 'allow' : 'deny' }, ingress: { default: 'deny' } };
|
||||
}
|
||||
|
||||
private _quotePowerShellArgument(value: string): string {
|
||||
return `'${value.replace(/'/g, `''`)}'`;
|
||||
}
|
||||
|
||||
private _quoteWindowsCommandLineArgument(value: string): string {
|
||||
return `"${value.replace(/(\\*)"/g, '$1$1\\"').replace(/\\+$/g, '$&$&')}"`;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type { ContainerRequest, MxcProcess } from '@microsoft/mxc-sdk/v1';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { finished, type Readable, type Writable } from 'stream';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
|
||||
|
||||
/** Streams an MXC workload through the terminal, forwards termination signals, and drains output before disposal. */
|
||||
export async function runWindowsMxc(
|
||||
request: ContainerRequest,
|
||||
spawn: (request: ContainerRequest) => Promise<Pick<MxcProcess, 'standardInput' | 'standardOutput' | 'standardError' | 'wait' | 'kill' | 'dispose' | 'warnings'>> = async request => (await import('@microsoft/mxc-sdk/v1')).spawn(request),
|
||||
input: Readable = process.stdin,
|
||||
output: Writable = process.stdout,
|
||||
errorOutput: Writable = process.stderr,
|
||||
): Promise<number> {
|
||||
const store = new DisposableStore();
|
||||
try {
|
||||
const sandbox = store.add(await spawn(request));
|
||||
const kill = () => sandbox.kill();
|
||||
const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGBREAK'];
|
||||
for (const signal of signals) {
|
||||
process.on(signal, kill);
|
||||
store.add(toDisposable(() => process.off(signal, kill)));
|
||||
}
|
||||
const stdin = sandbox.standardInput;
|
||||
const stdout = sandbox.standardOutput;
|
||||
const stderr = sandbox.standardError;
|
||||
const outputFinished = [stdout, stderr].map(stream => stream ? new Promise<void>((resolve, reject) => {
|
||||
store.add(toDisposable(finished(stream, { readable: true, writable: false }, error => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
})));
|
||||
}) : undefined);
|
||||
if (stdin) {
|
||||
input.pipe(stdin);
|
||||
store.add(toDisposable(() => input.unpipe(stdin)));
|
||||
}
|
||||
stdout?.pipe(output, { end: false });
|
||||
stderr?.pipe(errorOutput, { end: false });
|
||||
store.add(toDisposable(() => {
|
||||
stdout?.unpipe(output);
|
||||
stderr?.unpipe(errorOutput);
|
||||
}));
|
||||
const [result] = await Promise.all([sandbox.wait(), Promise.all(outputFinished)]);
|
||||
for (const warning of sandbox.warnings) {
|
||||
errorOutput.write(`${warning}\n`);
|
||||
}
|
||||
return result.exitCode;
|
||||
} finally {
|
||||
store.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
try {
|
||||
const [configPath, binPath] = process.argv.slice(2);
|
||||
if (!configPath || !binPath) {
|
||||
throw new Error('Expected an MXC request file and native binary directory');
|
||||
}
|
||||
process.env['MXC_BIN_DIR'] = binPath;
|
||||
const request: ContainerRequest = JSON.parse(await readFile(configPath, 'utf8'));
|
||||
process.exitCode = await runWindowsMxc(request);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,10 @@
|
||||
|
||||
import { execFile } from 'child_process';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { getCaseInsensitive } from '../../../base/common/objects.js';
|
||||
import { win32 } from '../../../base/common/path.js';
|
||||
import { isLinux, isWindows } from '../../../base/common/platform.js';
|
||||
import { isLinux } from '../../../base/common/platform.js';
|
||||
import { getOSReleaseInfo } from '../../../base/node/osReleaseInfo.js';
|
||||
import { findExecutable } from '../../../base/node/processes.js';
|
||||
import { ISandboxDependencyStatus, ISandboxHelperService, type IWindowsMxcConfig, IWindowsMxcFilesystemPolicy, type IWindowsMxcPolicyContainment, type IWindowsMxcSandboxPolicy } from '../common/sandboxHelperService.js';
|
||||
import { ISandboxDependencyStatus, ISandboxHelperService } from '../common/sandboxHelperService.js';
|
||||
|
||||
type FindCommand = (command: string) => Promise<string | undefined>;
|
||||
type BubblewrapProbe = (command: string) => Promise<{ usable: boolean; error?: string }>;
|
||||
@@ -103,81 +101,4 @@ export class SandboxHelperService implements ISandboxHelperService {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy | undefined> {
|
||||
if (!isWindows) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { policy: { filesystem: { getAvailableToolsPolicy, getUserProfilePolicy, getTemporaryFilesPolicy } } } = await import('@microsoft/mxc-sdk/v1');
|
||||
const availableToolsPolicy = getAvailableToolsPolicy(process.env, { containerType: 'processcontainer' });
|
||||
const userProfilePolicy = getUserProfilePolicy();
|
||||
const temporaryFilesPolicy = getTemporaryFilesPolicy(process.env);
|
||||
const psHome = await this._getPSHome();
|
||||
return {
|
||||
readonlyPaths: [...new Set([...availableToolsPolicy.readonlyPaths, ...userProfilePolicy.readonlyPaths, ...temporaryFilesPolicy.readonlyPaths, ...(psHome ? [psHome] : [])])],
|
||||
readwritePaths: [...new Set([...availableToolsPolicy.readwritePaths, ...userProfilePolicy.readwritePaths, ...temporaryFilesPolicy.readwritePaths])],
|
||||
};
|
||||
}
|
||||
|
||||
async getWindowsMxcEnvironment(): Promise<string[] | undefined> {
|
||||
if (!isWindows) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const env: string[] = [];
|
||||
for (const variable of ['SystemRoot', 'PATH', 'ComSpec', 'PATHEXT', 'PSModulePath']) {
|
||||
const value = getCaseInsensitive(process.env, variable);
|
||||
if (typeof value === 'string' && value) {
|
||||
env.push(`${variable}=${value}`);
|
||||
}
|
||||
}
|
||||
const userProfile = getCaseInsensitive(process.env, 'USERPROFILE');
|
||||
if (typeof userProfile === 'string' && userProfile) {
|
||||
env.push(`USERPROFILE=${userProfile}`);
|
||||
}
|
||||
const appData = getCaseInsensitive(process.env, 'APPDATA');
|
||||
if (typeof appData === 'string' && appData) {
|
||||
env.push(`APPDATA=${appData}`);
|
||||
}
|
||||
const localAppData = this._getLocalAppData();
|
||||
if (typeof localAppData === 'string' && localAppData) {
|
||||
env.push(`LOCALAPPDATA=${localAppData}`);
|
||||
}
|
||||
|
||||
const psHome = await this._getPSHome();
|
||||
if (psHome) {
|
||||
env.push(`PSHOME=${psHome}`);
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
async buildWindowsMxcSandboxPayload(commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment: IWindowsMxcPolicyContainment = 'process'): Promise<IWindowsMxcConfig | undefined> {
|
||||
if (!isWindows) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...policy,
|
||||
command: commandLine,
|
||||
workingDirectory,
|
||||
containerName,
|
||||
containment: { type: containment },
|
||||
};
|
||||
}
|
||||
|
||||
private async _getPSHome(): Promise<string | undefined> {
|
||||
const psHome = getCaseInsensitive(process.env, 'PSHOME');
|
||||
if (typeof psHome === 'string' && psHome) {
|
||||
return psHome;
|
||||
}
|
||||
|
||||
const powerShellPath = await findExecutable('pwsh') ?? await findExecutable('powershell');
|
||||
return powerShellPath ? win32.dirname(powerShellPath) : undefined;
|
||||
}
|
||||
|
||||
private _getLocalAppData(): string | undefined {
|
||||
const localAppData = getCaseInsensitive(process.env, 'LOCALAPPDATA');
|
||||
return typeof localAppData === 'string' && localAppData ? localAppData : undefined;
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,9 @@ import { IFileService } from '../../../files/common/files.js';
|
||||
import { TestInstantiationService } from '../../../instantiation/test/common/instantiationServiceMock.js';
|
||||
import { ILogService, NullLogService } from '../../../log/common/log.js';
|
||||
import { AgentNetworkDomainSettingId } from '../../../networkFilter/common/settings.js';
|
||||
import type { ISandboxDependencyStatus, IWindowsMxcConfig, IWindowsMxcFilesystemPolicy, IWindowsMxcPolicyContainment, IWindowsMxcSandboxPolicy } from '../../common/sandboxHelperService.js';
|
||||
import { AgentSandboxEnabledValue, AgentSandboxSettingId } from '../../common/settings.js';
|
||||
import type { ISandboxDependencyStatus, } from '../../common/sandboxHelperService.js';
|
||||
import { AgentSandboxEnabledValue, AgentSandboxSettingId, isTerminalSandboxSupported } from '../../common/settings.js';
|
||||
import { ITerminalSandboxEngineHost, ITerminalSandboxRuntimeInfo, TerminalSandboxEngine } from '../../common/terminalSandboxEngine.js';
|
||||
import { IWindowsMxcTerminalSandboxRuntime, WindowsMxcTerminalSandboxRuntime } from '../../common/terminalSandboxMxcRuntime.js';
|
||||
import { type ITerminalSandboxResolvedNetworkDomains, TerminalSandboxPrerequisiteCheck, TerminalSandboxPreCheckRemediation } from '../../common/terminalSandboxService.js';
|
||||
|
||||
suite('TerminalSandboxEngine', () => {
|
||||
@@ -65,16 +64,6 @@ suite('TerminalSandboxEngine', () => {
|
||||
async del(_uri: URI): Promise<void> { }
|
||||
}
|
||||
|
||||
function buildMockWindowsMxcSandboxPayload(commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName: string = 'vscode-terminal-sandbox', containment: IWindowsMxcPolicyContainment = 'process'): IWindowsMxcConfig {
|
||||
return {
|
||||
...policy,
|
||||
command: commandLine,
|
||||
workingDirectory,
|
||||
containerName,
|
||||
containment: { type: containment },
|
||||
};
|
||||
}
|
||||
|
||||
function createHost(overrides: Partial<ITerminalSandboxEngineHost> = {}): ITerminalSandboxEngineHost & { rootsEmitter: Emitter<void> } {
|
||||
const rootsEmitter = new Emitter<void>();
|
||||
const defaultRuntime: ITerminalSandboxRuntimeInfo = {
|
||||
@@ -92,9 +81,6 @@ suite('TerminalSandboxEngine', () => {
|
||||
getWriteRoots: () => [URI.file('/workspace')],
|
||||
onDidChangeRoots: rootsEmitter.event,
|
||||
checkSandboxDependencies: (): Promise<ISandboxDependencyStatus | undefined> => Promise.resolve({ bubblewrapInstalled: true, bubblewrapUsable: true, socatInstalled: true }),
|
||||
getWindowsMxcFilesystemPolicy: (): Promise<IWindowsMxcFilesystemPolicy | undefined> => Promise.resolve(undefined),
|
||||
getWindowsMxcEnvironment: (): Promise<string[] | undefined> => Promise.resolve(undefined),
|
||||
buildWindowsMxcSandboxPayload: (commandLine, policy, workingDirectory, containerName, containment): Promise<IWindowsMxcConfig | undefined> => Promise.resolve(buildMockWindowsMxcSandboxPayload(commandLine, policy, workingDirectory, containerName, containment)),
|
||||
getSandboxSetting: <T>(settingId: string): T | undefined => sandboxSettings.has(settingId) ? sandboxSettings.get(settingId) as T : undefined,
|
||||
onDidChangeSandboxSettings: sandboxSettingsEmitter.event,
|
||||
...overrides,
|
||||
@@ -102,39 +88,6 @@ suite('TerminalSandboxEngine', () => {
|
||||
return Object.assign(host, { rootsEmitter });
|
||||
}
|
||||
|
||||
function createWindowsHost(overrides: Partial<ITerminalSandboxEngineHost> = {}): ITerminalSandboxEngineHost & { rootsEmitter: Emitter<void> } {
|
||||
return createHost({
|
||||
getOS: () => Promise.resolve(OperatingSystem.Windows),
|
||||
getRuntimeInfo: () => Promise.resolve({ appRoot: 'C:\\app', execPath: 'C:\\app\\node.exe', arch: 'x64' }),
|
||||
getUserHome: () => Promise.resolve(URI.from({ scheme: 'file', path: '/c:/Users/user' })),
|
||||
getSandboxTempDir: () => Promise.resolve(URI.from({ scheme: 'file', path: '/c:/Users/user/.test-data/tmp' })),
|
||||
getWorkspaceStorageReadRoot: () => Promise.resolve(URI.from({ scheme: 'file', path: '/c:/Users/user/workspaceStorage/workspace-id' })),
|
||||
getWriteRoots: () => [URI.from({ scheme: 'file', path: '/c:/workspace' })],
|
||||
getWindowsMxcFilesystemPolicy: () => Promise.resolve({ readonlyPaths: ['C:\\tools\\node', 'C:\\tools\\python', 'C:\\Users\\user\\AppData\\Local\\Programs\\Git'], readwritePaths: ['C:\\Users\\user\\AppData\\Local\\Temp'] }),
|
||||
getWindowsMxcEnvironment: () => Promise.resolve([
|
||||
'SystemRoot=C:\\Windows',
|
||||
'PATH=C:\\tools\\node;C:\\Windows\\System32',
|
||||
'ComSpec=C:\\Windows\\System32\\cmd.exe',
|
||||
'PATHEXT=.COM;.EXE;.BAT;.CMD;.PS1',
|
||||
'PSModulePath=C:\\Users\\user\\Documents\\PowerShell\\Modules;C:\\Program Files\\PowerShell\\Modules',
|
||||
'USERPROFILE=C:\\Users\\user',
|
||||
'APPDATA=C:\\Users\\user\\AppData\\Roaming',
|
||||
'LOCALAPPDATA=C:\\Users\\user\\AppData\\Local',
|
||||
'PSHOME=C:\\Program Files\\PowerShell\\7'
|
||||
]),
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeWindowsPathForAssert(path: string): string {
|
||||
return path.replace(/\\/g, '/').toLowerCase();
|
||||
}
|
||||
|
||||
function enableWindowsSandbox(): void {
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxEnabled, AgentSandboxEnabledValue.On);
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxAllowNetwork, true);
|
||||
}
|
||||
|
||||
setup(() => {
|
||||
createdFiles = new Map();
|
||||
createFileCount = 0;
|
||||
@@ -150,7 +103,6 @@ suite('TerminalSandboxEngine', () => {
|
||||
|
||||
instantiationService.stub(IFileService, fileService);
|
||||
instantiationService.stub(ILogService, new NullLogService());
|
||||
instantiationService.stub(IWindowsMxcTerminalSandboxRuntime, instantiationService.createInstance(WindowsMxcTerminalSandboxRuntime));
|
||||
});
|
||||
|
||||
test('runAsNode=true prefixes the wrapped command with ELECTRON_RUN_AS_NODE=1', async () => {
|
||||
@@ -166,6 +118,45 @@ suite('TerminalSandboxEngine', () => {
|
||||
ok(wrapped.command.startsWith('ELECTRON_RUN_AS_NODE=1 '), `Expected ELECTRON_RUN_AS_NODE=1 prefix. Actual: ${wrapped.command}`);
|
||||
});
|
||||
|
||||
for (const enabled of [AgentSandboxEnabledValue.Off, AgentSandboxEnabledValue.On, true]) {
|
||||
test(`Windows sandbox is unsupported with enablement ${enabled}`, async () => {
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxEnabled, enabled);
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxAllowNetwork, true);
|
||||
const host = createHost({
|
||||
getOS: async () => OperatingSystem.Windows,
|
||||
getRuntimeInfo: async () => { throw new Error('Windows must not resolve a sandbox runtime'); },
|
||||
checkSandboxDependencies: async () => { throw new Error('Windows must not probe sandbox dependencies'); },
|
||||
});
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
deepStrictEqual({
|
||||
enabled: await engine.isEnabled(),
|
||||
network: await engine.isSandboxAllowNetworkEnabled(),
|
||||
configPath: await engine.getSandboxConfigPath(),
|
||||
prerequisites: await engine.checkForSandboxingPrereqs(true),
|
||||
wrapped: await engine.wrapCommand('curl https://example.com', false, 'pwsh'),
|
||||
createdFiles: createFileCount,
|
||||
createdFolders,
|
||||
}, {
|
||||
enabled: false,
|
||||
network: false,
|
||||
configPath: undefined,
|
||||
prerequisites: { enabled: false, sandboxConfigPath: undefined, failedCheck: undefined },
|
||||
wrapped: { command: 'curl https://example.com', isSandboxWrapped: false },
|
||||
createdFiles: 0,
|
||||
createdFolders: [],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('Local sandbox toggle support follows the execution OS', () => {
|
||||
deepStrictEqual([
|
||||
isTerminalSandboxSupported(OperatingSystem.Windows),
|
||||
isTerminalSandboxSupported(OperatingSystem.Linux),
|
||||
isTerminalSandboxSupported(OperatingSystem.Macintosh),
|
||||
], [false, true, true]);
|
||||
});
|
||||
|
||||
test('runAsNode=false omits the ELECTRON_RUN_AS_NODE=1 prefix', async () => {
|
||||
const host = createHost({
|
||||
getRuntimeInfo: () => Promise.resolve({ appRoot: '/app', execPath: '/app/node', runAsNode: false }),
|
||||
@@ -659,331 +650,6 @@ suite('TerminalSandboxEngine', () => {
|
||||
strictEqual(createFileCount, 0, 'Disabled sandbox precheck should not create sandbox config files');
|
||||
});
|
||||
|
||||
test('isEnabled returns false on Windows when the unified sandbox setting is off', async () => {
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxEnabled, AgentSandboxEnabledValue.Off);
|
||||
const host = createWindowsHost();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
strictEqual(await engine.isEnabled(), false);
|
||||
strictEqual(await engine.isSandboxAllowNetworkEnabled(), false);
|
||||
strictEqual(await engine.getSandboxConfigPath(), undefined);
|
||||
});
|
||||
|
||||
test('isEnabled returns true on Windows when the unified sandbox setting is on', async () => {
|
||||
enableWindowsSandbox();
|
||||
const host = createWindowsHost();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
strictEqual(await engine.isEnabled(), true);
|
||||
strictEqual(await engine.isSandboxAllowNetworkEnabled(), true);
|
||||
});
|
||||
|
||||
test('the unified enable setting does not enable allowNetwork on Windows', async () => {
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxEnabled, AgentSandboxEnabledValue.On);
|
||||
const host = createWindowsHost();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
strictEqual(await engine.isEnabled(), true);
|
||||
strictEqual(await engine.isSandboxAllowNetworkEnabled(), false);
|
||||
});
|
||||
|
||||
test('wrapCommand uses MXC SDK runner and writes a V1 request on Windows', async () => {
|
||||
enableWindowsSandbox();
|
||||
const host = createWindowsHost();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
const wrapped = await engine.wrapCommand('echo hello', false, 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', URI.from({ scheme: 'file', path: '/c:/workspace' }));
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const config = JSON.parse(createdFiles.get(configPath)!);
|
||||
|
||||
strictEqual(wrapped.isSandboxWrapped, true);
|
||||
ok(wrapped.command.startsWith(`& 'C:\\app\\node.exe' 'C:\\app\\out\\vs\\platform\\sandbox\\node\\mxcMain.js'`), `Expected MXC SDK runner. Actual: ${wrapped.command}`);
|
||||
ok(wrapped.command.includes(` '${configPath}'`), `Expected wrapped command to pass the MXC config path. Actual: ${wrapped.command}`);
|
||||
strictEqual(config.version, undefined);
|
||||
deepStrictEqual(config.containment, { type: 'process' });
|
||||
strictEqual(config.command, '"C:\\Program Files\\PowerShell\\7\\pwsh.exe" -NoProfile -Command "echo hello"');
|
||||
strictEqual(normalizeWindowsPathForAssert(config.workingDirectory), 'c:/workspace');
|
||||
strictEqual(config.ui.disable, false);
|
||||
deepStrictEqual(config.environment, {
|
||||
SystemRoot: 'C:\\Windows',
|
||||
PATH: 'C:\\tools\\node;C:\\Windows\\System32',
|
||||
ComSpec: 'C:\\Windows\\System32\\cmd.exe',
|
||||
PATHEXT: '.COM;.EXE;.BAT;.CMD;.PS1',
|
||||
PSModulePath: 'C:\\Users\\user\\Documents\\PowerShell\\Modules;C:\\Program Files\\PowerShell\\Modules',
|
||||
USERPROFILE: 'C:\\Users\\user',
|
||||
APPDATA: 'C:\\Users\\user\\AppData\\Roaming',
|
||||
LOCALAPPDATA: 'C:\\Users\\user\\AppData\\Local',
|
||||
PSHOME: 'C:\\Program Files\\PowerShell\\7',
|
||||
});
|
||||
deepStrictEqual(config.network, { egress: { default: 'allow' }, ingress: { default: 'deny' } });
|
||||
ok(config.filesystem.readwritePaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/workspace'), 'Workspace should be writable');
|
||||
ok(config.filesystem.readwritePaths.some((path: string) => normalizeWindowsPathForAssert(path).endsWith('/.test-data/tmp')), 'Sandbox temp dir should be writable');
|
||||
ok(config.filesystem.readwritePaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/users/user/appdata/local/temp'), 'MXC temporary files policy should add host temp path to writable paths');
|
||||
ok(config.filesystem.readonlyPaths.some((path: string) => normalizeWindowsPathForAssert(path).endsWith('/.test-data/tmp')), 'Sandbox temp dir should be readable through readonly paths');
|
||||
ok(config.filesystem.readonlyPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/tools/node'), 'MXC available tools policy should add tool paths to readonly paths');
|
||||
ok(config.filesystem.readonlyPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/program files/powershell/7'), 'Resolved PowerShell executable directory should be readable');
|
||||
ok(config.filesystem.readonlyPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/users/user/appdata/local/programs/git'), 'MXC user profile policy should add user profile paths to readonly paths');
|
||||
ok(!config.filesystem.deniedPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/users/user'), 'User home should not be denied by default on Windows');
|
||||
});
|
||||
|
||||
test('wrapCommand applies Windows filesystem setting to MXC config', async () => {
|
||||
enableWindowsSandbox();
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxWindowsFileSystem, {
|
||||
allowWrite: ['C:/configured/write'],
|
||||
allowRead: ['C:/configured/read'],
|
||||
denyRead: ['C:/configured/secret'],
|
||||
});
|
||||
const host = createWindowsHost();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
await engine.wrapCommand('echo hello', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const serializedConfig = createdFiles.get(configPath)!;
|
||||
const config = JSON.parse(serializedConfig);
|
||||
|
||||
ok(serializedConfig.includes('C:\\\\configured\\\\write'), 'Configured Windows allowWrite path should be escaped in the serialized MXC config');
|
||||
ok(serializedConfig.includes('C:\\\\configured\\\\read'), 'Configured Windows allowRead path should be escaped in the serialized MXC config');
|
||||
ok(serializedConfig.includes('C:\\\\configured\\\\secret'), 'Configured Windows denyRead path should be escaped in the serialized MXC config');
|
||||
ok(config.filesystem.readwritePaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/configured/write'), 'Configured Windows allowWrite path should be writable');
|
||||
ok(config.filesystem.readonlyPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/configured/read'), 'Configured Windows allowRead path should be readonly');
|
||||
ok(config.filesystem.readwritePaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/users/user/appdata/local/temp'), 'Host temp path from Windows policy should be writable');
|
||||
ok(config.filesystem.deniedPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/configured/secret'), 'Configured Windows denyRead path should be denied');
|
||||
ok(!config.filesystem.deniedPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/users/user'), 'User home should not be denied by default on Windows');
|
||||
});
|
||||
|
||||
test('Windows sandbox config includes host read roots without granting write access', async () => {
|
||||
enableWindowsSandbox();
|
||||
const host = createWindowsHost({
|
||||
getReadRoots: () => [URI.from({ scheme: 'file', path: '/c:/session-data/session-1/attachments' })],
|
||||
});
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
await engine.wrapCommand('echo hello', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const config: IWindowsMxcConfig = JSON.parse(createdFiles.get(configPath)!);
|
||||
const attachmentPath = 'c:/session-data/session-1/attachments';
|
||||
|
||||
deepStrictEqual({
|
||||
readonly: config.filesystem?.readonlyPaths?.some(path => normalizeWindowsPathForAssert(path) === attachmentPath),
|
||||
readwrite: config.filesystem?.readwritePaths?.some(path => normalizeWindowsPathForAssert(path) === attachmentPath),
|
||||
sessionStorage: config.filesystem?.readonlyPaths?.some(path => normalizeWindowsPathForAssert(path) === 'c:/session-data/session-1'),
|
||||
}, {
|
||||
readonly: true,
|
||||
readwrite: false,
|
||||
sessionStorage: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('checkFileAccess includes host read roots on Windows', async () => {
|
||||
enableWindowsSandbox();
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxWindowsFileSystem, {
|
||||
denyRead: ['C:/session-data'],
|
||||
});
|
||||
const host = createWindowsHost({
|
||||
getReadRoots: () => [URI.from({ scheme: 'file', path: '/c:/session-data/session-1/attachments' })],
|
||||
});
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
deepStrictEqual({
|
||||
read: await engine.checkFileAccess('read', [
|
||||
'C:\\session-data\\session-1\\attachments\\image.png',
|
||||
'C:\\session-data\\session-1\\private.json',
|
||||
]),
|
||||
write: await engine.checkFileAccess('write', ['C:\\session-data\\session-1\\attachments\\image.png']),
|
||||
}, {
|
||||
read: { allowed: false, denied: ['C:\\session-data\\session-1\\private.json'] },
|
||||
write: { allowed: false, denied: ['C:\\session-data\\session-1\\attachments\\image.png'] },
|
||||
});
|
||||
});
|
||||
|
||||
test('deduplicates Windows filesystem paths regardless of case or separator', async () => {
|
||||
enableWindowsSandbox();
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxWindowsFileSystem, {
|
||||
allowWrite: ['C:/configured/write'],
|
||||
allowRead: ['C:\\configured\\read'],
|
||||
denyRead: ['C:/configured/secret', 'c:\\configured\\secret'],
|
||||
});
|
||||
const host = createWindowsHost({
|
||||
getWindowsMxcFilesystemPolicy: () => Promise.resolve({
|
||||
readwritePaths: ['c:\\configured\\write'],
|
||||
readonlyPaths: ['c:/configured/read'],
|
||||
}),
|
||||
});
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
await engine.wrapCommand('echo hello', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const config = JSON.parse(createdFiles.get(configPath)!);
|
||||
const matchingPaths = (paths: string[], expectedPath: string) => paths.filter(path => normalizeWindowsPathForAssert(path) === expectedPath);
|
||||
|
||||
deepStrictEqual({
|
||||
readwrite: matchingPaths(config.filesystem.readwritePaths, 'c:/configured/write'),
|
||||
readonly: matchingPaths(config.filesystem.readonlyPaths, 'c:/configured/read'),
|
||||
denied: matchingPaths(config.filesystem.deniedPaths, 'c:/configured/secret'),
|
||||
}, {
|
||||
readwrite: ['C:\\configured\\write'],
|
||||
readonly: ['C:\\configured\\read'],
|
||||
denied: ['C:\\configured\\secret'],
|
||||
});
|
||||
});
|
||||
|
||||
test('deduplicates resolved Windows paths regardless of case or separator', async () => {
|
||||
enableWindowsSandbox();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, createWindowsHost()));
|
||||
await engine.getOS();
|
||||
const resolveFileSystemPaths = (engine as unknown as { _resolveFileSystemPaths(paths: string[]): Promise<string[]> })._resolveFileSystemPaths.bind(engine);
|
||||
|
||||
deepStrictEqual(await resolveFileSystemPaths([
|
||||
'C:/configured/path',
|
||||
'c:\\configured\\path',
|
||||
'C:\\configured\\other-path',
|
||||
]), [
|
||||
'C:/configured/path',
|
||||
'C:\\configured\\other-path',
|
||||
]);
|
||||
});
|
||||
|
||||
test('wrapCommand leaves the wire version to the MXC SDK', async () => {
|
||||
enableWindowsSandbox();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, createWindowsHost()));
|
||||
|
||||
await engine.wrapCommand('echo hello', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const config = JSON.parse(createdFiles.get(configPath)!);
|
||||
|
||||
strictEqual(config.version, undefined);
|
||||
});
|
||||
|
||||
test('wrapCommand temporarily enables Electron-as-Node for the Windows SDK runner', async () => {
|
||||
enableWindowsSandbox();
|
||||
const host = createWindowsHost({
|
||||
getRuntimeInfo: () => Promise.resolve({ appRoot: 'C:\\app', execPath: 'C:\\app\\Code.exe', runAsNode: true }),
|
||||
});
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
const wrapped = await engine.wrapCommand('echo hello', false, 'pwsh');
|
||||
|
||||
ok(wrapped.command.includes('$env:ELECTRON_RUN_AS_NODE = \'1\'; & \'C:\\app\\Code.exe\''));
|
||||
ok(wrapped.command.includes('finally { $env:ELECTRON_RUN_AS_NODE = $vscodeSandboxRunAsNode }'));
|
||||
});
|
||||
|
||||
test('wrapCommand denies egress and ingress when Windows network access is disabled', async () => {
|
||||
enableWindowsSandbox();
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxAllowNetwork, false);
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, createWindowsHost()));
|
||||
|
||||
await engine.wrapCommand('echo hello', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath);
|
||||
|
||||
deepStrictEqual(JSON.parse(createdFiles.get(configPath)!).network, {
|
||||
egress: { default: 'deny' },
|
||||
ingress: { default: 'deny' },
|
||||
});
|
||||
});
|
||||
|
||||
test('preserves Windows filesystem symlink paths and resolves their targets when writing MXC config', async () => {
|
||||
enableWindowsSandbox();
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxWindowsFileSystem, {
|
||||
allowWrite: ['C:\\configured\\write-link'],
|
||||
allowRead: ['C:\\configured\\read-link'],
|
||||
denyRead: ['C:\\configured\\secret-link'],
|
||||
});
|
||||
fileService.setRealpath('/c:/workspace-link', '/c:/real/workspace');
|
||||
fileService.setRealpath('/c:/configured/write-link', '/c:/real/configured-write');
|
||||
fileService.setRealpath('/c:/configured/read-link', '/c:/real/configured-read');
|
||||
fileService.setRealpath('/c:/configured/secret-link', '/c:/real/configured-secret');
|
||||
fileService.setRealpath('/c:/tools/node', '/c:/real/tools-node');
|
||||
const host = createWindowsHost({
|
||||
getWriteRoots: () => [URI.from({ scheme: 'file', path: '/c:/workspace-link' })],
|
||||
});
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
await engine.wrapCommand('echo hello', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const config = JSON.parse(createdFiles.get(configPath)!);
|
||||
|
||||
ok(config.filesystem.readwritePaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/workspace-link'), 'Workspace write root symlink should be preserved on Windows');
|
||||
ok(config.filesystem.readwritePaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/real/workspace'), 'Workspace write root symlink target should be included on Windows');
|
||||
ok(config.filesystem.readwritePaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/configured/write-link'), 'Configured Windows allowWrite symlink should be preserved');
|
||||
ok(config.filesystem.readwritePaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/real/configured-write'), 'Configured Windows allowWrite symlink target should be included');
|
||||
ok(config.filesystem.readonlyPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/configured/read-link'), 'Configured Windows allowRead symlink should be preserved');
|
||||
ok(config.filesystem.readonlyPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/real/configured-read'), 'Configured Windows allowRead symlink target should be included');
|
||||
ok(config.filesystem.readonlyPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/tools/node'), 'Windows policy readonly symlink should be preserved');
|
||||
ok(config.filesystem.readonlyPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/real/tools-node'), 'Windows policy readonly symlink target should be included');
|
||||
ok(config.filesystem.deniedPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/configured/secret-link'), 'Configured Windows denyRead symlink should be preserved');
|
||||
ok(config.filesystem.deniedPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/real/configured-secret'), 'Configured Windows denyRead symlink target should be included');
|
||||
});
|
||||
|
||||
test('wrapCommand uses unpacked MXC binaries on Windows', async () => {
|
||||
enableWindowsSandbox();
|
||||
const host = createWindowsHost({
|
||||
getRuntimeInfo: () => Promise.resolve({ appRoot: 'C:\\app', execPath: 'C:\\app\\node.exe', arch: 'arm64', nativeModulesDir: 'node_modules.asar.unpacked' }),
|
||||
});
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
const wrapped = await engine.wrapCommand('echo hello', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const config = JSON.parse(createdFiles.get(configPath)!);
|
||||
|
||||
strictEqual(wrapped.command, `& 'C:\\app\\node.exe' 'C:\\app\\out\\vs\\platform\\sandbox\\node\\mxcMain.js' '${configPath}' 'C:\\app\\node_modules.asar.unpacked\\@microsoft\\mxc-sdk\\bin'`);
|
||||
strictEqual(normalizeWindowsPathForAssert(config.workingDirectory), 'c:/workspace');
|
||||
});
|
||||
|
||||
test('wrapCommand rewrites MXC config when Windows command changes', async () => {
|
||||
enableWindowsSandbox();
|
||||
const host = createWindowsHost();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
await engine.wrapCommand('echo first', false, 'C:\\Program Files\\PowerShell\\7\\pwsh.exe');
|
||||
let configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const firstCommandLine = JSON.parse(createdFiles.get(configPath)!).command;
|
||||
strictEqual(firstCommandLine, '"C:\\Program Files\\PowerShell\\7\\pwsh.exe" -NoProfile -Command "echo first"');
|
||||
|
||||
await engine.wrapCommand('echo second', false, 'C:\\Program Files\\PowerShell\\7\\pwsh.exe');
|
||||
configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const secondCommandLine = JSON.parse(createdFiles.get(configPath)!).command;
|
||||
strictEqual(secondCommandLine, '"C:\\Program Files\\PowerShell\\7\\pwsh.exe" -NoProfile -Command "echo second"');
|
||||
});
|
||||
|
||||
test('allowNetwork maps to MXC allow network config on Windows', async () => {
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxEnabled, AgentSandboxEnabledValue.On);
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxAllowNetwork, true);
|
||||
const host = createWindowsHost();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
await engine.wrapCommand('curl https://example.com', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const config = JSON.parse(createdFiles.get(configPath)!);
|
||||
|
||||
deepStrictEqual(config.network, { egress: { default: 'allow' }, ingress: { default: 'deny' } });
|
||||
});
|
||||
|
||||
test('Windows MXC config ignores unsupported network host lists', async () => {
|
||||
enableWindowsSandbox();
|
||||
setSandboxSetting(AgentNetworkDomainSettingId.AllowedNetworkDomains, ['example.com']);
|
||||
setSandboxSetting(AgentNetworkDomainSettingId.DeniedNetworkDomains, ['blocked.example.com']);
|
||||
const host = createWindowsHost();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
await engine.wrapCommand('curl https://example.com', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const config = JSON.parse(createdFiles.get(configPath)!);
|
||||
|
||||
deepStrictEqual(config.network, { egress: { default: 'allow' }, ingress: { default: 'deny' } });
|
||||
});
|
||||
|
||||
test('uses OS-specific filesystem absolute path detection', async () => {
|
||||
const linuxEngine = store.add(instantiationService.createInstance(TerminalSandboxEngine, createHost()));
|
||||
await linuxEngine.getOS();
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { deepStrictEqual, rejects } from 'assert';
|
||||
import { finished, PassThrough, Readable } from 'stream';
|
||||
import { promisify } from 'util';
|
||||
import type { ContainerRequest } from '@microsoft/mxc-sdk/v1';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
import { runWindowsMxc } from '../../node/mxcMain.js';
|
||||
|
||||
const waitForStream = promisify(finished);
|
||||
const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGBREAK'];
|
||||
|
||||
suite('Windows MXC SDK runner', () => {
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
test('forwards the V1 request, streams stdio, and preserves the exit code', async () => {
|
||||
const input = Readable.from(['terminal input']);
|
||||
const output = new PassThrough();
|
||||
const errorOutput = new PassThrough();
|
||||
const stdin = new PassThrough();
|
||||
const stdout = new PassThrough();
|
||||
const stderr = new PassThrough();
|
||||
let receivedRequest: ContainerRequest | undefined;
|
||||
let receivedInput = '';
|
||||
let receivedOutput = '';
|
||||
let receivedError = '';
|
||||
let disposed = false;
|
||||
stdin.on('data', chunk => receivedInput += chunk.toString());
|
||||
output.on('data', chunk => receivedOutput += chunk.toString());
|
||||
errorOutput.on('data', chunk => receivedError += chunk.toString());
|
||||
const request: ContainerRequest = {
|
||||
command: '"pwsh.exe" -NoProfile -Command "echo hello"',
|
||||
workingDirectory: 'C:\\workspace',
|
||||
environment: { PATH: 'C:\\tools', EXAMPLE: 'value=with=equals' },
|
||||
filesystem: { readonlyPaths: ['C:\\tools'], deniedPaths: ['C:\\secret'] },
|
||||
network: { egress: { default: 'deny' }, ingress: { default: 'deny' } },
|
||||
ui: { disable: false, clipboard: 'none', allowInputInjection: false },
|
||||
};
|
||||
const listenerCounts = signals.map(signal => process.listenerCount(signal));
|
||||
try {
|
||||
const exitCode = await runWindowsMxc(request, async value => {
|
||||
receivedRequest = value;
|
||||
return {
|
||||
standardInput: stdin,
|
||||
standardOutput: stdout,
|
||||
standardError: stderr,
|
||||
warnings: ['sandbox warning'],
|
||||
wait: async () => {
|
||||
await waitForStream(stdin);
|
||||
setImmediate(() => {
|
||||
stdout.end('terminal output');
|
||||
setImmediate(() => stderr.end('terminal error'));
|
||||
});
|
||||
return { exitCode: 7, timedOut: false };
|
||||
},
|
||||
kill: () => { },
|
||||
dispose: () => {
|
||||
disposed = true;
|
||||
stdout.destroy();
|
||||
stderr.destroy();
|
||||
},
|
||||
};
|
||||
}, input, output, errorOutput);
|
||||
deepStrictEqual({
|
||||
receivedRequest, receivedInput, receivedOutput, receivedError, exitCode, disposed,
|
||||
listenerCounts: signals.map(signal => process.listenerCount(signal)),
|
||||
}, {
|
||||
receivedRequest: request,
|
||||
receivedInput: 'terminal input',
|
||||
receivedOutput: 'terminal output',
|
||||
receivedError: 'terminal errorsandbox warning\n',
|
||||
exitCode: 7,
|
||||
disposed: true,
|
||||
listenerCounts,
|
||||
});
|
||||
} finally {
|
||||
for (const stream of [input, output, errorOutput, stdin, stdout, stderr]) {
|
||||
stream.destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('surfaces launch failures instead of running outside the sandbox', async () => {
|
||||
await rejects(runWindowsMxc({ command: 'echo hello' }, async () => {
|
||||
throw new Error('Sandbox unavailable');
|
||||
}), /Sandbox unavailable/);
|
||||
});
|
||||
|
||||
for (const signal of signals) {
|
||||
test(`forwards ${signal} to the sandbox and removes the handler`, async () => {
|
||||
let killCount = 0;
|
||||
let disposed = false;
|
||||
const listenerCounts = signals.map(signal => process.listenerCount(signal));
|
||||
const exitCode = await runWindowsMxc({ command: 'echo hello' }, async () => ({
|
||||
standardInput: null,
|
||||
standardOutput: null,
|
||||
standardError: null,
|
||||
warnings: [],
|
||||
wait: async () => {
|
||||
process.emit(signal);
|
||||
return { exitCode: 1, timedOut: false };
|
||||
},
|
||||
kill: () => { killCount++; },
|
||||
dispose: () => { disposed = true; },
|
||||
}));
|
||||
deepStrictEqual({
|
||||
killCount, disposed, exitCode,
|
||||
listenerCounts: signals.map(signal => process.listenerCount(signal)),
|
||||
}, { killCount: 1, disposed: true, exitCode: 1, listenerCounts });
|
||||
});
|
||||
}
|
||||
|
||||
test('surfaces output errors and cleans up stream and signal handlers', async () => {
|
||||
const stdout = new PassThrough();
|
||||
const output = new PassThrough();
|
||||
let disposed = false;
|
||||
const listenerCounts = signals.map(signal => process.listenerCount(signal));
|
||||
const streamListenerCounts = ['end', 'finish', 'error', 'close'].map(event => stdout.listenerCount(event));
|
||||
try {
|
||||
await rejects(runWindowsMxc({ command: 'echo hello' }, async () => ({
|
||||
standardInput: null,
|
||||
standardOutput: stdout,
|
||||
standardError: null,
|
||||
warnings: [],
|
||||
wait: async () => {
|
||||
setImmediate(() => stdout.destroy(new Error('Output failed')));
|
||||
return { exitCode: 0, timedOut: false };
|
||||
},
|
||||
kill: () => { },
|
||||
dispose: () => { disposed = true; },
|
||||
}), Readable.from([]), output), /Output failed/);
|
||||
deepStrictEqual({
|
||||
disposed,
|
||||
listenerCounts: signals.map(signal => process.listenerCount(signal)),
|
||||
streamListenerCounts: ['end', 'finish', 'error', 'close'].map(event => stdout.listenerCount(event)),
|
||||
}, { disposed: true, listenerCounts, streamListenerCounts });
|
||||
} finally {
|
||||
stdout.destroy();
|
||||
output.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
test('disposes the process and signal handlers when execution fails', async () => {
|
||||
let disposed = false;
|
||||
const listenerCounts = signals.map(signal => process.listenerCount(signal));
|
||||
await rejects(runWindowsMxc({ command: 'echo hello' }, async () => ({
|
||||
standardInput: null,
|
||||
standardOutput: null,
|
||||
standardError: null,
|
||||
warnings: [],
|
||||
wait: async () => { throw new Error('Execution failed'); },
|
||||
kill: () => { },
|
||||
dispose: () => { disposed = true; },
|
||||
})), /Execution failed/);
|
||||
deepStrictEqual({
|
||||
disposed,
|
||||
listenerCounts: signals.map(signal => process.listenerCount(signal)),
|
||||
}, { disposed: true, listenerCounts });
|
||||
});
|
||||
});
|
||||
@@ -4,31 +4,12 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { deepStrictEqual, strictEqual } from 'assert';
|
||||
import type { ContainerRequest } from '@microsoft/mxc-sdk/v1';
|
||||
import { isWindows } from '../../../../base/common/platform.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
import { SandboxHelperService } from '../../node/sandboxHelper.js';
|
||||
|
||||
suite('SandboxHelperService', () => {
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
test('builds a V1 container request without a caller-selected wire version', async () => {
|
||||
const policy = {
|
||||
filesystem: { readonlyPaths: ['C:\\tools'], deniedPaths: ['C:\\secret'] },
|
||||
network: { egress: { default: 'deny' as const }, ingress: { default: 'deny' as const } },
|
||||
ui: { disable: false, clipboard: 'none' as const, allowInputInjection: false },
|
||||
};
|
||||
const request: ContainerRequest | undefined = await new SandboxHelperService().buildWindowsMxcSandboxPayload('echo hello', policy, 'C:\\workspace', 'test-container');
|
||||
|
||||
deepStrictEqual(request, isWindows ? {
|
||||
...policy,
|
||||
command: 'echo hello',
|
||||
workingDirectory: 'C:\\workspace',
|
||||
containerName: 'test-container',
|
||||
containment: { type: 'process' },
|
||||
} : undefined);
|
||||
});
|
||||
|
||||
test('does not inspect sandbox dependencies on non-Linux platforms', async () => {
|
||||
let findCalled = false;
|
||||
const result = await SandboxHelperService.checkSandboxDependenciesWith(async () => {
|
||||
|
||||
@@ -1627,7 +1627,7 @@ configurationRegistry.registerConfiguration({
|
||||
},
|
||||
[AgentHostCustomTerminalToolEnabledSettingId]: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('chat.agentHost.customTerminalTool.enabled', "When enabled, Copilot SDK sessions use the Agent Host terminal tool override instead of the SDK's default terminal behavior."),
|
||||
description: nls.localize('chat.agentHost.customTerminalTool.enabled', "When enabled, Copilot SDK sessions use the Agent Host terminal tool override instead of the SDK's default terminal behavior. Not supported on Windows, where sessions use the SDK's terminal and sandbox runtime."),
|
||||
default: false,
|
||||
tags: ['experimental', 'advanced'],
|
||||
policy: {
|
||||
|
||||
@@ -31,7 +31,9 @@ import { MarshalledId } from '../../../../../../base/common/marshallingIds.js';
|
||||
import { Schemas } from '../../../../../../base/common/network.js';
|
||||
import { mixin } from '../../../../../../base/common/objects.js';
|
||||
import { autorun, constObservable, derived, derivedOpts, IObservable, ISettableObservable, ITransaction, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from '../../../../../../base/common/observable.js';
|
||||
import { isMacintosh, isWeb } from '../../../../../../base/common/platform.js';
|
||||
import { isMacintosh, isWeb, OS } from '../../../../../../base/common/platform.js';
|
||||
import { isTerminalSandboxSupported } from '../../../../../../platform/sandbox/common/settings.js';
|
||||
import { IRemoteAgentService } from '../../../../../services/remote/common/remoteAgentService.js';
|
||||
import { isEqual } from '../../../../../../base/common/resources.js';
|
||||
import { ScrollbarVisibility } from '../../../../../../base/common/scrollable.js';
|
||||
import { ThemeIcon } from '../../../../../../base/common/themables.js';
|
||||
@@ -782,6 +784,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
private modeWidget: ModePickerActionItem | undefined;
|
||||
private attachContextActionViewItem: MenuEntryActionViewItem | undefined;
|
||||
private permissionWidget: PermissionPickerActionItem | undefined;
|
||||
private _localSandboxSupported = false;
|
||||
private readonly permissionWidgetDisposeListener = this._register(new MutableDisposable<IDisposable>());
|
||||
private readonly overflowPickerWidget = this._register(new MutableDisposable<IDisposable>());
|
||||
private sessionTargetWidget: SessionTypePickerActionItem | undefined;
|
||||
@@ -1020,8 +1023,16 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
@IChatPetService private readonly chatPetService: IChatPetService,
|
||||
@IActionViewItemService private readonly actionViewItemService: IActionViewItemService,
|
||||
@IAccountPolicyGateService private readonly accountPolicyGateService: IAccountPolicyGateService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
super();
|
||||
remoteAgentService.getEnvironment().then(environment => {
|
||||
if (this._store.isDisposed) {
|
||||
return;
|
||||
}
|
||||
this._localSandboxSupported = isTerminalSandboxSupported(environment?.os ?? OS);
|
||||
this.permissionWidget?.refresh();
|
||||
}, onUnexpectedError);
|
||||
this._modelSelectionDiagnostics = new ChatModelSelectionDiagnostics(this.logService, this.storageService, () => ({
|
||||
surface: 'workbench',
|
||||
location: this.location,
|
||||
@@ -4081,7 +4092,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
}
|
||||
this.permissionWidget?.refresh();
|
||||
},
|
||||
isSandboxToggleApplicable: () => this.getEffectiveSessionType(this.getCurrentSessionResource()) === SessionType.Local,
|
||||
isSandboxToggleApplicable: () => this._localSandboxSupported && this.getEffectiveSessionType(this.getCurrentSessionResource()) === SessionType.Local,
|
||||
};
|
||||
const createPicker = () => this.instantiationService.createInstance(PermissionPickerActionItem, action, delegate, getSecondaryPickerOptions(action.id));
|
||||
secondaryOverflowPickerHandlers.set(action.id, anchor => showOverflowPicker(createPicker, anchor));
|
||||
|
||||
@@ -57,7 +57,6 @@ export const enum TerminalContribSettingId {
|
||||
AgentSandboxRetryWithAllowNetworkRequests = AgentSandboxSettingId.AgentSandboxRetryWithAllowNetworkRequests,
|
||||
AgentSandboxLinuxFileSystem = TerminalChatAgentToolsSettingId.AgentSandboxLinuxFileSystem,
|
||||
AgentSandboxMacFileSystem = TerminalChatAgentToolsSettingId.AgentSandboxMacFileSystem,
|
||||
AgentSandboxWindowsFileSystem = TerminalChatAgentToolsSettingId.AgentSandboxWindowsFileSystem,
|
||||
}
|
||||
|
||||
// HACK: Export some context key strings from `terminalContrib/` that are depended upon elsewhere.
|
||||
|
||||
+1
-4
@@ -36,13 +36,11 @@ import { GetTaskOutputTool, GetTaskOutputToolData } from './tools/task/getTaskOu
|
||||
import { RunTaskTool, RunTaskToolData } from './tools/task/runTaskTool.js';
|
||||
import { registerTerminalCompressors } from './tools/terminalOutputCompressor.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js';
|
||||
import { IWindowsMxcTerminalSandboxRuntime, WindowsMxcTerminalSandboxRuntime } from '../../../../../platform/sandbox/common/terminalSandboxMxcRuntime.js';
|
||||
import { ITerminalSandboxService, TerminalSandboxService } from '../common/terminalSandboxService.js';
|
||||
import { isNumber } from '../../../../../base/common/types.js';
|
||||
|
||||
// #region Services
|
||||
|
||||
registerSingleton(IWindowsMxcTerminalSandboxRuntime, WindowsMxcTerminalSandboxRuntime, InstantiationType.Delayed);
|
||||
registerSingleton(ITerminalSandboxService, TerminalSandboxService, InstantiationType.Delayed);
|
||||
|
||||
// #endregion Services
|
||||
@@ -155,8 +153,7 @@ export class ChatAgentToolsContribution extends Disposable implements IWorkbench
|
||||
e.affectsConfiguration(AgentNetworkDomainSettingId.AllowedNetworkDomains) ||
|
||||
e.affectsConfiguration(AgentNetworkDomainSettingId.DeniedNetworkDomains) ||
|
||||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxLinuxFileSystem) ||
|
||||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxMacFileSystem) ||
|
||||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxWindowsFileSystem)
|
||||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxMacFileSystem)
|
||||
) {
|
||||
this._registerRunInTerminalTool();
|
||||
}
|
||||
|
||||
+3
-3
@@ -33,14 +33,14 @@ export class SandboxOutputAnalyzer extends Disposable implements IOutputAnalyzer
|
||||
}
|
||||
|
||||
const os = await this._sandboxService.getOS();
|
||||
if (os === OperatingSystem.Windows) {
|
||||
return undefined;
|
||||
}
|
||||
let fileSystemSetting: TerminalChatAgentToolsSettingId;
|
||||
switch (os) {
|
||||
case OperatingSystem.Linux:
|
||||
fileSystemSetting = TerminalChatAgentToolsSettingId.AgentSandboxLinuxFileSystem;
|
||||
break;
|
||||
case OperatingSystem.Windows:
|
||||
fileSystemSetting = TerminalChatAgentToolsSettingId.AgentSandboxWindowsFileSystem;
|
||||
break;
|
||||
default:
|
||||
fileSystemSetting = TerminalChatAgentToolsSettingId.AgentSandboxMacFileSystem;
|
||||
break;
|
||||
|
||||
-1
@@ -23,7 +23,6 @@ export const SANDBOX_SETTING_KEYS: readonly string[] = [
|
||||
AgentSandboxSettingId.AgentSandboxUserConfiguredPaths,
|
||||
AgentSandboxSettingId.AgentSandboxLinuxFileSystem,
|
||||
AgentSandboxSettingId.AgentSandboxMacFileSystem,
|
||||
AgentSandboxSettingId.AgentSandboxWindowsFileSystem,
|
||||
AgentSandboxSettingId.AgentSandboxAdvancedRuntime,
|
||||
AgentNetworkDomainSettingId.AllowedNetworkDomains,
|
||||
AgentNetworkDomainSettingId.DeniedNetworkDomains,
|
||||
|
||||
+1
-37
@@ -52,7 +52,6 @@ export const enum TerminalChatAgentToolsSettingId {
|
||||
OutputLocation = 'chat.tools.terminal.outputLocation',
|
||||
AgentSandboxLinuxFileSystem = 'chat.agent.sandbox.fileSystem.linux',
|
||||
AgentSandboxMacFileSystem = 'chat.agent.sandbox.fileSystem.mac',
|
||||
AgentSandboxWindowsFileSystem = 'chat.agent.sandbox.fileSystem.windows',
|
||||
AgentSandboxAdvancedRuntime = 'chat.agent.sandbox.advanced.runtime',
|
||||
PreventShellHistory = 'chat.tools.terminal.preventShellHistory',
|
||||
EnforceTimeoutFromModel = 'chat.tools.terminal.enforceTimeoutFromModel',
|
||||
@@ -532,7 +531,7 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary<IConfigurati
|
||||
managedSettingsPresentation: read => SandboxSettingsResolutionHelper.resolveEnabled(undefined, read(COPILOT_SANDBOX_ENABLED_KEY) === true),
|
||||
order: 10,
|
||||
keywords: ['Sandbox', 'sandboxing'],
|
||||
markdownDescription: localize('agentSandbox.enabledSetting', "Controls whether agent mode uses sandboxing to restrict what tools can do. When enabled, tools like the terminal are run in a sandboxed environment to limit access to the system."),
|
||||
markdownDescription: localize('agentSandbox.enabledSetting', "Controls whether agent mode uses sandboxing to restrict what tools can do. When enabled, tools like the terminal are run in a sandboxed environment to limit access to the system. The Local harness supports sandboxing on Linux and macOS only. The Copilot Agent Host harness uses the SDK runtime's sandbox, including on supported Windows hosts."),
|
||||
type: 'string',
|
||||
enum: [AgentSandboxEnabledValue.Off, AgentSandboxEnabledValue.On],
|
||||
enumDescriptions: [
|
||||
@@ -829,41 +828,6 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary<IConfigurati
|
||||
tags: ['preview'],
|
||||
restricted: true,
|
||||
},
|
||||
[TerminalChatAgentToolsSettingId.AgentSandboxWindowsFileSystem]: {
|
||||
order: 90,
|
||||
keywords: ['Sandbox', 'sandboxing'],
|
||||
markdownDeprecationMessage: localize('agentSandbox.fileSystem.deprecated', "This setting will be deprecated soon. For the Copilot Agent Host sandbox, use {0} instead.", `\`#${AgentSandboxSettingId.AgentSandboxUserConfiguredPaths}#\``),
|
||||
deprecationMessageShowInSettings: true,
|
||||
markdownDescription: localize('agentSandbox.windowsFileSystemSetting', "Note: this setting is applicable only when {0} is enabled. Controls file system access in sandbox on Windows. Paths do not support glob patterns, only literal paths (ex: C:\\src, C:\\Users\\me\\.ssh, .env).", `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``),
|
||||
type: 'object',
|
||||
properties: {
|
||||
denyRead: {
|
||||
type: 'array',
|
||||
description: localize('agentSandbox.windowsFileSystemSetting.denyRead', "Array of paths to deny access. Leave empty to allow reading all paths."),
|
||||
items: { type: 'string' },
|
||||
default: []
|
||||
},
|
||||
allowRead: {
|
||||
type: 'array',
|
||||
description: localize('agentSandbox.windowsFileSystemSetting.allowRead', "Array of additional paths to allow read-only access. Takes precedence over denyRead."),
|
||||
items: { type: 'string' },
|
||||
default: []
|
||||
},
|
||||
allowWrite: {
|
||||
type: 'array',
|
||||
description: localize('agentSandbox.windowsFileSystemSetting.allowWrite', "Array of additional paths to allow read/write access. Leave empty to disallow writes outside the workspace folders, workspace storage folder, and sandbox temp directory."),
|
||||
items: { type: 'string' },
|
||||
default: []
|
||||
}
|
||||
},
|
||||
default: {
|
||||
denyRead: [],
|
||||
allowRead: [],
|
||||
allowWrite: []
|
||||
},
|
||||
tags: ['preview'],
|
||||
restricted: true,
|
||||
},
|
||||
[TerminalChatAgentToolsSettingId.AgentSandboxAdvancedRuntime]: {
|
||||
markdownDescription: localize('agentSandbox.runtimeSetting', "Note: this setting is applicable only when {0} is enabled. Key/value pairs are passed through to the root of the sandbox runtime configuration.", `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``),
|
||||
markdownDeprecationMessage: localize('agentSandbox.runtimeSetting.deprecated', "This setting will be deprecated soon. It does not apply to the Copilot Agent Host sandbox."),
|
||||
|
||||
+3
-40
@@ -11,7 +11,6 @@ import { Disposable, DisposableStore } from '../../../../../base/common/lifecycl
|
||||
import { FileAccess } from '../../../../../base/common/network.js';
|
||||
import { dirname } from '../../../../../base/common/path.js';
|
||||
import { OperatingSystem, OS } from '../../../../../base/common/platform.js';
|
||||
import { arch } from '../../../../../base/common/process.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { localize } from '../../../../../nls.js';
|
||||
import { IConfigurationChangeEvent, IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
|
||||
@@ -22,7 +21,7 @@ import { ILogService } from '../../../../../platform/log/common/log.js';
|
||||
import { IProductService } from '../../../../../platform/product/common/productService.js';
|
||||
import { IRemoteAgentEnvironment } from '../../../../../platform/remote/common/remoteAgentEnvironment.js';
|
||||
import { SANDBOX_HELPER_CHANNEL_NAME, SandboxHelperChannelClient } from '../../../../../platform/sandbox/common/sandboxHelperIpc.js';
|
||||
import { ISandboxDependencyStatus, ISandboxHelperService, type IWindowsMxcConfig, IWindowsMxcFilesystemPolicy, type IWindowsMxcPolicyContainment, type IWindowsMxcSandboxPolicy } from '../../../../../platform/sandbox/common/sandboxHelperService.js';
|
||||
import { ISandboxDependencyStatus, ISandboxHelperService } from '../../../../../platform/sandbox/common/sandboxHelperService.js';
|
||||
import { ITerminalSandboxEngineHost, ITerminalSandboxRuntimeInfo, TerminalSandboxEngine } from '../../../../../platform/sandbox/common/terminalSandboxEngine.js';
|
||||
import { readSandboxSetting, SANDBOX_SETTING_KEYS } from './sandboxSettingsReader.js';
|
||||
import { ITerminalSandboxService, TerminalSandboxPreCheckRemediation, type ISandboxDependencyInstallOptions, type ISandboxDependencyInstallResult, type ITerminalSandboxCommand, type ITerminalSandboxFileAccessCheckResult, type ITerminalSandboxPrecheckInputs, type ITerminalSandboxPrerequisiteCheckResult, type ITerminalSandboxResolvedNetworkDomains, type ITerminalSandboxWrapResult, type TerminalSandboxFileAccessPermission } from '../../../../../platform/sandbox/common/terminalSandboxService.js';
|
||||
@@ -92,9 +91,6 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
|
||||
getWriteRoots: () => this._workspaceContextService.getWorkspace().folders.map(folder => folder.uri),
|
||||
onDidChangeRoots: this._onDidChangeRoots.event,
|
||||
checkSandboxDependencies: () => this._resolveSandboxDependencyStatus(),
|
||||
getWindowsMxcFilesystemPolicy: () => this._resolveWindowsMxcFilesystemPolicy(),
|
||||
getWindowsMxcEnvironment: () => this._resolveWindowsMxcEnvironment(),
|
||||
buildWindowsMxcSandboxPayload: (commandLine, policy, workingDirectory, containerName, containment) => this._resolveWindowsMxcSandboxPayload(commandLine, policy, workingDirectory, containerName, containment),
|
||||
getSandboxSetting: <T>(settingId: string): T | undefined => this._readSandboxSetting<T>(settingId),
|
||||
onDidChangeSandboxSettings: Event.map(onDidChangeSandboxSettings, () => undefined),
|
||||
};
|
||||
@@ -182,7 +178,7 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
|
||||
const remoteEnv = await this._resolveRemoteEnv();
|
||||
if (remoteEnv) {
|
||||
// Remote workbench: server resolves a real `node` binary, no env prefix needed.
|
||||
return { appRoot: remoteEnv.os === OperatingSystem.Windows ? this._toWindowsPath(remoteEnv.appRoot) : remoteEnv.appRoot.path, execPath: remoteEnv.execPath, runAsNode: false, arch: remoteEnv.arch, nativeModulesDir: 'node_modules' };
|
||||
return { appRoot: remoteEnv.os === OperatingSystem.Windows ? this._toWindowsPath(remoteEnv.appRoot) : remoteEnv.appRoot.path, execPath: remoteEnv.execPath, runAsNode: false, nativeModulesDir: 'node_modules' };
|
||||
}
|
||||
// Local workbench: app root is local and exec path points at the Electron binary,
|
||||
// so the engine must prefix `ELECTRON_RUN_AS_NODE=1` when invoking it.
|
||||
@@ -190,7 +186,7 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
|
||||
const localAppRoot = OS === OperatingSystem.Windows ? dirname(localAppRootUri.fsPath) : dirname(localAppRootUri.path);
|
||||
const nativeEnv = this._environmentService as IEnvironmentService & { execPath?: string };
|
||||
const nativeModulesDir = this._environmentService.isBuilt ? 'node_modules.asar.unpacked' : 'node_modules';
|
||||
return { appRoot: localAppRoot, execPath: nativeEnv.execPath, runAsNode: true, arch, nativeModulesDir };
|
||||
return { appRoot: localAppRoot, execPath: nativeEnv.execPath, runAsNode: true, nativeModulesDir };
|
||||
}
|
||||
|
||||
private _toWindowsPath(uri: URI): string {
|
||||
@@ -254,39 +250,6 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
|
||||
return this._sandboxHelperService.checkSandboxDependencies();
|
||||
}
|
||||
|
||||
private async _resolveWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy | undefined> {
|
||||
const connection = this._remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
return connection.withChannel(SANDBOX_HELPER_CHANNEL_NAME, channel => {
|
||||
const sandboxHelper = new SandboxHelperChannelClient(channel);
|
||||
return sandboxHelper.getWindowsMxcFilesystemPolicy();
|
||||
});
|
||||
}
|
||||
return this._sandboxHelperService.getWindowsMxcFilesystemPolicy();
|
||||
}
|
||||
|
||||
private async _resolveWindowsMxcEnvironment(): Promise<string[] | undefined> {
|
||||
const connection = this._remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
return connection.withChannel(SANDBOX_HELPER_CHANNEL_NAME, channel => {
|
||||
const sandboxHelper = new SandboxHelperChannelClient(channel);
|
||||
return sandboxHelper.getWindowsMxcEnvironment();
|
||||
});
|
||||
}
|
||||
return this._sandboxHelperService.getWindowsMxcEnvironment();
|
||||
}
|
||||
|
||||
private async _resolveWindowsMxcSandboxPayload(commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment): Promise<IWindowsMxcConfig | undefined> {
|
||||
const connection = this._remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
return connection.withChannel(SANDBOX_HELPER_CHANNEL_NAME, channel => {
|
||||
const sandboxHelper = new SandboxHelperChannelClient(channel);
|
||||
return sandboxHelper.buildWindowsMxcSandboxPayload(commandLine, policy, workingDirectory, containerName, containment);
|
||||
});
|
||||
}
|
||||
return this._sandboxHelperService.buildWindowsMxcSandboxPayload(commandLine, policy, workingDirectory, containerName, containment);
|
||||
}
|
||||
|
||||
// ---- workbench-only flows -----------------------------------------------
|
||||
|
||||
async installMissingSandboxDependencies(missingDependencies: string[], sessionResource: URI | undefined, token: CancellationToken, options: ISandboxDependencyInstallOptions): Promise<ISandboxDependencyInstallResult> {
|
||||
|
||||
+17
-91
@@ -29,8 +29,7 @@ import { IRemoteAgentEnvironment } from '../../../../../../platform/remote/commo
|
||||
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, WorkbenchState } from '../../../../../../platform/workspace/common/workspace.js';
|
||||
import { testWorkspace } from '../../../../../../platform/workspace/test/common/testWorkspace.js';
|
||||
import { ILifecycleService } from '../../../../../services/lifecycle/common/lifecycle.js';
|
||||
import { ISandboxDependencyStatus, ISandboxHelperService, type IWindowsMxcConfig, IWindowsMxcFilesystemPolicy, type IWindowsMxcPolicyContainment, type IWindowsMxcSandboxPolicy } from '../../../../../../platform/sandbox/common/sandboxHelperService.js';
|
||||
import { IWindowsMxcTerminalSandboxRuntime, WindowsMxcTerminalSandboxRuntime } from '../../../../../../platform/sandbox/common/terminalSandboxMxcRuntime.js';
|
||||
import { ISandboxDependencyStatus, ISandboxHelperService } from '../../../../../../platform/sandbox/common/sandboxHelperService.js';
|
||||
import { getTerminalSandboxReadAllowListForCommands } from '../../../../../../platform/sandbox/common/terminalSandboxReadAllowList.js';
|
||||
import { getTerminalSandboxRuntimeConfigurationForCommands } from '../../../../../../platform/sandbox/common/terminalSandboxRuntimeConfigurationPerOperation.js';
|
||||
|
||||
@@ -167,43 +166,11 @@ suite('TerminalSandboxService - network domains', () => {
|
||||
bubblewrapUsable: true,
|
||||
socatInstalled: true,
|
||||
};
|
||||
filesystemPolicy: IWindowsMxcFilesystemPolicy = {
|
||||
readonlyPaths: ['c:\\tools\\node'],
|
||||
readwritePaths: [],
|
||||
};
|
||||
environment = [
|
||||
'SystemRoot=c:\\windows',
|
||||
'PATH=c:\\tools\\node;c:\\windows\\system32',
|
||||
'ComSpec=c:\\windows\\system32\\cmd.exe',
|
||||
'PATHEXT=.COM;.EXE;.BAT;.CMD;.PS1',
|
||||
'PSModulePath=c:\\users\\test\\documents\\powershell\\modules;c:\\program files\\powershell\\modules',
|
||||
'USERPROFILE=c:\\users\\test',
|
||||
'APPDATA=c:\\users\\test\\appdata\\roaming',
|
||||
'PSHOME=c:\\program files\\powershell\\7'
|
||||
];
|
||||
|
||||
checkSandboxDependencies(): Promise<ISandboxDependencyStatus> {
|
||||
this.callCount++;
|
||||
return Promise.resolve(this.status);
|
||||
}
|
||||
|
||||
getWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy> {
|
||||
return Promise.resolve(this.filesystemPolicy);
|
||||
}
|
||||
|
||||
getWindowsMxcEnvironment(): Promise<string[]> {
|
||||
return Promise.resolve(this.environment);
|
||||
}
|
||||
|
||||
buildWindowsMxcSandboxPayload(commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName: string = 'vscode-terminal-sandbox', containment: IWindowsMxcPolicyContainment = 'process'): Promise<IWindowsMxcConfig> {
|
||||
return Promise.resolve({
|
||||
...policy,
|
||||
command: commandLine,
|
||||
workingDirectory,
|
||||
containerName,
|
||||
containment: { type: containment },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setup(() => {
|
||||
@@ -251,7 +218,6 @@ suite('TerminalSandboxService - network domains', () => {
|
||||
instantiationService.stub(IWorkspaceContextService, workspaceContextService);
|
||||
instantiationService.stub(ILifecycleService, lifecycleService);
|
||||
instantiationService.stub(ISandboxHelperService, sandboxHelperService);
|
||||
instantiationService.stub(IWindowsMxcTerminalSandboxRuntime, instantiationService.createInstance(WindowsMxcTerminalSandboxRuntime));
|
||||
});
|
||||
|
||||
test('dependency checks should not be called for isEnabled', async () => {
|
||||
@@ -1494,7 +1460,7 @@ suite('TerminalSandboxService - network domains', () => {
|
||||
|
||||
test('should prefix wrapped command with ELECTRON_RUN_AS_NODE=1 when no remote env is available', async function () {
|
||||
if (isWindows) {
|
||||
// Windows uses PowerShell syntax to launch the MXC SDK runner.
|
||||
// The Local harness does not support Windows sandboxing.
|
||||
this.skip();
|
||||
}
|
||||
remoteAgentService.remoteEnvironment = null;
|
||||
@@ -1515,56 +1481,8 @@ suite('TerminalSandboxService - network domains', () => {
|
||||
ok(!wrapped.command.startsWith('ELECTRON_RUN_AS_NODE='), `Remote workbench should not add the env prefix. Actual: ${wrapped.command}`);
|
||||
});
|
||||
|
||||
test('should route remote Windows sandbox commands through MXC', async () => {
|
||||
test('should keep remote Windows sandbox disabled even when the unified setting is on', async () => {
|
||||
configurationService.setUserConfiguration(AgentSandboxSettingId.AgentSandboxEnabled, AgentSandboxEnabledValue.On);
|
||||
configurationService.setUserConfiguration(AgentSandboxSettingId.AgentSandboxAllowNetwork, true);
|
||||
remoteAgentService.remoteEnvironment = {
|
||||
...remoteAgentService.remoteEnvironment!,
|
||||
os: OperatingSystem.Windows,
|
||||
appRoot: URI.file('/c:/app'),
|
||||
execPath: 'c:\\app\\Code.exe',
|
||||
tmpDir: URI.file('/c:/tmp'),
|
||||
userHome: URI.file('/c:/Users/test'),
|
||||
workspaceStorageHome: URI.file('/c:/Users/test/AppData/Roaming/Code/User/workspaceStorage'),
|
||||
arch: 'arm64'
|
||||
};
|
||||
workspaceContextService.setWorkspaceFolders([URI.file('/c:/workspace-one')]);
|
||||
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
|
||||
|
||||
const configPath = await sandboxService.getSandboxConfigPath();
|
||||
const wrapped = await sandboxService.wrapCommand('echo test', false, 'c:\\program files\\powershell\\7\\pwsh.exe', URI.file('/c:/workspace-one'));
|
||||
|
||||
ok(configPath, 'Config path should be defined for remote Windows');
|
||||
const configContent = createdFiles.get(configPath);
|
||||
ok(configContent, 'Config file should be created for remote Windows');
|
||||
const config = JSON.parse(configContent);
|
||||
|
||||
strictEqual(wrapped.isSandboxWrapped, true);
|
||||
ok(wrapped.command.includes('out\\vs\\platform\\sandbox\\node\\mxcMain.js'), `Wrapped command should use the MXC SDK runner. Actual: ${wrapped.command}`);
|
||||
ok(wrapped.command.includes(configPath), `Wrapped command should pass the MXC config path. Actual: ${wrapped.command}`);
|
||||
strictEqual(config.version, undefined);
|
||||
deepStrictEqual(config.containment, { type: 'process' });
|
||||
strictEqual(config.command, '"c:\\program files\\powershell\\7\\pwsh.exe" -NoProfile -Command "echo test"');
|
||||
strictEqual(config.workingDirectory, 'c:\\workspace-one');
|
||||
deepStrictEqual(config.environment, {
|
||||
SystemRoot: 'c:\\windows',
|
||||
PATH: 'c:\\tools\\node;c:\\windows\\system32',
|
||||
ComSpec: 'c:\\windows\\system32\\cmd.exe',
|
||||
PATHEXT: '.COM;.EXE;.BAT;.CMD;.PS1',
|
||||
PSModulePath: 'c:\\users\\test\\documents\\powershell\\modules;c:\\program files\\powershell\\modules',
|
||||
USERPROFILE: 'c:\\users\\test',
|
||||
APPDATA: 'c:\\users\\test\\appdata\\roaming',
|
||||
PSHOME: 'c:\\program files\\powershell\\7',
|
||||
});
|
||||
ok(config.filesystem.readwritePaths.includes('c:\\workspace-one'), 'Workspace folder should be writable in the MXC config');
|
||||
ok(config.filesystem.readwritePaths.some((path: string) => path.includes('tmp_vscode_7')), 'Sandbox temp dir should be writable in the MXC config');
|
||||
ok(config.filesystem.readonlyPaths.includes('c:\\tools\\node'), 'MXC available tools policy should add tool paths to readonly paths');
|
||||
ok(config.filesystem.readonlyPaths.includes('c:\\program files\\powershell\\7'), 'Resolved PowerShell executable directory should be readable in the MXC config');
|
||||
ok(!config.filesystem.deniedPaths.includes('c:\\Users\\test'), 'User home should not be denied by default in the MXC config on Windows');
|
||||
});
|
||||
|
||||
test('should keep remote Windows sandbox disabled when the unified setting is off', async () => {
|
||||
configurationService.setUserConfiguration(AgentSandboxSettingId.AgentSandboxEnabled, AgentSandboxEnabledValue.Off);
|
||||
remoteAgentService.remoteEnvironment = {
|
||||
...remoteAgentService.remoteEnvironment!,
|
||||
os: OperatingSystem.Windows,
|
||||
@@ -1578,16 +1496,24 @@ suite('TerminalSandboxService - network domains', () => {
|
||||
workspaceContextService.setWorkspaceFolders([URI.file('/c:/workspace-one')]);
|
||||
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
|
||||
|
||||
strictEqual(await sandboxService.isEnabled(), false, 'Windows sandbox should be disabled when the unified setting is off');
|
||||
strictEqual(await sandboxService.getSandboxConfigPath(), undefined, 'Windows sandbox config should not be created unless the unified setting is enabled');
|
||||
const prereqs = await sandboxService.checkForSandboxingPrereqs();
|
||||
strictEqual(prereqs.enabled, false, 'Prereq checks should report Windows sandbox disabled when the unified setting is off');
|
||||
strictEqual(prereqs.failedCheck, undefined, 'No prereq check should fail when Windows sandboxing is disabled');
|
||||
deepStrictEqual({
|
||||
enabled: await sandboxService.isEnabled(),
|
||||
configPath: await sandboxService.getSandboxConfigPath(),
|
||||
prerequisites: await sandboxService.checkForSandboxingPrereqs(),
|
||||
wrapped: await sandboxService.wrapCommand('echo test'),
|
||||
createdFiles: createFileCount,
|
||||
}, {
|
||||
enabled: false,
|
||||
configPath: undefined,
|
||||
prerequisites: { enabled: false, sandboxConfigPath: undefined, failedCheck: undefined },
|
||||
wrapped: { command: 'echo test', isSandboxWrapped: false },
|
||||
createdFiles: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test('should place sandbox temp dir under the local data folder when no remote env is available', async function () {
|
||||
if (isWindows) {
|
||||
// Local Windows uses MXC, which does not use the POSIX local temp-dir path shape asserted below.
|
||||
// The Local harness does not support Windows sandboxing.
|
||||
this.skip();
|
||||
}
|
||||
remoteAgentService.remoteEnvironment = null;
|
||||
|
||||
+1
-3
@@ -66,7 +66,6 @@ suite('Terminal chat agent tools configuration', () => {
|
||||
[AgentSandboxSettingId.AgentSandboxUserConfiguredPaths, 65],
|
||||
[AgentSandboxSettingId.AgentSandboxLinuxFileSystem, 70],
|
||||
[AgentSandboxSettingId.AgentSandboxMacFileSystem, 80],
|
||||
[AgentSandboxSettingId.AgentSandboxWindowsFileSystem, 90],
|
||||
] as const;
|
||||
assert.deepStrictEqual(
|
||||
Object.entries(terminalChatAgentToolsConfiguration)
|
||||
@@ -120,7 +119,6 @@ suite('Terminal chat agent tools configuration', () => {
|
||||
legacy: [
|
||||
AgentSandboxSettingId.AgentSandboxLinuxFileSystem,
|
||||
AgentSandboxSettingId.AgentSandboxMacFileSystem,
|
||||
AgentSandboxSettingId.AgentSandboxWindowsFileSystem,
|
||||
].map(key => {
|
||||
const legacy = terminalChatAgentToolsConfiguration[key];
|
||||
return [legacy.markdownDeprecationMessage, legacy.deprecationMessageShowInSettings, legacy.deprecated];
|
||||
@@ -132,7 +130,7 @@ suite('Terminal chat agent tools configuration', () => {
|
||||
default: { readwritePaths: [], readonlyPaths: [], deniedPaths: [] },
|
||||
restricted: true,
|
||||
additionalProperties: false,
|
||||
legacy: Array.from({ length: 3 }, () => [
|
||||
legacy: Array.from({ length: 2 }, () => [
|
||||
'This setting will be deprecated soon. For the Copilot Agent Host sandbox, use `#chat.agent.sandbox.fileSystem.userConfiguredPaths#` instead.', true, undefined,
|
||||
]),
|
||||
});
|
||||
|
||||
@@ -56,9 +56,10 @@ import { IAgentHostUntitledProvisionalSessionService } from '../../../../contrib
|
||||
import { IAgentHostSessionWorkingDirectoryResolver } from '../../../../contrib/chat/browser/agentSessions/agentHost/agentHostSessionWorkingDirectoryResolver.js';
|
||||
import { IAgentHostNewSessionFolderService } from '../../../../contrib/chat/browser/agentSessions/agentHost/agentHostNewSessionFolderService.js';
|
||||
import { IAgentHostCustomizationService } from '../../../../contrib/chat/browser/agentSessions/agentHost/agentHostCustomizationService.js';
|
||||
import { TestPathService } from '../../workbenchTestServices.js';
|
||||
import { TestPathService, TestRemoteAgentService } from '../../workbenchTestServices.js';
|
||||
import { IAgentSdkSetupService } from '../../../../services/agentHost/browser/agentSdkSetupService.js';
|
||||
import { ICodexAccountService } from '../../../../services/agentHost/browser/codexAccountService.js';
|
||||
import { IRemoteAgentService } from '../../../../services/remote/common/remoteAgentService.js';
|
||||
import { IVoiceModeOnboardingService } from '../../../../contrib/agentsVoice/browser/voiceModeOnboarding.js';
|
||||
import { IChatAccessibilityService, IChatWidget, IChatWidgetService } from '../../../../contrib/chat/browser/chat.js';
|
||||
import { IChatResponseFileChangesService } from '../../../../contrib/chat/browser/chatResponseFileChangesService.js';
|
||||
@@ -196,6 +197,7 @@ export function registerChatFixtureServices(reg: ServiceRegistration, options: I
|
||||
reg.defineInstance(IEditorService, new class extends mock<IEditorService>() { override onDidActiveEditorChange = Event.None; }());
|
||||
reg.defineInstance(IExtensionService, new class extends mock<IExtensionService>() { override readonly onDidChangeExtensions = Event.None; }());
|
||||
reg.defineInstance(IPathService, new TestPathService());
|
||||
reg.defineInstance(IRemoteAgentService, new TestRemoteAgentService());
|
||||
reg.defineInstance(IWorkbenchAssignmentService, new class extends mock<IWorkbenchAssignmentService>() { override async getCurrentExperiments() { return []; } override async getTreatment() { return undefined; } override onDidRefetchAssignments = Event.None; }());
|
||||
reg.defineInstance(IWorkspaceContextService, new class extends mock<IWorkspaceContextService>() { override onDidChangeWorkspaceFolders = Event.None; override getWorkspace(): IWorkspace { return { id: '', folders: [], configuration: undefined }; } }());
|
||||
// `getContainer` stands in for the workbench container that widgets use to host
|
||||
|
||||
+3
-1
@@ -64,7 +64,8 @@ import { IAgentHostUntitledProvisionalSessionService } from '../../../../contrib
|
||||
import { IAgentHostSessionWorkingDirectoryResolver } from '../../../../contrib/chat/browser/agentSessions/agentHost/agentHostSessionWorkingDirectoryResolver.js';
|
||||
import { IAgentHostNewSessionFolderService } from '../../../../contrib/chat/browser/agentSessions/agentHost/agentHostNewSessionFolderService.js';
|
||||
import { IAgentHostCustomizationService } from '../../../../contrib/chat/browser/agentSessions/agentHost/agentHostCustomizationService.js';
|
||||
import { TestPathService } from '../../workbenchTestServices.js';
|
||||
import { TestPathService, TestRemoteAgentService } from '../../workbenchTestServices.js';
|
||||
import { IRemoteAgentService } from '../../../../services/remote/common/remoteAgentService.js';
|
||||
import { IWorkspaceContextService, IWorkspace } from '../../../../../platform/workspace/common/workspace.js';
|
||||
import { IViewDescriptorService } from '../../../../common/views.js';
|
||||
import { IListService, ListService } from '../../../../../platform/list/browser/listService.js';
|
||||
@@ -173,6 +174,7 @@ function renderInlineChatZoneWidget({ container, disposableStore, theme, focus }
|
||||
reg.define(IContextKeyService, ContextKeyService);
|
||||
reg.define(IMenuService, MenuService);
|
||||
reg.define(IMarkdownRendererService, MarkdownRendererService);
|
||||
reg.defineInstance(IRemoteAgentService, new TestRemoteAgentService());
|
||||
|
||||
reg.defineInstance(IAccessibleViewService, new class extends mock<IAccessibleViewService>() {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
@@ -87,15 +87,11 @@ const targetArtifacts: Readonly<Record<string, string>> = {
|
||||
*/
|
||||
export class TestContext {
|
||||
private static readonly authenticodeInclude = /^.+\.(exe|dll|sys|cab|cat|msi|jar|ocx|ps1|psm1|psd1|ps1xml|pssc1)$/i;
|
||||
// MXC SDK ships per-arch SPDX catalog manifests that Get-AuthenticodeSignature reports as UnknownError.
|
||||
private static readonly authenticodeExclude = /[\\/]node_modules[\\/]@microsoft[\\/]mxc-sdk[\\/]bin[\\/][^\\/]+[\\/]_manifest[\\/][^\\/]+[\\/]manifest\.cat$/i;
|
||||
private static readonly authenticodeTestCertificate = /Code Sign Test \(DO NOT TRUST\)/i;
|
||||
private static readonly versionInfoInclude = /^.+\.(exe|dll|node|msi)$/i;
|
||||
// Electron helpers (dxil/ffmpeg) and Copilot-vendored MSAL runtime DLLs ship VersionInfo that
|
||||
// FileVersionInfo cannot resolve to a ProductName (x64: msalruntime.dll, arm64: msalruntime_arm64.dll).
|
||||
private static readonly versionInfoFileExclude = /^(dxil\.dll|ffmpeg\.dll|msalruntime(_arm64)?\.dll)$/i;
|
||||
// MXC SDK binaries under bin are signed, but they do not carry a ProductName VersionInfo resource.
|
||||
private static readonly versionInfoPathExclude = /(?:^|[\\/])node_modules(?:\.asar\.unpacked)?[\\/]@microsoft[\\/]mxc-sdk[\\/]bin[\\/]/i;
|
||||
private static readonly dpkgLockError = /dpkg frontend lock was locked by another process|unable to acquire the dpkg frontend lock|could not get lock \/var\/lib\/dpkg\/lock-frontend/i;
|
||||
|
||||
private readonly tempDirs = new Set<string>();
|
||||
@@ -552,11 +548,7 @@ export class TestContext {
|
||||
if (entry.isDirectory()) {
|
||||
this.collectAuthenticodeFiles(filePath, files);
|
||||
} else if (TestContext.authenticodeInclude.test(entry.name)) {
|
||||
if (TestContext.authenticodeExclude.test(filePath)) {
|
||||
this.log(`Skipping excluded file from Authenticode validation: ${filePath}`);
|
||||
} else {
|
||||
files.push(filePath);
|
||||
}
|
||||
files.push(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -589,7 +581,7 @@ export class TestContext {
|
||||
if (entry.isDirectory()) {
|
||||
this.collectVersionInfoFiles(filePath, files);
|
||||
} else if (TestContext.versionInfoInclude.test(entry.name)) {
|
||||
if (TestContext.versionInfoFileExclude.test(entry.name) || TestContext.versionInfoPathExclude.test(filePath)) {
|
||||
if (TestContext.versionInfoFileExclude.test(entry.name)) {
|
||||
this.log(`Skipping excluded file from VersionInfo validation: ${filePath}`);
|
||||
} else {
|
||||
files.push(filePath);
|
||||
|
||||
Reference in new issue
Block a user