Skip to content

Commit bbd1961

Browse files
authored
Merge pull request microsoft#253008 from microsoft/copilot/fix-190253
Add shellIntegrationNonce support to TerminalOptions and ExtensionTerminalOptions
2 parents 45e9c8e + c310dac commit bbd1961

File tree

8 files changed

+75
-5
lines changed

8 files changed

+75
-5
lines changed

src/vs/platform/terminal/common/terminal.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,12 @@ export interface IShellLaunchConfig {
654654
* Report terminal's shell environment variables to VS Code and extensions
655655
*/
656656
shellIntegrationEnvironmentReporting?: boolean;
657+
658+
/**
659+
* A custom nonce to use for shell integration when provided by an extension.
660+
* This allows extensions to control shell integration for terminals they create.
661+
*/
662+
shellIntegrationNonce?: string;
657663
}
658664

659665
export interface ITerminalTabAction {

src/vs/platform/terminal/node/terminalProcess.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
234234
} else {
235235
this._onDidChangeProperty.fire({ type: ProcessPropertyType.FailedShellIntegrationActivation, value: true });
236236
this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellIntegrationInjectionFailureReason, value: injection.reason });
237+
// Even if shell integration injection failed, still set the nonce if one was provided
238+
// This allows extensions to use shell integration with custom shells
239+
if (this._options.shellIntegration.nonce) {
240+
this._ptyOptions.env ||= {};
241+
this._ptyOptions.env['VSCODE_NONCE'] = this._options.shellIntegration.nonce;
242+
}
237243
}
238244

239245
try {

src/vs/platform/terminal/test/node/terminalEnvironment.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,5 +232,33 @@ suite('platform - terminalEnvironment', async () => {
232232
});
233233
});
234234
}
235+
236+
suite('custom shell integration nonce', async () => {
237+
test('should fail for unsupported shell but nonce should still be available', async () => {
238+
const customProcessOptions: ITerminalProcessOptions = {
239+
shellIntegration: { enabled: true, suggestEnabled: false, nonce: 'custom-nonce-12345' },
240+
windowsEnableConpty: true,
241+
windowsUseConptyDll: false,
242+
environmentVariableCollections: undefined,
243+
workspaceFolder: undefined
244+
};
245+
246+
// Test with an unsupported shell (julia)
247+
const result = await getShellIntegrationInjection(
248+
{ executable: 'julia', args: ['-i'] },
249+
customProcessOptions,
250+
defaultEnvironment,
251+
logService,
252+
productService,
253+
true
254+
);
255+
256+
// Should fail due to unsupported shell
257+
strictEqual(result.type, 'failure');
258+
259+
// But the nonce should be available in the process options for the terminal process to use
260+
strictEqual(customProcessOptions.shellIntegration.nonce, 'custom-nonce-12345');
261+
});
262+
});
235263
});
236264
});

src/vs/workbench/api/browser/mainThreadTerminalService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
164164
isFeatureTerminal: launchConfig.isFeatureTerminal,
165165
isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal,
166166
useShellEnvironment: launchConfig.useShellEnvironment,
167-
isTransient: launchConfig.isTransient
167+
isTransient: launchConfig.isTransient,
168+
shellIntegrationNonce: launchConfig.shellIntegrationNonce
168169
};
169170
const terminal = Promises.withAsyncBody<ITerminalInstance>(async r => {
170171
const terminal = await this._terminalService.createTerminal({

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ export interface TerminalLaunchConfig {
563563
useShellEnvironment?: boolean;
564564
location?: TerminalLocation | { viewColumn: number; preserveFocus?: boolean } | { parentTerminal: ExtHostTerminalIdentifier } | { splitActiveTerminal: boolean };
565565
isTransient?: boolean;
566+
shellIntegrationNonce?: string;
566567
}
567568

568569

src/vs/workbench/api/common/extHostTerminalService.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,12 @@ export class ExtHostTerminal extends Disposable {
191191
useShellEnvironment: internalOptions?.useShellEnvironment ?? undefined,
192192
location: internalOptions?.location || this._serializeParentTerminal(options.location, internalOptions?.resolvedExtHostIdentifier),
193193
isTransient: options.isTransient ?? undefined,
194+
shellIntegrationNonce: options.shellIntegrationNonce ?? undefined,
194195
});
195196
}
196197

197198

198-
public async createExtensionTerminal(location?: TerminalLocation | vscode.TerminalEditorLocationOptions | vscode.TerminalSplitLocationOptions, internalOptions?: ITerminalInternalOptions, parentTerminal?: ExtHostTerminalIdentifier, iconPath?: TerminalIcon, color?: ThemeColor): Promise<number> {
199+
public async createExtensionTerminal(location?: TerminalLocation | vscode.TerminalEditorLocationOptions | vscode.TerminalSplitLocationOptions, internalOptions?: ITerminalInternalOptions, parentTerminal?: ExtHostTerminalIdentifier, iconPath?: TerminalIcon, color?: ThemeColor, shellIntegrationNonce?: string): Promise<number> {
199200
if (typeof this._id !== 'string') {
200201
throw new Error('Terminal has already been created');
201202
}
@@ -205,7 +206,8 @@ export class ExtHostTerminal extends Disposable {
205206
icon: iconPath,
206207
color: ThemeColor.isThemeColor(color) ? color.id : undefined,
207208
location: internalOptions?.location || this._serializeParentTerminal(location, parentTerminal),
208-
isTransient: true
209+
isTransient: true,
210+
shellIntegrationNonce: shellIntegrationNonce ?? undefined,
209211
});
210212
// At this point, the id has been set via `$acceptTerminalOpened`
211213
if (typeof this._id === 'string') {
@@ -511,7 +513,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
511513
public createExtensionTerminal(options: vscode.ExtensionTerminalOptions, internalOptions?: ITerminalInternalOptions): vscode.Terminal {
512514
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
513515
const p = new ExtHostPseudoterminal(options.pty);
514-
terminal.createExtensionTerminal(options.location, internalOptions, this._serializeParentTerminal(options, internalOptions).resolvedExtHostIdentifier, asTerminalIcon(options.iconPath), asTerminalColor(options.color)).then(id => {
516+
terminal.createExtensionTerminal(options.location, internalOptions, this._serializeParentTerminal(options, internalOptions).resolvedExtHostIdentifier, asTerminalIcon(options.iconPath), asTerminalColor(options.color), options.shellIntegrationNonce).then(id => {
515517
const disposable = this._setupExtHostProcessListeners(id, p);
516518
this._terminalProcessDisposables[id] = disposable;
517519
});

src/vs/workbench/contrib/terminal/browser/terminalInstance.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1399,7 +1399,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
13991399
this._instanceId,
14001400
this.shellLaunchConfig?.cwd,
14011401
deserializedCollections,
1402-
this.shellLaunchConfig.attachPersistentProcess?.shellIntegrationNonce
1402+
this.shellLaunchConfig.shellIntegrationNonce ?? this.shellLaunchConfig.attachPersistentProcess?.shellIntegrationNonce
14031403
);
14041404
this.capabilities.add(processManager.capabilities);
14051405
this._register(processManager.onProcessReady(async (e) => {

src/vscode-dts/vscode.d.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12464,6 +12464,20 @@ declare module 'vscode' {
1246412464
* This will only take effect when `terminal.integrated.enablePersistentSessions` is enabled.
1246512465
*/
1246612466
isTransient?: boolean;
12467+
12468+
/**
12469+
* The nonce to use to verify shell integration sequences are coming from a trusted source.
12470+
* An example impact of UX of this is if the command line is reported with a nonce, it will
12471+
* not need to verify with the user that the command line is correct before rerunning it
12472+
* via the [shell integration command decoration](https://code.visualstudio.com/docs/terminal/shell-integration#_command-decorations-and-the-overview-ruler).
12473+
*
12474+
* This should be used if the terminal includes [custom shell integration support](https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences).
12475+
* It should be set to a random GUID which will then set the `VSCODE_NONCE` environment
12476+
* variable. Inside the shell, this should then be removed from the environment so as to
12477+
* protect it from general access. Once that is done it can be passed through in the
12478+
* relevant sequences to make them trusted.
12479+
*/
12480+
shellIntegrationNonce?: string;
1246712481
}
1246812482

1246912483
/**
@@ -12503,6 +12517,18 @@ declare module 'vscode' {
1250312517
* This will only take effect when `terminal.integrated.enablePersistentSessions` is enabled.
1250412518
*/
1250512519
isTransient?: boolean;
12520+
12521+
/**
12522+
* The nonce to use to verify shell integration sequences are coming from a trusted source.
12523+
* An example impact of UX of this is if the command line is reported with a nonce, it will
12524+
* not need to verify with the user that the command line is correct before rerunning it
12525+
* via the [shell integration command decoration](https://code.visualstudio.com/docs/terminal/shell-integration#_command-decorations-and-the-overview-ruler).
12526+
*
12527+
* This should be used if the terminal includes [custom shell integration support](https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences).
12528+
* It should be set to a random GUID. Inside the {@link Pseudoterminal} implementation, this value
12529+
* can be passed through in the relevant sequences to make them trusted.
12530+
*/
12531+
shellIntegrationNonce?: string;
1250612532
}
1250712533

1250812534
/**

0 commit comments

Comments
 (0)