diff --git a/packages/opencode/src/mcp/oauth-callback.ts b/packages/opencode/src/mcp/oauth-callback.ts index dd1d886fc1..0be49033e7 100644 --- a/packages/opencode/src/mcp/oauth-callback.ts +++ b/packages/opencode/src/mcp/oauth-callback.ts @@ -5,6 +5,10 @@ import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH } from "./oauth-provider" const log = Log.create({ service: "mcp.oauth-callback" }) +function escapeHtml(str: string): string { + return str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'") +} + const HTML_SUCCESS = ` @@ -41,7 +45,7 @@ const HTML_ERROR = (error: string) => `

Authorization Failed

An error occurred during authorization.

-
${error}
+
${escapeHtml(error)}
` diff --git a/packages/opencode/test/mcp/oauth-callback-xss.test.ts b/packages/opencode/test/mcp/oauth-callback-xss.test.ts new file mode 100644 index 0000000000..89cd4c6742 --- /dev/null +++ b/packages/opencode/test/mcp/oauth-callback-xss.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from "bun:test" + +/** + * CWE-79: Cross-Site Scripting (XSS) + * File: packages/opencode/src/mcp/oauth-callback.ts + * + * HTML_ERROR interpolated error string directly into HTML template. + * Since error comes from URL query params (error, error_description), + * an attacker could craft a malicious OAuth callback URL to inject scripts. + */ + +function escapeHtml(str: string): string { + return str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'") +} + +const HTML_ERROR = (error: string) => `
${escapeHtml(error)}
` + +describe("CWE-79: XSS in oauth-callback.ts HTML_ERROR", () => { + test("should escape script tags in error message", () => { + const result = HTML_ERROR('') + expect(result).not.toContain("