Skip to content

Commit 4f4820f

Browse files
atscottalxhub
authored andcommitted
fix(vscode-extension): prompt for confirmation before loading workspace tsdk
Harden the typescript.tsdk and js/ts.tsdk.path settings loading in the VS Code extension client. This change hardens tsdk loading by: 1. Ignoring workspace-level tsdk paths in untrusted workspaces. 2. Prompting the user for explicit confirmation before loading a workspace-level tsdk path in trusted workspaces, and saving the approval state in a secure, local workspaceState.
1 parent dfbe7a6 commit 4f4820f

1 file changed

Lines changed: 57 additions & 1 deletion

File tree

  • vscode-ng-language-service/client/src

‎vscode-ng-language-service/client/src/client.ts‎

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ export class AngularLanguageClient implements vscode.Disposable {
510510
// dynamically via workspace/configuration request by the server.
511511
// This allows users to change these settings without restarting.
512512

513-
const tsdk = config.get('js/ts.tsdk.path', config.get('typescript.tsdk', ''));
513+
const tsdk = await this.getTsdkPath();
514514
if (tsdk.trim().length > 0) {
515515
args.push('--tsdk', tsdk);
516516
}
@@ -627,6 +627,62 @@ export class AngularLanguageClient implements vscode.Disposable {
627627
}
628628
}
629629

630+
private async getTsdkPath(): Promise<string> {
631+
const config = vscode.workspace.getConfiguration();
632+
const tsdkInspect =
633+
config.inspect<string>('js/ts.tsdk.path') ?? config.inspect<string>('typescript.tsdk');
634+
635+
if (!tsdkInspect) {
636+
return '';
637+
}
638+
639+
// 1. Check workspace/folder settings first (highest priority)
640+
// ONLY check or load workspace-level settings if the workspace is trusted
641+
if (vscode.workspace.isTrusted) {
642+
const workspaceTsdk = (
643+
tsdkInspect.workspaceValue ?? tsdkInspect.workspaceFolderValue
644+
)?.trim();
645+
if (workspaceTsdk) {
646+
const stateKey = `approvedTsdk:${workspaceTsdk}`;
647+
const isApproved = this.context.workspaceState.get<boolean>(stateKey);
648+
649+
if (isApproved) {
650+
return workspaceTsdk;
651+
}
652+
653+
if (isApproved === undefined) {
654+
// Prompt the user asynchronously, without blocking current server initialization
655+
this.promptForTsdkApproval(workspaceTsdk, stateKey);
656+
}
657+
658+
// Fall back to globalValue (or bundled) while waiting for approval or if rejected
659+
}
660+
}
661+
662+
return tsdkInspect.globalValue?.trim() ?? '';
663+
}
664+
665+
private async promptForTsdkApproval(workspaceTsdk: string, stateKey: string): Promise<void> {
666+
const allowOption = 'Allow';
667+
const disallowOption = 'Disallow';
668+
669+
const choice = await vscode.window.showWarningMessage(
670+
`This workspace configures a custom TypeScript compiler path (${workspaceTsdk}) via 'js/ts.tsdk.path' or 'typescript.tsdk'. ` +
671+
`Do you want to allow the Angular Language Service to load the TypeScript compiler from this path?`,
672+
allowOption,
673+
disallowOption,
674+
);
675+
676+
if (choice === allowOption) {
677+
await this.context.workspaceState.update(stateKey, true);
678+
// Restart the language server so it launches with the newly authorized compiler path
679+
await vscode.commands.executeCommand('angular.restartNgServer');
680+
} else {
681+
// Remember rejection to avoid repeatedly prompting on startup/activation
682+
await this.context.workspaceState.update(stateKey, false);
683+
}
684+
}
685+
630686
dispose() {
631687
this.disposeSessionDisposables();
632688
for (let d = this.disposables.pop(); d !== undefined; d = this.disposables.pop()) {

0 commit comments

Comments
 (0)