Backend handoff failure names the status, never the app's answer
Same class as Soul pass 4's F1: deliverBackendHandoff put the receiving app's whole response body into its error message. The handoff carries tokens and an app's error page can quote it, and the message reaches whatever catches the error. It now names the status alone and discards the body unread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
698f5dfc0e
commit
b5b980578c
2 changed files with 30 additions and 4 deletions
|
|
@ -44,10 +44,10 @@ export async function deliverBackendHandoff(callbackUrl: string, payload: Backen
|
|||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
// The status alone: the app's answer body can quote the handoff, which
|
||||
// carries tokens, and whatever catches this may record or print it.
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(
|
||||
`Backend handoff delivery failed (status ${response.status}): ${body || "empty response"}`
|
||||
);
|
||||
await response.body?.cancel();
|
||||
throw new Error(`Backend handoff delivery failed (status ${response.status}).`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { type FastifyInstance } from "fastify";
|
||||
import { buildApp } from "../src/app.js";
|
||||
import { deliverBackendHandoff, type BackendHandoffPayload } from "../src/backend-handoff.js";
|
||||
import { type HeimdallConfig, loadConfig } from "../src/config.js";
|
||||
import { entitlementFacts, identityFacts } from "../src/facts.js";
|
||||
import { createOAuthRuntimeRegistry, type OAuthProviderRuntime } from "../src/oauth.js";
|
||||
|
|
@ -1588,3 +1589,28 @@ describe("the failed-callback audit event for a provider's HTTP refusal", () =>
|
|||
expect(failed?.eventPayloadJson.providerStatus).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("a backend handoff the app refuses", () => {
|
||||
it("fails by status alone, never with the app's answer body", async () => {
|
||||
globalThis.fetch = async () => new Response('{"error":"rejected","echo":"CANARYhandoff"}', { status: 500 });
|
||||
const failure = await deliverBackendHandoff("https://bifrost.gamecult.org/auth/heimdall/callback", {
|
||||
source: "heimdall",
|
||||
kind: "oauth_result",
|
||||
handoffKind: "backend_callback",
|
||||
attemptId: "attempt-1",
|
||||
status: "error",
|
||||
provider: "discord",
|
||||
appSlug: "bifrost",
|
||||
returnTo: "https://bifrost.gamecult.org/",
|
||||
connection: null,
|
||||
error: "oauth_callback_failed",
|
||||
} as BackendHandoffPayload).then(
|
||||
() => undefined,
|
||||
(error: unknown) => error
|
||||
);
|
||||
|
||||
expect(failure).toBeInstanceOf(Error);
|
||||
expect((failure as Error).message).toBe("Backend handoff delivery failed (status 500).");
|
||||
expect(JSON.stringify(failure) + String(failure)).not.toContain("CANARY");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue