Skip to content

Commit 9e44f18

Browse files
refactor: add safe list for external app protocols (#17742)
To prevent malicious apps and vendors to use the Coder session token we are adding safe protocols/schemas we want to support. - vscode: - vscode-insiders: - windsurf: - cursor: - jetbrains-gateway: - jetbrains: Fix coder/security#77
1 parent 5c53277 commit 9e44f18

File tree

3 files changed

+36
-1
lines changed

3 files changed

+36
-1
lines changed

site/src/modules/apps/apps.test.ts

+16
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ describe("getAppHref", () => {
5353
expect(href).toBe(externalApp.url);
5454
});
5555

56+
it("doesn't return the URL with the session token replaced when using unauthorized protocol", () => {
57+
const externalApp = {
58+
...MockWorkspaceApp,
59+
external: true,
60+
url: `ftp://example.com?token=${SESSION_TOKEN_PLACEHOLDER}`,
61+
};
62+
const href = getAppHref(externalApp, {
63+
host: "*.apps-host.tld",
64+
agent: MockWorkspaceAgent,
65+
workspace: MockWorkspace,
66+
path: "/path-base",
67+
token: "user-session-token",
68+
});
69+
expect(href).toBe(externalApp.url);
70+
});
71+
5672
it("returns a path when app doesn't use a subdomain", () => {
5773
const app = {
5874
...MockWorkspaceApp,

site/src/modules/apps/apps.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@ import type {
1010
// be used internally, and is highly subject to break.
1111
export const SESSION_TOKEN_PLACEHOLDER = "$SESSION_TOKEN";
1212

13+
// This is a list of external app protocols that we
14+
// allow to be opened in a new window. This is
15+
// used to prevent phishing attacks where a user
16+
// is tricked into clicking a link that opens
17+
// a malicious app using the Coder session token.
18+
const ALLOWED_EXTERNAL_APP_PROTOCOLS = [
19+
"vscode:",
20+
"vscode-insiders:",
21+
"windsurf:",
22+
"cursor:",
23+
"jetbrains-gateway:",
24+
"jetbrains:",
25+
];
26+
1327
type GetVSCodeHrefParams = {
1428
owner: string;
1529
workspace: string;
@@ -78,7 +92,11 @@ export const getAppHref = (
7892
{ path, token, workspace, agent, host }: GetAppHrefParams,
7993
): string => {
8094
if (isExternalApp(app)) {
81-
return needsSessionToken(app)
95+
const appProtocol = new URL(app.url).protocol;
96+
const isAllowedProtocol =
97+
ALLOWED_EXTERNAL_APP_PROTOCOLS.includes(appProtocol);
98+
99+
return needsSessionToken(app) && isAllowedProtocol
82100
? app.url.replaceAll(SESSION_TOKEN_PLACEHOLDER, token ?? "")
83101
: app.url;
84102
}

site/src/modules/resources/AppLink/AppLink.stories.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export const ExternalApp: Story = {
8080
workspace: MockWorkspace,
8181
app: {
8282
...MockWorkspaceApp,
83+
url: "vscode://open",
8384
external: true,
8485
},
8586
agent: MockWorkspaceAgent,

0 commit comments

Comments
 (0)