From 765ba30f9be13a71414abaf04ce7fcfc9d1a7f07 Mon Sep 17 00:00:00 2001 From: Rui Conti Date: Thu, 9 Jul 2026 13:28:03 -0400 Subject: [PATCH] fix(eve): detect credentialed deployment protection Signed-off-by: Rui Conti --- .changeset/bright-dogs-login.md | 5 +++++ .../eve/src/cli/dev/tui/remote-connection.test.ts | 10 ++++++++++ .../services/dev-client/vercel-auth-error.test.ts | 10 ++++++++++ .../src/services/dev-client/vercel-auth-error.ts | 13 +++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 .changeset/bright-dogs-login.md diff --git a/.changeset/bright-dogs-login.md b/.changeset/bright-dogs-login.md new file mode 100644 index 000000000..e3afc52ab --- /dev/null +++ b/.changeset/bright-dogs-login.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Start remote authentication when a credentialed Vercel deployment returns an `UNAUTHORIZED` protection response. diff --git a/packages/eve/src/cli/dev/tui/remote-connection.test.ts b/packages/eve/src/cli/dev/tui/remote-connection.test.ts index 09bd03040..81422ef55 100644 --- a/packages/eve/src/cli/dev/tui/remote-connection.test.ts +++ b/packages/eve/src/cli/dev/tui/remote-connection.test.ts @@ -189,6 +189,16 @@ describe("createRemoteConnectionController", () => { challenge: { kind: "vercel-deployment-protection" }, }, }, + { + name: "Vercel's credentialed Deployment Protection rejection", + error: new ClientError(401, "You must sign in\n\nUNAUTHORIZED\n\niad1::request-id\n", { + "x-vercel-error": "UNAUTHORIZED", + }), + expected: { + state: "auth-required", + challenge: { kind: "vercel-deployment-protection" }, + }, + }, { name: "a 403 Trusted Sources environment mismatch", error: new ClientError(403, TRUSTED_SOURCES_MISMATCH), diff --git a/packages/eve/src/services/dev-client/vercel-auth-error.test.ts b/packages/eve/src/services/dev-client/vercel-auth-error.test.ts index 3ea67e460..645f69956 100644 --- a/packages/eve/src/services/dev-client/vercel-auth-error.test.ts +++ b/packages/eve/src/services/dev-client/vercel-auth-error.test.ts @@ -64,6 +64,16 @@ describe("isVercelAuthChallenge", () => { ); }); + it("detects Vercel's credentialed deployment-protection rejection", () => { + expect( + isVercelAuthChallenge( + new ClientError(401, "You must sign in\n\nUNAUTHORIZED\n\niad1::request-id\n", { + "x-vercel-error": "UNAUTHORIZED", + }), + ), + ).toBe(true); + }); + it("requires HTTP 401 and the complete legacy HTML challenge signature", () => { expect(isVercelAuthChallenge(new ClientError(500, VERCEL_SSO_CHALLENGE_BODY))).toBe(false); expect( diff --git a/packages/eve/src/services/dev-client/vercel-auth-error.ts b/packages/eve/src/services/dev-client/vercel-auth-error.ts index 8a310cffd..f901fc4ac 100644 --- a/packages/eve/src/services/dev-client/vercel-auth-error.ts +++ b/packages/eve/src/services/dev-client/vercel-auth-error.ts @@ -72,6 +72,18 @@ function isVercelSsoRedirect( return false; } +function isVercelUnauthorized( + status: number, + headers: Readonly> | undefined, +): boolean { + if (status !== 401 || headers === undefined) return false; + + for (const [name, value] of Object.entries(headers)) { + if (name.toLowerCase() === "x-vercel-error" && value === "UNAUTHORIZED") return true; + } + return false; +} + function bodyLooksLikeStructuredVercelAuthChallenge(body: string): boolean { let payload: unknown; try { @@ -114,6 +126,7 @@ export function isVercelAuthChallenge(error: unknown): boolean { const headers = isObject(candidate.headers) ? candidate.headers : undefined; return ( isVercelSsoRedirect(candidate.status, headers) || + isVercelUnauthorized(candidate.status, headers) || (candidate.status === 401 && (bodyLooksLikeVercelAuthChallenge(candidate.body) || bodyLooksLikeStructuredVercelAuthChallenge(candidate.body)))