Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions apps/mesh/src/api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,36 @@ export async function createApp(options: CreateAppOptions = {}) {
// 2. Cookies are set on the correct domain
// 3. The user can interact with the consent screen
if (endpoint === "authorize") {
// Validate redirect_uri to prevent OAuth hijacking — only allow our own origin.
// Use .get() to grab the first value, then .set() to canonicalize to exactly
// one redirect_uri param, preventing parser-differential bypasses via duplicates.
const redirectUri = targetUrl.searchParams.get("redirect_uri");
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
if (redirectUri) {
Comment thread
0xcucumbersalad marked this conversation as resolved.
const allowedOrigin = getSettings().baseUrl ?? reqUrl.origin;
try {
const redirectOrigin = new URL(redirectUri).origin;
if (redirectOrigin !== new URL(allowedOrigin).origin) {
return c.json(
{
error: "invalid_request",
error_description: "redirect_uri is not allowed",
},
400,
);
}
} catch {
return c.json(
{
error: "invalid_request",
error_description: "redirect_uri is malformed",
},
400,
);
}
// Collapse any duplicate redirect_uri params to the single validated value
targetUrl.searchParams.set("redirect_uri", redirectUri);
}

// IMPORTANT: Rewrite the 'resource' parameter to point to the origin MCP endpoint
// Some auth servers (like Supabase) validate that the resource is their actual endpoint,
// not our proxy. We keep the proxy URL for redirect_uri since that's where we handle the callback.
Expand Down
Loading