Skip to content

Commit c36c93a

Browse files
authored
Let opener service validate that only specific commands can be run in command uris (microsoft#165204)
1 parent 6aaf830 commit c36c93a

File tree

4 files changed

+54
-52
lines changed

4 files changed

+54
-52
lines changed

src/vs/editor/browser/services/openerService.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,25 @@ class CommandOpener implements IOpener {
2525
if (!matchesScheme(target, Schemas.command)) {
2626
return false;
2727
}
28+
2829
if (!options?.allowCommands) {
2930
// silently ignore commands when command-links are disabled, also
30-
// surpress other openers by returning TRUE
31+
// suppress other openers by returning TRUE
3132
return true;
3233
}
33-
// run command or bail out if command isn't known
34+
3435
if (typeof target === 'string') {
3536
target = URI.parse(target);
3637
}
38+
39+
if (Array.isArray(options.allowCommands)) {
40+
// Only allow specific commands
41+
if (!options.allowCommands.includes(target.path)) {
42+
// Suppress other openers by returning TRUE
43+
return true;
44+
}
45+
}
46+
3747
// execute as command
3848
let args: any = [];
3949
try {

src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts

+17-28
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import { onUnexpectedError } from 'vs/base/common/errors';
88
import { Emitter } from 'vs/base/common/event';
99
import { IMarkdownString, MarkdownStringTrustedOptions } from 'vs/base/common/htmlContent';
1010
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
11-
import { Schemas } from 'vs/base/common/network';
12-
import { URI } from 'vs/base/common/uri';
1311
import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
1412
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
1513
import { EditorOption } from 'vs/editor/common/config/editorOptions';
@@ -112,35 +110,26 @@ export class MarkdownRenderer {
112110
}
113111

114112
export async function openLinkFromMarkdown(openerService: IOpenerService, link: string, isTrusted: boolean | MarkdownStringTrustedOptions | undefined): Promise<boolean> {
115-
let allowCommands = false;
116-
if (isTrusted) {
117-
try {
118-
const uri = URI.parse(link);
119-
if (uri.scheme === Schemas.command) {
120-
if (typeof isTrusted === 'boolean') {
121-
if (!isTrusted) {
122-
return false;
123-
}
124-
125-
allowCommands = true;
126-
} else {
127-
// Only allow a subset of commands
128-
if (!isTrusted.enabledCommands.includes(uri.path)) {
129-
return false;
130-
}
131-
132-
allowCommands = true;
133-
}
134-
}
135-
} catch {
136-
// noop
137-
}
138-
}
139-
140113
try {
141-
return await openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true, allowCommands });
114+
return await openerService.open(link, {
115+
fromUserGesture: true,
116+
allowContributedOpeners: true,
117+
allowCommands: toAllowCommandsOption(isTrusted),
118+
});
142119
} catch (e) {
143120
onUnexpectedError(e);
144121
return false;
145122
}
146123
}
124+
125+
function toAllowCommandsOption(isTrusted: boolean | MarkdownStringTrustedOptions | undefined): boolean | readonly string[] {
126+
if (isTrusted === true) {
127+
return true; // Allow all commands
128+
}
129+
130+
if (isTrusted && Array.isArray(isTrusted.enabledCommands)) {
131+
return isTrusted.enabledCommands; // Allow subset of commands
132+
}
133+
134+
return false; // Block commands
135+
}

src/vs/platform/opener/common/opener.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ export type OpenInternalOptions = {
3333

3434
/**
3535
* Allow command links to be handled.
36+
*
37+
* If this is an array, then only the commands included in the array can be run.
3638
*/
37-
readonly allowCommands?: boolean;
39+
readonly allowCommands?: boolean | readonly string[];
3840
};
3941

4042
export type OpenExternalOptions = {

src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts

+22-21
Original file line numberDiff line numberDiff line change
@@ -682,35 +682,36 @@ var requirejs = (function() {
682682
break;
683683
}
684684
case 'clicked-link': {
685-
let linkToOpen: URI | string | undefined;
686-
687685
if (matchesScheme(data.href, Schemas.command)) {
688-
// We allow a very limited set of commands
689686
const uri = URI.parse(data.href);
690-
switch (uri.path) {
691-
case 'workbench.action.openLargeOutput': {
692-
const outputId = uri.query;
693-
const group = this.editorGroupService.activeGroup;
694-
if (group) {
695-
if (group.activeEditor) {
696-
group.pinEditor(group.activeEditor);
697-
}
698-
}
699687

700-
this.openerService.open(CellUri.generateCellOutputUri(this.documentUri, outputId));
701-
return;
702-
}
703-
case 'github-issues.authNow':
704-
case 'workbench.extensions.search':
705-
case 'workbench.action.openSettings': {
706-
this.openerService.open(data.href, { fromUserGesture: true, allowCommands: true, fromWorkspace: true });
707-
return;
688+
if (uri.path === 'workbench.action.openLargeOutput') {
689+
const outputId = uri.query;
690+
const group = this.editorGroupService.activeGroup;
691+
if (group) {
692+
if (group.activeEditor) {
693+
group.pinEditor(group.activeEditor);
694+
}
708695
}
696+
697+
this.openerService.open(CellUri.generateCellOutputUri(this.documentUri, outputId));
698+
return;
709699
}
710700

701+
// We allow a very limited set of commands
702+
this.openerService.open(data.href, {
703+
fromUserGesture: true,
704+
fromWorkspace: true,
705+
allowCommands: [
706+
'github-issues.authNow',
707+
'workbench.extensions.search',
708+
'workbench.action.openSettings',
709+
],
710+
});
711711
return;
712712
}
713713

714+
let linkToOpen: URI | string | undefined;
714715
if (matchesSomeScheme(data.href, Schemas.http, Schemas.https, Schemas.mailto, Schemas.vscodeNotebookCell, Schemas.vscodeNotebook)) {
715716
linkToOpen = data.href;
716717
} else if (!/^[\w\-]+:/.test(data.href)) {
@@ -742,7 +743,7 @@ var requirejs = (function() {
742743
}
743744

744745
if (linkToOpen) {
745-
this.openerService.open(linkToOpen, { fromUserGesture: true, allowCommands: false, fromWorkspace: true });
746+
this.openerService.open(linkToOpen, { fromUserGesture: true, fromWorkspace: true });
746747
}
747748
break;
748749
}

0 commit comments

Comments
 (0)