You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ya...@apache.org on 2026/10/18 03:54:30 UTC
(airavata-custos) 01/11: block off-site end-session callback redirects
This is an automated email from the ASF dual-hosted git repository.
yasithdev pushed a commit to branch stack-bugfixes
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit cfb83c35fa16f710f2c7ccec0ce980c793760d3b
Author: Yasith Jayawardana <ya...@icloud.com>
AuthorDate: Sat Oct 17 22:51:49 2026 -0400
block off-site end-session callback redirects
---
web/src/app/api/auth/end-session/__tests__/route.test.ts | 12 ++++++++++++
web/src/app/api/auth/end-session/route.ts | 4 +++-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/web/src/app/api/auth/end-session/__tests__/route.test.ts b/web/src/app/api/auth/end-session/__tests__/route.test.ts
index 64e9e0860..4420014cd 100644
--- a/web/src/app/api/auth/end-session/__tests__/route.test.ts
+++ b/web/src/app/api/auth/end-session/__tests__/route.test.ts
@@ -75,6 +75,18 @@ describe("end-session route", () => {
expect(res.headers.get("location")).toBe("https://portal.example.org/");
});
+ it("falls back to the deployment root for off-site callbacks", async () => {
+ for (const callbackUrl of ["//evil.example", "https://evil.example/x"]) {
+ fetchMock.mockResolvedValueOnce(discoveryResponse({}));
+ const res = await GET(
+ new NextRequest(
+ `http://localhost:3000/api/auth/end-session?callbackUrl=${encodeURIComponent(callbackUrl)}`,
+ ),
+ );
+ expect(res.headers.get("location")).toBe("https://portal.example.org/");
+ }
+ });
+
it("redirects locally when discovery fails", async () => {
fetchMock.mockRejectedValueOnce(new Error("network"));
const res = await GET(request());
diff --git a/web/src/app/api/auth/end-session/route.ts b/web/src/app/api/auth/end-session/route.ts
index 37281efd8..68e703d5a 100644
--- a/web/src/app/api/auth/end-session/route.ts
+++ b/web/src/app/api/auth/end-session/route.ts
@@ -64,7 +64,9 @@ export async function GET(request: NextRequest) {
const callbackUrl = request.nextUrl.searchParams.get("callbackUrl") ?? "/";
// Behind a reverse proxy request.nextUrl.origin is the internal address.
const origin = serverEnv.NEXTAUTH_URL ?? request.nextUrl.origin;
- const postLogout = new URL(callbackUrl, origin).toString();
+ const resolved = new URL(callbackUrl, origin);
+ const base = new URL("/", origin);
+ const postLogout = (resolved.origin === base.origin ? resolved : base).toString();
const session = await auth();
const idToken = session?.idToken ?? null;