From c6d0ecb68ba94817d08e52bee40fee6d0aad38fd Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 8 Sep 2026 13:15:25 +0200 Subject: [PATCH] Delete hard-coded Repixelizer callback trust (deletion_line 2) acceptsBackendCallback is now a pure per-app env allow-list lookup; Repixelizer's callback must be configured via GC_ACCESS_APP_REPIXELIZER_BACKEND_CALLBACK_URLS like every other app (R8: operator sets this on the running legacy body before the new build starts). Test config adds the allow-list entry so existing Repixelizer flows keep passing, and a new negative test pins that the slug name alone buys nothing. --- src/app.ts | 10 +--------- tests/app.test.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/app.ts b/src/app.ts index 0b2f2a6..d27e72f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -508,15 +508,7 @@ function acceptsBackendCallback(config: HeimdallConfig, appSlug: AppSlug, handof return false; } - if ((config.appBackendCallbacks[appSlug] ?? []).includes(handoff.callbackUrl)) { - return true; - } - - if (appSlug === "repixelizer") { - return handoff.callbackUrl === "https://repixelizer.gamecult.org/api/auth/heimdall/callback"; - } - - return false; + return (config.appBackendCallbacks[appSlug] ?? []).includes(handoff.callbackUrl); } async function maybeDeliverBackendHandoff(handoff: OAuthHandoff, payload: BackendHandoffPayload): Promise { diff --git a/tests/app.test.ts b/tests/app.test.ts index 938003b..47196c6 100644 --- a/tests/app.test.ts +++ b/tests/app.test.ts @@ -32,6 +32,7 @@ function createTestConfig(): HeimdallConfig { }, appBackendCallbacks: { bifrost: ["https://bifrost.gamecult.org/auth/heimdall/callback"], + repixelizer: ["https://repixelizer.gamecult.org/api/auth/heimdall/callback"], }, storage: { backend: "memory", @@ -898,6 +899,34 @@ describe("Heimdall service", () => { ); }); + it("no longer trusts Repixelizer's callback URL by slug name alone", async () => { + const baseConfig = createTestConfig(); + const config = { + ...baseConfig, + appBackendCallbacks: {}, + }; + const app = await buildApp({ config }); + apps.push(app); + + const response = await app.inject({ + method: "POST", + url: "/v1/oauth/discord/start", + payload: { + appSlug: "repixelizer", + mode: "sign_in", + returnTo: "https://repixelizer.gamecult.org/app/", + handoff: { + kind: "backend_callback", + attemptId: "attempt-123", + callbackUrl: "https://repixelizer.gamecult.org/api/auth/heimdall/callback", + }, + }, + }); + + expect(response.statusCode).toBe(400); + expect(response.json()).toEqual(expect.objectContaining({ error: "untrusted_backend_callback" })); + }); + it("resolves app-managed provider credentials without exposing refresh custody", async () => { const store = new InMemoryStore(); const app = await buildApp({