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 https://github.com/coder/security/issues/77
This commit is contained in:
Bruno Quaresma
2025-05-09 14:40:26 -03:00
committed by GitHub
parent 5c532779af
commit 9e44f18b4b
3 changed files with 36 additions and 1 deletions

View File

@ -53,6 +53,22 @@ describe("getAppHref", () => {
expect(href).toBe(externalApp.url);
});
it("doesn't return the URL with the session token replaced when using unauthorized protocol", () => {
const externalApp = {
...MockWorkspaceApp,
external: true,
url: `ftp://example.com?token=${SESSION_TOKEN_PLACEHOLDER}`,
};
const href = getAppHref(externalApp, {
host: "*.apps-host.tld",
agent: MockWorkspaceAgent,
workspace: MockWorkspace,
path: "/path-base",
token: "user-session-token",
});
expect(href).toBe(externalApp.url);
});
it("returns a path when app doesn't use a subdomain", () => {
const app = {
...MockWorkspaceApp,

View File

@ -10,6 +10,20 @@ import type {
// be used internally, and is highly subject to break.
export const SESSION_TOKEN_PLACEHOLDER = "$SESSION_TOKEN";
// This is a list of external app protocols that we
// allow to be opened in a new window. This is
// used to prevent phishing attacks where a user
// is tricked into clicking a link that opens
// a malicious app using the Coder session token.
const ALLOWED_EXTERNAL_APP_PROTOCOLS = [
"vscode:",
"vscode-insiders:",
"windsurf:",
"cursor:",
"jetbrains-gateway:",
"jetbrains:",
];
type GetVSCodeHrefParams = {
owner: string;
workspace: string;
@ -78,7 +92,11 @@ export const getAppHref = (
{ path, token, workspace, agent, host }: GetAppHrefParams,
): string => {
if (isExternalApp(app)) {
return needsSessionToken(app)
const appProtocol = new URL(app.url).protocol;
const isAllowedProtocol =
ALLOWED_EXTERNAL_APP_PROTOCOLS.includes(appProtocol);
return needsSessionToken(app) && isAllowedProtocol
? app.url.replaceAll(SESSION_TOKEN_PLACEHOLDER, token ?? "")
: app.url;
}

View File

@ -80,6 +80,7 @@ export const ExternalApp: Story = {
workspace: MockWorkspace,
app: {
...MockWorkspaceApp,
url: "vscode://open",
external: true,
},
agent: MockWorkspaceAgent,