From 790c3afc2f644f892062642151554e99d9c78af0 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 7 Oct 2026 23:24:37 -0700 Subject: [PATCH 1/9] Route sign-up and v2 paths on executor.sh to v2 --- apps/cloud/src/edge/marketing.ts | 127 +++++++++++++++++++++- apps/cloud/src/env-augment.d.ts | 7 ++ apps/cloud/src/server.ts | 7 +- apps/cloud/wrangler.jsonc | 9 ++ packages/core/api/src/account/org-slug.ts | 5 + 5 files changed, 148 insertions(+), 7 deletions(-) diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index 3e0fa5493d..0af2e9b551 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -1,10 +1,16 @@ // --------------------------------------------------------------------------- -// Marketing routes — proxied to the marketing worker via service binding. +// The `executor.sh` edge — decides which Worker answers a public request. // -// On the production domain (`executor.sh`), marketing paths and the -// unauthenticated landing page are served by the separate `executor-marketing` -// worker. This module deliberately has no TanStack Start or cloud application -// imports: the Worker entry calls it before loading the Start server graph. +// On the production domain (`executor.sh`): +// - marketing paths and the unauthenticated landing page go to the separate +// `executor-marketing` worker; +// - sign-up goes to v2 (a redirect to v2's sign-up page); +// - a fixed list of exact v2 paths (sign-in issuer metadata, social sign-in +// callbacks, the OAuth client metadata document, Git smart HTTP and the +// agent skills index) is forwarded to v2's Worker over a service binding. +// v1 owns everything not listed. This module deliberately has no TanStack +// Start or cloud application imports: the Worker entry calls it before +// loading the Start server graph. // --------------------------------------------------------------------------- import { parseCookie } from "../auth/cookies"; @@ -32,6 +38,8 @@ const MARKETING_PATHS = [ const SESSION_COOKIE = "wos-session"; +const PRODUCTION_HOST = "executor.sh"; + /** Whether an exact pathname belongs to the public marketing worker. */ export const isMarketingPath = (pathname: string): boolean => MARKETING_PATHS.some((p) => pathname === p || pathname.startsWith(`${p}/`)); @@ -42,7 +50,7 @@ export const isMarketingPath = (pathname: string): boolean => */ export const marketingProxyRequest = (request: Request): Request | null => { const url = new URL(request.url); - if (url.hostname !== "executor.sh") return null; + if (url.hostname !== PRODUCTION_HOST) return null; const shouldProxy = isMarketingPath(url.pathname) || @@ -52,3 +60,110 @@ export const marketingProxyRequest = (request: Request): Request | null => { if (url.pathname === "/home") url.pathname = "/"; return new Request(url, request); }; + +// --------------------------------------------------------------------------- +// v2 on `executor.sh` +// --------------------------------------------------------------------------- + +const SIGN_UP_PATHS: ReadonlySet = new Set(["/sign-up", "/signup"]); + +/** Paths v2 answers on `executor.sh`, matched exactly. */ +const V2_EXACT_PATHS: ReadonlySet = new Set([ + // Sign-in issuer metadata for the issuer `https://executor.sh/api/auth`. + "/.well-known/oauth-authorization-server/api/auth", + "/api/auth/.well-known/openid-configuration", + // Outbound OAuth client metadata document. v1's own document lives at + // `/oauth/client-id-metadata.json`, which stays with v1. + "/oauth/client-metadata.json", +]); + +/** Path trees v2 answers on `executor.sh`. Each prefix ends in `/`, so + * `/gitlab/...` never matches `/git/`. */ +const V2_PATH_PREFIXES: ReadonlyArray = ["/git/", "/.well-known/agent-skills/"]; + +/** v2's social sign-in callback is `/api/auth/callback/`. v1's + * WorkOS callback is the bare `/api/auth/callback`, which stays with v1. */ +const SOCIAL_CALLBACK_PREFIX = "/api/auth/callback/"; + +const isSocialCallbackPath = (pathname: string): boolean => { + if (!pathname.startsWith(SOCIAL_CALLBACK_PREFIX)) return false; + const provider = pathname.slice(SOCIAL_CALLBACK_PREFIX.length); + return provider.length > 0 && !provider.includes("/"); +}; + +/** Whether a pathname is a sign-up entry point that redirects to v2. */ +export const isSignUpPath = (pathname: string): boolean => SIGN_UP_PATHS.has(pathname); + +/** Whether `executor.sh` forwards a pathname to v2's Worker. */ +export const isV2Path = (pathname: string): boolean => + V2_EXACT_PATHS.has(pathname) || + V2_PATH_PREFIXES.some((prefix) => pathname.startsWith(prefix)) || + isSocialCallbackPath(pathname); + +/** Request headers v1 never passes to v2. The cookie header carries v1's + * `wos-session` (v2's cookies are host-only on its own hosts, so nothing of + * v2's travels on `executor.sh`). Client-sent forwarding headers are dropped + * so v2 sees no host claim other than the request URL's. */ +const V2_STRIPPED_HEADERS = ["cookie", "x-forwarded-host", "x-forwarded-proto"] as const; + +/** + * Project an `executor.sh` request onto the request sent to v2's Worker. + * + * Keeps the URL (so v2 sees host `executor.sh`, path and query unchanged), + * method, body stream and every header except {@link V2_STRIPPED_HEADERS}, + * including `Authorization`. Redirects are returned to the client, not + * followed: v2 answers callbacks with a redirect to its own host. + */ +export const v2ForwardRequest = (request: Request): Request => { + const headers = new Headers(request.headers); + for (const name of V2_STRIPPED_HEADERS) headers.delete(name); + return new Request(request, { headers, redirect: "manual" }); +}; + +/** The Worker that serves v2, reached over a service binding. */ +export interface V2Service { + readonly fetch: (request: Request) => Promise; +} + +/** What the edge needs to hand requests to v2. */ +export interface V2Edge { + /** v2's Worker. */ + readonly service: V2Service; + /** Absolute URL of v2's sign-up page (the `V2_SIGN_UP_URL` var). */ + readonly signUpUrl: URL; +} + +/** + * Parse the edge's v2 settings from the Worker environment. Returns `null` + * when the binding or the sign-up URL is absent or the URL is not absolute, + * so hosts without them (local dev, test workers) keep serving everything + * from v1. + */ +export const parseV2Edge = ( + service: V2Service | undefined, + signUpUrl: string | undefined, +): V2Edge | null => { + if (service === undefined || signUpUrl === undefined) return null; + const parsed = URL.parse(signUpUrl); + if (parsed === null || (parsed.protocol !== "https:" && parsed.protocol !== "http:")) { + return null; + } + return { service, signUpUrl: parsed }; +}; + +/** + * Answer a production request that belongs to v2: redirect sign-up (`GET`, + * any query) to v2's sign-up page, or forward a {@link isV2Path} request to + * v2's Worker and return its response unchanged (status, headers and body + * stream). Returns `null` when v1 owns the request. + */ +export const v2EdgeResponse = (request: Request, edge: V2Edge): Promise | null => { + const url = new URL(request.url); + if (url.hostname !== PRODUCTION_HOST) return null; + + if (isSignUpPath(url.pathname) && request.method === "GET") { + return Promise.resolve(Response.redirect(edge.signUpUrl.href, 302)); + } + if (isV2Path(url.pathname)) return edge.service.fetch(v2ForwardRequest(request)); + return null; +}; diff --git a/apps/cloud/src/env-augment.d.ts b/apps/cloud/src/env-augment.d.ts index 017570cf1a..4af210008e 100644 --- a/apps/cloud/src/env-augment.d.ts +++ b/apps/cloud/src/env-augment.d.ts @@ -137,6 +137,13 @@ declare global { MCP_RESIDENT_RUNTIME_SOFT_CAP?: string; NODE_ENV?: string; + // v2 on executor.sh (wrangler.jsonc `services` + `vars`). Optional so + // local dev and test workers without them serve everything from v1. + /** Service binding to v2's API Worker; `edge/marketing.ts` forwards to it. */ + V2?: Fetcher; + /** Absolute URL `/sign-up` and `/signup` redirect to. */ + V2_SIGN_UP_URL?: string; + // Shared with frontend VITE_PUBLIC_SITE_URL?: string; VITE_PUBLIC_OTLP_TRACES_URL?: string; diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 2dde0e8403..fa41d3582d 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -11,7 +11,7 @@ import * as Sentry from "@sentry/cloudflare"; import handler from "@tanstack/react-start/server-entry"; import { isAppOwnedPath, servedByAppPlane } from "./app-paths"; -import { marketingProxyRequest } from "./edge/marketing"; +import { marketingProxyRequest, parseV2Edge, v2EdgeResponse } from "./edge/marketing"; import { passthroughResponse } from "./edge/passthrough"; import { withPrivateReferrerPolicy } from "./edge/referrer-policy"; import { runWorkOsEventsSync } from "./auth/workos-events-runner"; @@ -318,6 +318,11 @@ const cloudflareHandler = { prewarmAppPlane(ctx); } + // Sign-up and the fixed list of v2 paths on `executor.sh` go to v2. + const v2Edge = parseV2Edge(env.V2, env.V2_SIGN_UP_URL); + const v2 = v2Edge && v2EdgeResponse(request, v2Edge); + if (v2) return v2; + const marketingRequest = marketingProxyRequest(request); const marketing: Fetcher | undefined = env.MARKETING; if (marketingRequest && marketing) return marketing.fetch(marketingRequest); diff --git a/apps/cloud/wrangler.jsonc b/apps/cloud/wrangler.jsonc index 04da6b0e34..44be69363a 100644 --- a/apps/cloud/wrangler.jsonc +++ b/apps/cloud/wrangler.jsonc @@ -95,6 +95,13 @@ "binding": "MARKETING", "service": "executor-marketing", }, + // v2's API Worker (the executor-next `v2` stage, same account). The edge + // (src/edge/marketing.ts) forwards a fixed list of executor.sh paths to + // it with the URL unchanged, so v2 sees host `executor.sh`. + { + "binding": "V2", + "service": "executor-next-hosted-api-v2-qs32brgjwvt7ytx4", + }, ], "hyperdrive": [ { @@ -133,6 +140,8 @@ }, "vars": { "VITE_PUBLIC_SITE_URL": "https://executor.sh", + // Where /sign-up and /signup redirect: v2's sign-up page. + "V2_SIGN_UP_URL": "https://v2.executor.sh/login?mode=signup", // Keeps the /__sentry-otel-verify probe live in production: Sentry // delivery failed silently for weeks (zero events after ~Jul 30 with an // active DSN), and without this there is no way to test the pipeline diff --git a/packages/core/api/src/account/org-slug.ts b/packages/core/api/src/account/org-slug.ts index addd842831..2950130233 100644 --- a/packages/core/api/src/account/org-slug.ts +++ b/packages/core/api/src/account/org-slug.ts @@ -27,6 +27,9 @@ const ORG_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){1,47}$/; * - Marketing worker: home, setup, privacy, terms, blog, pricing, careers, * changelog, _astro (executor.sh edge routes; the * non-route names are cheap insurance) + * - v2 on executor.sh: git (Git smart HTTP remotes the executor.sh edge + * forwards to v2), plus oauth, api, app, mcp, docs, + * sign-up, signup, pricing and blog listed elsewhere here * - Infra: assets (vite build output), cdn-cgi (Cloudflare), * static, public, favicon.ico, robots.txt, sitemap.xml * - Auth flows: auth, oauth, callback, logout, signin, signout, @@ -69,6 +72,8 @@ export const RESERVED_ORG_SLUGS: ReadonlySet = new Set([ "careers", "changelog", "_astro", + // v2 on executor.sh + "git", // infra "assets", "cdn-cgi", From 231698ef0ae17f1ff6e76ed6d193cbecde037baa Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 7 Oct 2026 23:33:21 -0700 Subject: [PATCH 2/9] Fail edge requests on broken v2 settings instead of serving them from v1 --- apps/cloud/src/edge/marketing.ts | 35 +++++++++++++++++++++----------- apps/cloud/src/server.ts | 5 ++--- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index 0af2e9b551..2aa3e5b602 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -135,18 +135,22 @@ export interface V2Edge { /** * Parse the edge's v2 settings from the Worker environment. Returns `null` - * when the binding or the sign-up URL is absent or the URL is not absolute, - * so hosts without them (local dev, test workers) keep serving everything - * from v1. + * when neither the binding nor the sign-up URL is set, so hosts without them + * (local dev, test workers) keep serving everything from v1. Throws when only + * one is set or the URL is not absolute, so a broken deployment fails loudly + * instead of silently serving sign-up from v1. */ export const parseV2Edge = ( service: V2Service | undefined, signUpUrl: string | undefined, ): V2Edge | null => { - if (service === undefined || signUpUrl === undefined) return null; + if (service === undefined && signUpUrl === undefined) return null; + if (service === undefined || signUpUrl === undefined) { + throw new Error("The V2 binding and V2_SIGN_UP_URL must be set together"); + } const parsed = URL.parse(signUpUrl); if (parsed === null || (parsed.protocol !== "https:" && parsed.protocol !== "http:")) { - return null; + throw new Error("V2_SIGN_UP_URL must be an absolute http(s) URL"); } return { service, signUpUrl: parsed }; }; @@ -155,15 +159,22 @@ export const parseV2Edge = ( * Answer a production request that belongs to v2: redirect sign-up (`GET`, * any query) to v2's sign-up page, or forward a {@link isV2Path} request to * v2's Worker and return its response unchanged (status, headers and body - * stream). Returns `null` when v1 owns the request. + * stream). Returns `null` when v1 owns the request. The settings are parsed + * only for requests the edge owns, so a broken setting fails those requests + * and leaves the rest of v1 serving. */ -export const v2EdgeResponse = (request: Request, edge: V2Edge): Promise | null => { +export const v2EdgeResponse = ( + request: Request, + service: V2Service | undefined, + signUpUrl: string | undefined, +): Promise | null => { const url = new URL(request.url); if (url.hostname !== PRODUCTION_HOST) return null; - if (isSignUpPath(url.pathname) && request.method === "GET") { - return Promise.resolve(Response.redirect(edge.signUpUrl.href, 302)); - } - if (isV2Path(url.pathname)) return edge.service.fetch(v2ForwardRequest(request)); - return null; + const signUp = isSignUpPath(url.pathname) && request.method === "GET"; + if (!signUp && !isV2Path(url.pathname)) return null; + const edge = parseV2Edge(service, signUpUrl); + if (edge === null) return null; + if (signUp) return Promise.resolve(Response.redirect(edge.signUpUrl.href, 302)); + return edge.service.fetch(v2ForwardRequest(request)); }; diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index fa41d3582d..a9e98183fd 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -11,7 +11,7 @@ import * as Sentry from "@sentry/cloudflare"; import handler from "@tanstack/react-start/server-entry"; import { isAppOwnedPath, servedByAppPlane } from "./app-paths"; -import { marketingProxyRequest, parseV2Edge, v2EdgeResponse } from "./edge/marketing"; +import { marketingProxyRequest, v2EdgeResponse } from "./edge/marketing"; import { passthroughResponse } from "./edge/passthrough"; import { withPrivateReferrerPolicy } from "./edge/referrer-policy"; import { runWorkOsEventsSync } from "./auth/workos-events-runner"; @@ -319,8 +319,7 @@ const cloudflareHandler = { } // Sign-up and the fixed list of v2 paths on `executor.sh` go to v2. - const v2Edge = parseV2Edge(env.V2, env.V2_SIGN_UP_URL); - const v2 = v2Edge && v2EdgeResponse(request, v2Edge); + const v2 = v2EdgeResponse(request, env.V2, env.V2_SIGN_UP_URL); if (v2) return v2; const marketingRequest = marketingProxyRequest(request); From 6eea400e1db73afdbe625a00a21a67376d76b410 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 7 Oct 2026 23:35:10 -0700 Subject: [PATCH 3/9] Answer edge requests with a 500 on broken v2 settings --- apps/cloud/src/edge/marketing.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index 2aa3e5b602..30e8b8da3f 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -135,22 +135,21 @@ export interface V2Edge { /** * Parse the edge's v2 settings from the Worker environment. Returns `null` - * when neither the binding nor the sign-up URL is set, so hosts without them - * (local dev, test workers) keep serving everything from v1. Throws when only - * one is set or the URL is not absolute, so a broken deployment fails loudly - * instead of silently serving sign-up from v1. + * when neither is set (local dev, test workers), so v1 serves everything, and + * the reason as a string when the deployment is broken: only one of them set, + * or a sign-up URL that is not absolute. */ export const parseV2Edge = ( service: V2Service | undefined, signUpUrl: string | undefined, -): V2Edge | null => { +): V2Edge | string | null => { if (service === undefined && signUpUrl === undefined) return null; if (service === undefined || signUpUrl === undefined) { - throw new Error("The V2 binding and V2_SIGN_UP_URL must be set together"); + return "The V2 binding and V2_SIGN_UP_URL must be set together"; } const parsed = URL.parse(signUpUrl); if (parsed === null || (parsed.protocol !== "https:" && parsed.protocol !== "http:")) { - throw new Error("V2_SIGN_UP_URL must be an absolute http(s) URL"); + return "V2_SIGN_UP_URL must be an absolute http(s) URL"; } return { service, signUpUrl: parsed }; }; @@ -159,9 +158,8 @@ export const parseV2Edge = ( * Answer a production request that belongs to v2: redirect sign-up (`GET`, * any query) to v2's sign-up page, or forward a {@link isV2Path} request to * v2's Worker and return its response unchanged (status, headers and body - * stream). Returns `null` when v1 owns the request. The settings are parsed - * only for requests the edge owns, so a broken setting fails those requests - * and leaves the rest of v1 serving. + * stream). Returns `null` when v1 owns the request. Broken settings answer + * the requests the edge owns with a 500 and leave the rest of v1 serving. */ export const v2EdgeResponse = ( request: Request, @@ -175,6 +173,10 @@ export const v2EdgeResponse = ( if (!signUp && !isV2Path(url.pathname)) return null; const edge = parseV2Edge(service, signUpUrl); if (edge === null) return null; + if (typeof edge === "string") { + console.error(`executor.sh v2 edge misconfigured: ${edge}`); + return Promise.resolve(new Response("Service misconfigured", { status: 500 })); + } if (signUp) return Promise.resolve(Response.redirect(edge.signUpUrl.href, 302)); return edge.service.fetch(v2ForwardRequest(request)); }; From 5bb09cdbe4aae55a29a50b4e48f1a2595f79735f Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 06:33:31 -0700 Subject: [PATCH 4/9] Forward only v2's contract paths and reserve the apps and experiments slugs --- apps/cloud/src/edge/marketing.ts | 33 ++++++++++++----------- packages/core/api/src/account/org-slug.ts | 8 ++++-- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index 30e8b8da3f..62d2b77b98 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -5,9 +5,9 @@ // - marketing paths and the unauthenticated landing page go to the separate // `executor-marketing` worker; // - sign-up goes to v2 (a redirect to v2's sign-up page); -// - a fixed list of exact v2 paths (sign-in issuer metadata, social sign-in -// callbacks, the OAuth client metadata document, Git smart HTTP and the -// agent skills index) is forwarded to v2's Worker over a service binding. +// - a fixed list of v2 paths (sign-in issuer metadata, social sign-in +// callbacks, Git smart HTTP and the agent skills index) is forwarded to +// v2's Worker over a service binding. // v1 owns everything not listed. This module deliberately has no TanStack // Start or cloud application imports: the Worker entry calls it before // loading the Start server graph. @@ -67,19 +67,21 @@ export const marketingProxyRequest = (request: Request): Request | null => { const SIGN_UP_PATHS: ReadonlySet = new Set(["/sign-up", "/signup"]); -/** Paths v2 answers on `executor.sh`, matched exactly. */ -const V2_EXACT_PATHS: ReadonlySet = new Set([ +/** A path pattern is an exact path, or `/x/*`, which matches every path + * that starts with `/x/` (and not `/x` itself). */ +const matchesPathPattern = (pathname: string, pattern: string): boolean => + pattern.endsWith("/*") ? pathname.startsWith(pattern.slice(0, -1)) : pathname === pattern; + +/** Path patterns v2 answers on `executor.sh`. `/git/*` never matches + * `/gitlab/...`. v2's outbound OAuth client metadata document stays off this + * list: its move to `executor.sh` is pending, and v1's own document + * (`/oauth/client-id-metadata.json`) stays with v1. */ +const V2_PATHS: ReadonlyArray = [ // Sign-in issuer metadata for the issuer `https://executor.sh/api/auth`. "/.well-known/oauth-authorization-server/api/auth", - "/api/auth/.well-known/openid-configuration", - // Outbound OAuth client metadata document. v1's own document lives at - // `/oauth/client-id-metadata.json`, which stays with v1. - "/oauth/client-metadata.json", -]); - -/** Path trees v2 answers on `executor.sh`. Each prefix ends in `/`, so - * `/gitlab/...` never matches `/git/`. */ -const V2_PATH_PREFIXES: ReadonlyArray = ["/git/", "/.well-known/agent-skills/"]; + "/git/*", + "/.well-known/agent-skills/*", +]; /** v2's social sign-in callback is `/api/auth/callback/`. v1's * WorkOS callback is the bare `/api/auth/callback`, which stays with v1. */ @@ -96,8 +98,7 @@ export const isSignUpPath = (pathname: string): boolean => SIGN_UP_PATHS.has(pat /** Whether `executor.sh` forwards a pathname to v2's Worker. */ export const isV2Path = (pathname: string): boolean => - V2_EXACT_PATHS.has(pathname) || - V2_PATH_PREFIXES.some((prefix) => pathname.startsWith(prefix)) || + V2_PATHS.some((pattern) => matchesPathPattern(pathname, pattern)) || isSocialCallbackPath(pathname); /** Request headers v1 never passes to v2. The cookie header carries v1's diff --git a/packages/core/api/src/account/org-slug.ts b/packages/core/api/src/account/org-slug.ts index 2950130233..5b83758805 100644 --- a/packages/core/api/src/account/org-slug.ts +++ b/packages/core/api/src/account/org-slug.ts @@ -28,8 +28,10 @@ const ORG_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){1,47}$/; * changelog, _astro (executor.sh edge routes; the * non-route names are cheap insurance) * - v2 on executor.sh: git (Git smart HTTP remotes the executor.sh edge - * forwards to v2), plus oauth, api, app, mcp, docs, - * sign-up, signup, pricing and blog listed elsewhere here + * forwards to v2), apps and experiments (v2 marketing + * pages; no v1 organization uses them), plus oauth, + * api, app, mcp, docs, sign-up, signup, pricing and + * blog listed elsewhere here * - Infra: assets (vite build output), cdn-cgi (Cloudflare), * static, public, favicon.ico, robots.txt, sitemap.xml * - Auth flows: auth, oauth, callback, logout, signin, signout, @@ -74,6 +76,8 @@ export const RESERVED_ORG_SLUGS: ReadonlySet = new Set([ "_astro", // v2 on executor.sh "git", + "apps", + "experiments", // infra "assets", "cdn-cgi", From 08635aa527304bb7dbecc63876855171fcca0fce Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 7 Oct 2026 23:24:37 -0700 Subject: [PATCH 5/9] Test the executor.sh v2 edge and reserved slugs --- apps/cloud/src/edge/marketing.test.ts | 271 +++++++++++++++++- .../core/api/src/account/org-slug.test.ts | 29 ++ 2 files changed, 299 insertions(+), 1 deletion(-) diff --git a/apps/cloud/src/edge/marketing.test.ts b/apps/cloud/src/edge/marketing.test.ts index b6af946dda..c36e090588 100644 --- a/apps/cloud/src/edge/marketing.test.ts +++ b/apps/cloud/src/edge/marketing.test.ts @@ -1,6 +1,15 @@ import { describe, expect, it } from "@effect/vitest"; -import { isMarketingPath, marketingProxyRequest } from "./marketing"; +import { + isMarketingPath, + isSignUpPath, + isV2Path, + marketingProxyRequest, + parseV2Edge, + v2EdgeResponse, + type V2Edge, + type V2Service, +} from "./marketing"; // On executor.sh the marketing middleware proxies an allow-list of paths to the // `executor-marketing` worker; everything else falls through to the auth-gated @@ -101,3 +110,263 @@ describe("marketingProxyRequest", () => { expect(marketingProxyRequest(new Request("https://executor.sh/login"))).toBeNull(); }); }); + +// v2 on executor.sh: sign-up redirects to v2, and a fixed list of exact paths +// is forwarded to v2's Worker. Everything else stays with v1. +describe("isV2Path", () => { + const forwarded = [ + "/.well-known/oauth-authorization-server/api/auth", + "/api/auth/.well-known/openid-configuration", + "/api/auth/callback/google", + "/api/auth/callback/github", + "/oauth/client-metadata.json", + "/git/acme/tools/info/refs", + "/git/acme/tools/git-upload-pack", + "/git/acme/tools/git-receive-pack", + "/.well-known/agent-skills/", + "/.well-known/agent-skills/index.json", + ]; + for (const pathname of forwarded) { + it(`forwards ${pathname} to v2`, () => { + expect(isV2Path(pathname)).toBe(true); + }); + } + + const v1Owned = [ + // v1's WorkOS callback has no provider segment. + "/api/auth/callback", + "/api/auth/callback/", + "/api/auth/callback/google/extra", + "/api/auth/login", + "/api/auth/me", + // v1's own issuer metadata and client metadata document. + "/.well-known/oauth-authorization-server", + "/.well-known/oauth-authorization-server/api/auth/extra", + "/.well-known/oauth-protected-resource/mcp", + "/api/auth/.well-known/openid-configuration/extra", + "/oauth/client-id-metadata.json", + "/oauth/client-metadata.json/extra", + // Git remotes need a path under /git/. + "/git", + "/gitlab/acme/tools/info/refs", + "/github", + "/.well-known/agent-skills", + "/.well-known/agent-skillset/index.json", + // Not yet: connected-account callback and marketing. + "/api/oauth/callback", + "/pricing", + // v1 dashboard, org pages and MCP, including org slugs that look similar. + "/", + "/login", + "/mcp", + "/acme/mcp", + "/gitops/mcp", + "/git-team/policies", + "/oauth-team/mcp", + "/api/connections", + ]; + for (const pathname of v1Owned) { + it(`leaves ${pathname} with v1`, () => { + expect(isV2Path(pathname)).toBe(false); + }); + } +}); + +describe("isSignUpPath", () => { + it("claims /sign-up and /signup only", () => { + expect(isSignUpPath("/sign-up")).toBe(true); + expect(isSignUpPath("/signup")).toBe(true); + expect(isSignUpPath("/sign-up/")).toBe(false); + expect(isSignUpPath("/signup/team")).toBe(false); + expect(isSignUpPath("/sign-in")).toBe(false); + expect(isSignUpPath("/signups")).toBe(false); + }); +}); + +describe("parseV2Edge", () => { + const service: V2Service = { fetch: () => Promise.resolve(new Response(null)) }; + + it("needs both the binding and an absolute sign-up URL", () => { + expect(parseV2Edge(undefined, "https://v2.executor.sh/login?mode=signup")).toBeNull(); + expect(parseV2Edge(service, undefined)).toBeNull(); + expect(parseV2Edge(service, "/login?mode=signup")).toBeNull(); + expect(parseV2Edge(service, "javascript:alert(1)")).toBeNull(); + expect(parseV2Edge(service, "https://v2.executor.sh/login?mode=signup")?.signUpUrl.href).toBe( + "https://v2.executor.sh/login?mode=signup", + ); + }); +}); + +describe("v2EdgeResponse", () => { + const SIGN_UP_URL = "https://v2.executor.sh/login?mode=signup"; + + /** A v2 service that records what it receives and answers with `respond`. */ + const recordingService = (respond: (request: Request) => Promise | Response) => { + const received: Request[] = []; + const service: V2Service = { + fetch: async (request) => { + received.push(request); + return respond(request); + }, + }; + return { received, service }; + }; + + const edgeWith = (service: V2Service): V2Edge => { + const edge = parseV2Edge(service, SIGN_UP_URL); + if (edge === null) return expect.unreachable("edge settings must parse"); + return edge; + }; + + it("redirects sign-up to v2's configured sign-up page, ignoring the query", async () => { + const { received, service } = recordingService(() => new Response(null)); + const edge = edgeWith(service); + + for (const url of ["https://executor.sh/sign-up", "https://executor.sh/signup?ref=docs"]) { + const response = await v2EdgeResponse(new Request(url), edge); + expect(response?.status).toBe(302); + expect(response?.headers.get("location")).toBe(SIGN_UP_URL); + } + expect(received).toHaveLength(0); + }); + + it("follows the configured sign-up URL", async () => { + const edge = parseV2Edge( + { fetch: () => Promise.resolve(new Response(null)) }, + "https://app.executor.sh/sign-up", + ); + if (edge === null) return expect.unreachable("edge settings must parse"); + + const response = await v2EdgeResponse(new Request("https://executor.sh/signup"), edge); + expect(response?.headers.get("location")).toBe("https://app.executor.sh/sign-up"); + }); + + it("leaves non-GET sign-up requests with v1", () => { + const edge = edgeWith(recordingService(() => new Response(null)).service); + expect( + v2EdgeResponse(new Request("https://executor.sh/sign-up", { method: "POST" }), edge), + ).toBeNull(); + }); + + it("only acts on executor.sh", () => { + const edge = edgeWith(recordingService(() => new Response(null)).service); + expect(v2EdgeResponse(new Request("http://executor-cloud.localhost/sign-up"), edge)).toBeNull(); + expect( + v2EdgeResponse(new Request("https://v2.executor.sh/api/auth/callback/google"), edge), + ).toBeNull(); + }); + + it("leaves v1 paths with v1", () => { + const { received, service } = recordingService(() => new Response(null)); + const edge = edgeWith(service); + expect( + v2EdgeResponse(new Request("https://executor.sh/api/auth/callback?code=c"), edge), + ).toBeNull(); + expect(v2EdgeResponse(new Request("https://executor.sh/gitlab/x/info/refs"), edge)).toBeNull(); + expect(received).toHaveLength(0); + }); + + it("forwards with the public host, path and query, and returns v2's redirect unfollowed", async () => { + const { received, service } = recordingService( + () => + new Response(null, { + status: 302, + headers: { location: "https://app.executor.sh/api/auth/callback/google?code=c&state=s" }, + }), + ); + + const response = await v2EdgeResponse( + new Request("https://executor.sh/api/auth/callback/google?code=c&state=s"), + edgeWith(service), + ); + + expect(response?.status).toBe(302); + expect(response?.headers.get("location")).toBe( + "https://app.executor.sh/api/auth/callback/google?code=c&state=s", + ); + expect(received).toHaveLength(1); + expect(received[0]?.url).toBe("https://executor.sh/api/auth/callback/google?code=c&state=s"); + expect(received[0]?.redirect).toBe("manual"); + }); + + it("strips v1's cookies and client forwarding headers but keeps Authorization", async () => { + const { received, service } = recordingService(() => new Response("ok")); + + await v2EdgeResponse( + new Request("https://executor.sh/git/acme/tools/info/refs?service=git-upload-pack", { + headers: { + authorization: "Basic dXNlcjp0b2tlbg==", + cookie: "wos-session=sealed; ph_id=1", + "git-protocol": "version=2", + "x-forwarded-host": "attacker.example", + "x-forwarded-proto": "http", + }, + }), + edgeWith(service), + ); + + const forwarded = received[0]; + expect(forwarded?.headers.get("authorization")).toBe("Basic dXNlcjp0b2tlbg=="); + expect(forwarded?.headers.get("git-protocol")).toBe("version=2"); + expect(forwarded?.headers.has("cookie")).toBe(false); + expect(forwarded?.headers.has("x-forwarded-host")).toBe(false); + expect(forwarded?.headers.has("x-forwarded-proto")).toBe(false); + expect(new URL(forwarded?.url ?? "").search).toBe("?service=git-upload-pack"); + }); + + it("returns v2's response unchanged, status and body stream included", async () => { + const upstream = new Response("not found", { + status: 404, + headers: { "content-type": "text/plain", "www-authenticate": 'Basic realm="git"' }, + }); + const { service } = recordingService(() => upstream); + + const response = await v2EdgeResponse( + new Request("https://executor.sh/git/acme/tools/info/refs"), + edgeWith(service), + ); + + expect(response).toBe(upstream); + }); + + it("streams a git push body to v2 without buffering it, preserving the method", async () => { + const encoder = new TextEncoder(); + let source: ReadableStreamDefaultController | undefined; + const body = new ReadableStream({ + start(controller) { + source = controller; + controller.enqueue(encoder.encode("first-pack-chunk")); + }, + }); + // v2 reads the first chunk while the client is still sending: a buffering + // edge would wait for the end of the body and never reach v2. + const { received, service } = recordingService(async (request) => { + const reader = request.body?.getReader(); + if (reader === undefined) return new Response("no body", { status: 500 }); + const first = await reader.read(); + source?.enqueue(encoder.encode("second-pack-chunk")); + source?.close(); + const second = await reader.read(); + const done = await reader.read(); + const decoder = new TextDecoder(); + return new Response( + `${decoder.decode(first.value)}|${decoder.decode(second.value)}|${done.done}`, + ); + }); + + const response = await v2EdgeResponse( + new Request("https://executor.sh/git/acme/tools/git-receive-pack", { + method: "POST", + headers: { "content-type": "application/x-git-receive-pack-request" }, + body, + // @ts-expect-error -- Node's fetch needs `duplex` for a stream body; workerd does not. + duplex: "half", + }), + edgeWith(service), + ); + + expect(received[0]?.method).toBe("POST"); + expect(received[0]?.headers.get("content-type")).toBe("application/x-git-receive-pack-request"); + expect(await response?.text()).toBe("first-pack-chunk|second-pack-chunk|true"); + }); +}); diff --git a/packages/core/api/src/account/org-slug.test.ts b/packages/core/api/src/account/org-slug.test.ts index 28e5a1d6f6..c00d755bf3 100644 --- a/packages/core/api/src/account/org-slug.test.ts +++ b/packages/core/api/src/account/org-slug.test.ts @@ -88,6 +88,35 @@ describe("isValidOrgSlug", () => { expect(RESERVED_ORG_SLUGS.has(critical), critical).toBe(true); } }); + + it("reserves the root segments the executor.sh edge gives to v2", () => { + // `/git//` and the other forwarded or redirected roots would + // otherwise read as an org's console URL (`//...`). + for (const claimed of [ + "git", + "oauth", + "api", + "app", + "mcp", + "docs", + "sign-up", + "signup", + "pricing", + "blog", + ]) { + expect(isValidOrgSlug(claimed), claimed).toBe(false); + } + // Look-alikes stay claimable. + for (const lookalike of ["gitlab", "git-team", "github", "apps", "oauth-co", "blogs"]) { + expect(isValidOrgSlug(lookalike), lookalike).toBe(true); + } + }); + + it("never mints a reserved edge slug for an org name", async () => { + const slug = await generateOrgSlug("Git", async () => false); + expect(slug).not.toBe("git"); + expect(slug).toMatch(/^git-[a-z2-9]{4}$/); + }); }); describe("generateOrgSlug", () => { From 2f2a2f015ed1b59a9dc3fa4a9eaf1ef1c2a33410 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 7 Oct 2026 23:34:18 -0700 Subject: [PATCH 6/9] Test that broken edge settings fail only edge requests --- apps/cloud/src/edge/marketing.test.ts | 105 ++++++++++++++++++-------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/apps/cloud/src/edge/marketing.test.ts b/apps/cloud/src/edge/marketing.test.ts index c36e090588..899d2c73e1 100644 --- a/apps/cloud/src/edge/marketing.test.ts +++ b/apps/cloud/src/edge/marketing.test.ts @@ -7,7 +7,6 @@ import { marketingProxyRequest, parseV2Edge, v2EdgeResponse, - type V2Edge, type V2Service, } from "./marketing"; @@ -186,14 +185,26 @@ describe("isSignUpPath", () => { describe("parseV2Edge", () => { const service: V2Service = { fetch: () => Promise.resolve(new Response(null)) }; - it("needs both the binding and an absolute sign-up URL", () => { - expect(parseV2Edge(undefined, "https://v2.executor.sh/login?mode=signup")).toBeNull(); - expect(parseV2Edge(service, undefined)).toBeNull(); - expect(parseV2Edge(service, "/login?mode=signup")).toBeNull(); - expect(parseV2Edge(service, "javascript:alert(1)")).toBeNull(); - expect(parseV2Edge(service, "https://v2.executor.sh/login?mode=signup")?.signUpUrl.href).toBe( - "https://v2.executor.sh/login?mode=signup", + it("is off when neither setting is present", () => { + expect(parseV2Edge(undefined, undefined)).toBeNull(); + }); + + it("refuses a binding or sign-up URL set without the other", () => { + expect(typeof parseV2Edge(undefined, "https://v2.executor.sh/login?mode=signup")).toBe( + "string", ); + expect(typeof parseV2Edge(service, undefined)).toBe("string"); + }); + + it("refuses a sign-up URL that is not absolute http(s)", () => { + expect(typeof parseV2Edge(service, "/login?mode=signup")).toBe("string"); + expect(typeof parseV2Edge(service, "javascript:alert(1)")).toBe("string"); + }); + + it("parses both settings", () => { + const edge = parseV2Edge(service, "https://v2.executor.sh/login?mode=signup"); + if (edge === null || typeof edge === "string") return expect.unreachable("settings must parse"); + expect(edge.signUpUrl.href).toBe("https://v2.executor.sh/login?mode=signup"); }); }); @@ -212,18 +223,11 @@ describe("v2EdgeResponse", () => { return { received, service }; }; - const edgeWith = (service: V2Service): V2Edge => { - const edge = parseV2Edge(service, SIGN_UP_URL); - if (edge === null) return expect.unreachable("edge settings must parse"); - return edge; - }; - it("redirects sign-up to v2's configured sign-up page, ignoring the query", async () => { const { received, service } = recordingService(() => new Response(null)); - const edge = edgeWith(service); for (const url of ["https://executor.sh/sign-up", "https://executor.sh/signup?ref=docs"]) { - const response = await v2EdgeResponse(new Request(url), edge); + const response = await v2EdgeResponse(new Request(url), service, SIGN_UP_URL); expect(response?.status).toBe(302); expect(response?.headers.get("location")).toBe(SIGN_UP_URL); } @@ -231,38 +235,71 @@ describe("v2EdgeResponse", () => { }); it("follows the configured sign-up URL", async () => { - const edge = parseV2Edge( + const response = await v2EdgeResponse( + new Request("https://executor.sh/signup"), { fetch: () => Promise.resolve(new Response(null)) }, "https://app.executor.sh/sign-up", ); - if (edge === null) return expect.unreachable("edge settings must parse"); - - const response = await v2EdgeResponse(new Request("https://executor.sh/signup"), edge); expect(response?.headers.get("location")).toBe("https://app.executor.sh/sign-up"); }); it("leaves non-GET sign-up requests with v1", () => { - const edge = edgeWith(recordingService(() => new Response(null)).service); + const { service } = recordingService(() => new Response(null)); + expect( + v2EdgeResponse( + new Request("https://executor.sh/sign-up", { method: "POST" }), + service, + SIGN_UP_URL, + ), + ).toBeNull(); + }); + + it("answers edge requests with a 500 on a broken setting and leaves other paths with v1", async () => { + const { received, service } = recordingService(() => new Response(null)); + const response = await v2EdgeResponse( + new Request("https://executor.sh/sign-up"), + service, + "/relative", + ); + expect(response?.status).toBe(500); expect( - v2EdgeResponse(new Request("https://executor.sh/sign-up", { method: "POST" }), edge), + v2EdgeResponse(new Request("https://executor.sh/acme/mcp"), service, "/relative"), + ).toBeNull(); + expect(received).toHaveLength(0); + }); + + it("is off without settings", () => { + expect( + v2EdgeResponse(new Request("https://executor.sh/sign-up"), undefined, undefined), ).toBeNull(); }); it("only acts on executor.sh", () => { - const edge = edgeWith(recordingService(() => new Response(null)).service); - expect(v2EdgeResponse(new Request("http://executor-cloud.localhost/sign-up"), edge)).toBeNull(); + const { service } = recordingService(() => new Response(null)); + expect( + v2EdgeResponse(new Request("http://executor-cloud.localhost/sign-up"), service, SIGN_UP_URL), + ).toBeNull(); expect( - v2EdgeResponse(new Request("https://v2.executor.sh/api/auth/callback/google"), edge), + v2EdgeResponse( + new Request("https://v2.executor.sh/api/auth/callback/google"), + service, + SIGN_UP_URL, + ), ).toBeNull(); }); it("leaves v1 paths with v1", () => { const { received, service } = recordingService(() => new Response(null)); - const edge = edgeWith(service); expect( - v2EdgeResponse(new Request("https://executor.sh/api/auth/callback?code=c"), edge), + v2EdgeResponse( + new Request("https://executor.sh/api/auth/callback?code=c"), + service, + SIGN_UP_URL, + ), + ).toBeNull(); + expect( + v2EdgeResponse(new Request("https://executor.sh/gitlab/x/info/refs"), service, SIGN_UP_URL), ).toBeNull(); - expect(v2EdgeResponse(new Request("https://executor.sh/gitlab/x/info/refs"), edge)).toBeNull(); expect(received).toHaveLength(0); }); @@ -277,7 +314,8 @@ describe("v2EdgeResponse", () => { const response = await v2EdgeResponse( new Request("https://executor.sh/api/auth/callback/google?code=c&state=s"), - edgeWith(service), + service, + SIGN_UP_URL, ); expect(response?.status).toBe(302); @@ -302,7 +340,8 @@ describe("v2EdgeResponse", () => { "x-forwarded-proto": "http", }, }), - edgeWith(service), + service, + SIGN_UP_URL, ); const forwarded = received[0]; @@ -323,7 +362,8 @@ describe("v2EdgeResponse", () => { const response = await v2EdgeResponse( new Request("https://executor.sh/git/acme/tools/info/refs"), - edgeWith(service), + service, + SIGN_UP_URL, ); expect(response).toBe(upstream); @@ -362,7 +402,8 @@ describe("v2EdgeResponse", () => { // @ts-expect-error -- Node's fetch needs `duplex` for a stream body; workerd does not. duplex: "half", }), - edgeWith(service), + service, + SIGN_UP_URL, ); expect(received[0]?.method).toBe("POST"); From 422c226de11e51291aaf54d0569b0c6feda62931 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 06:34:21 -0700 Subject: [PATCH 7/9] Test the contract paths and the apps and experiments slugs --- apps/cloud/src/edge/marketing.test.ts | 12 ++++++------ packages/core/api/src/account/org-slug.test.ts | 12 +++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/cloud/src/edge/marketing.test.ts b/apps/cloud/src/edge/marketing.test.ts index 899d2c73e1..d72ac1a06c 100644 --- a/apps/cloud/src/edge/marketing.test.ts +++ b/apps/cloud/src/edge/marketing.test.ts @@ -110,15 +110,13 @@ describe("marketingProxyRequest", () => { }); }); -// v2 on executor.sh: sign-up redirects to v2, and a fixed list of exact paths -// is forwarded to v2's Worker. Everything else stays with v1. +// v2 on executor.sh: sign-up redirects to v2, and a fixed list of paths is +// forwarded to v2's Worker. Everything else stays with v1. describe("isV2Path", () => { const forwarded = [ "/.well-known/oauth-authorization-server/api/auth", - "/api/auth/.well-known/openid-configuration", "/api/auth/callback/google", "/api/auth/callback/github", - "/oauth/client-metadata.json", "/git/acme/tools/info/refs", "/git/acme/tools/git-upload-pack", "/git/acme/tools/git-receive-pack", @@ -142,9 +140,11 @@ describe("isV2Path", () => { "/.well-known/oauth-authorization-server", "/.well-known/oauth-authorization-server/api/auth/extra", "/.well-known/oauth-protected-resource/mcp", - "/api/auth/.well-known/openid-configuration/extra", "/oauth/client-id-metadata.json", - "/oauth/client-metadata.json/extra", + // v2 does not serve OpenID configuration on executor.sh, and its client + // metadata document's move to executor.sh is pending. + "/api/auth/.well-known/openid-configuration", + "/oauth/client-metadata.json", // Git remotes need a path under /git/. "/git", "/gitlab/acme/tools/info/refs", diff --git a/packages/core/api/src/account/org-slug.test.ts b/packages/core/api/src/account/org-slug.test.ts index c00d755bf3..4dad5d5ecc 100644 --- a/packages/core/api/src/account/org-slug.test.ts +++ b/packages/core/api/src/account/org-slug.test.ts @@ -94,6 +94,8 @@ describe("isValidOrgSlug", () => { // otherwise read as an org's console URL (`//...`). for (const claimed of [ "git", + "apps", + "experiments", "oauth", "api", "app", @@ -107,7 +109,15 @@ describe("isValidOrgSlug", () => { expect(isValidOrgSlug(claimed), claimed).toBe(false); } // Look-alikes stay claimable. - for (const lookalike of ["gitlab", "git-team", "github", "apps", "oauth-co", "blogs"]) { + for (const lookalike of [ + "gitlab", + "git-team", + "github", + "app-team", + "experiment", + "oauth-co", + "blogs", + ]) { expect(isValidOrgSlug(lookalike), lookalike).toBe(true); } }); From 706f299e9a38e15c8858b526abe6e10026b718ae Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:42:49 -0700 Subject: [PATCH 8/9] Forward connected-account callbacks with v2's state prefix to v2 --- apps/cloud/src/edge/marketing.ts | 81 +++++++++++++++++++++++--------- apps/cloud/src/env-augment.d.ts | 3 ++ apps/cloud/src/server.ts | 5 +- apps/cloud/wrangler.jsonc | 4 ++ 4 files changed, 69 insertions(+), 24 deletions(-) diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index 62d2b77b98..31942b1cae 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -7,7 +7,9 @@ // - sign-up goes to v2 (a redirect to v2's sign-up page); // - a fixed list of v2 paths (sign-in issuer metadata, social sign-in // callbacks, Git smart HTTP and the agent skills index) is forwarded to -// v2's Worker over a service binding. +// v2's Worker over a service binding; +// - so is a connected-account OAuth callback whose `state` carries v2's +// prefix. // v1 owns everything not listed. This module deliberately has no TanStack // Start or cloud application imports: the Worker entry calls it before // loading the Start server graph. @@ -126,58 +128,93 @@ export interface V2Service { readonly fetch: (request: Request) => Promise; } +/** v1's connected-account OAuth callback. v2 shares it on `executor.sh`: + * a callback whose `state` starts with v2's prefix belongs to v2. */ +const OAUTH_CALLBACK_PATH = "/api/oauth/callback"; + +/** A state prefix is URL-safe as is (so the query value carries it + * unencoded) and includes a character outside base64url. v1's states are + * base64url, so no v1 state can start with such a prefix. */ +const OAUTH_STATE_PREFIX_PATTERN = /^[A-Za-z0-9._~-]*[.~][A-Za-z0-9._~-]*$/; + +/** The edge's raw v2 settings, as the Worker environment holds them. */ +export interface V2EdgeEnv { + /** Service binding to v2's Worker. */ + readonly V2?: V2Service; + /** Absolute URL of v2's sign-up page. */ + readonly V2_SIGN_UP_URL?: string; + /** The prefix v2 puts on every connected-account OAuth `state`. */ + readonly V2_OAUTH_STATE_PREFIX?: string; +} + /** What the edge needs to hand requests to v2. */ export interface V2Edge { /** v2's Worker. */ readonly service: V2Service; /** Absolute URL of v2's sign-up page (the `V2_SIGN_UP_URL` var). */ readonly signUpUrl: URL; + /** v2's connected-account OAuth state prefix (the `V2_OAUTH_STATE_PREFIX` var). */ + readonly oauthStatePrefix: string; } /** * Parse the edge's v2 settings from the Worker environment. Returns `null` - * when neither is set (local dev, test workers), so v1 serves everything, and - * the reason as a string when the deployment is broken: only one of them set, - * or a sign-up URL that is not absolute. + * when none is set (local dev, test workers), so v1 serves everything, and + * the reason as a string when the deployment is broken: only some of them + * set, a sign-up URL that is not absolute, or a state prefix that could + * match a v1 state. */ -export const parseV2Edge = ( - service: V2Service | undefined, - signUpUrl: string | undefined, -): V2Edge | string | null => { - if (service === undefined && signUpUrl === undefined) return null; - if (service === undefined || signUpUrl === undefined) { - return "The V2 binding and V2_SIGN_UP_URL must be set together"; +export const parseV2Edge = (env: V2EdgeEnv): V2Edge | string | null => { + const service = env.V2; + const signUpUrl = env.V2_SIGN_UP_URL; + const oauthStatePrefix = env.V2_OAUTH_STATE_PREFIX; + if (service === undefined && signUpUrl === undefined && oauthStatePrefix === undefined) { + return null; + } + if (service === undefined || signUpUrl === undefined || oauthStatePrefix === undefined) { + return "The V2 binding, V2_SIGN_UP_URL and V2_OAUTH_STATE_PREFIX must be set together"; } const parsed = URL.parse(signUpUrl); if (parsed === null || (parsed.protocol !== "https:" && parsed.protocol !== "http:")) { return "V2_SIGN_UP_URL must be an absolute http(s) URL"; } - return { service, signUpUrl: parsed }; + if (!OAUTH_STATE_PREFIX_PATTERN.test(oauthStatePrefix)) { + return "V2_OAUTH_STATE_PREFIX must be URL-safe and contain '.' or '~'"; + } + return { service, signUpUrl: parsed, oauthStatePrefix }; }; +/** Whether a connected-account callback carries v2's state prefix. Reads the + * query only: a callback without `state` in its query stays with v1. */ +const isV2OAuthCallback = (url: URL, prefix: string): boolean => + url.searchParams.get("state")?.startsWith(prefix) === true; + /** * Answer a production request that belongs to v2: redirect sign-up (`GET`, - * any query) to v2's sign-up page, or forward a {@link isV2Path} request to + * any query) to v2's sign-up page, or forward a {@link isV2Path} request, or + * a connected-account callback whose `state` starts with v2's prefix, to * v2's Worker and return its response unchanged (status, headers and body - * stream). Returns `null` when v1 owns the request. Broken settings answer - * the requests the edge owns with a 500 and leave the rest of v1 serving. + * stream). Returns `null` when v1 owns the request. + * + * Broken settings answer the requests the edge owns with a 500 and leave the + * rest of v1 serving. The callback is the exception: which callbacks are v2's + * depends on the settings, so v1 keeps every callback until they parse. */ -export const v2EdgeResponse = ( - request: Request, - service: V2Service | undefined, - signUpUrl: string | undefined, -): Promise | null => { +export const v2EdgeResponse = (request: Request, env: V2EdgeEnv): Promise | null => { const url = new URL(request.url); if (url.hostname !== PRODUCTION_HOST) return null; const signUp = isSignUpPath(url.pathname) && request.method === "GET"; - if (!signUp && !isV2Path(url.pathname)) return null; - const edge = parseV2Edge(service, signUpUrl); + const callback = url.pathname === OAUTH_CALLBACK_PATH; + if (!signUp && !callback && !isV2Path(url.pathname)) return null; + const edge = parseV2Edge(env); if (edge === null) return null; if (typeof edge === "string") { console.error(`executor.sh v2 edge misconfigured: ${edge}`); + if (callback) return null; return Promise.resolve(new Response("Service misconfigured", { status: 500 })); } if (signUp) return Promise.resolve(Response.redirect(edge.signUpUrl.href, 302)); + if (callback && !isV2OAuthCallback(url, edge.oauthStatePrefix)) return null; return edge.service.fetch(v2ForwardRequest(request)); }; diff --git a/apps/cloud/src/env-augment.d.ts b/apps/cloud/src/env-augment.d.ts index 4af210008e..e733d119fe 100644 --- a/apps/cloud/src/env-augment.d.ts +++ b/apps/cloud/src/env-augment.d.ts @@ -143,6 +143,9 @@ declare global { V2?: Fetcher; /** Absolute URL `/sign-up` and `/signup` redirect to. */ V2_SIGN_UP_URL?: string; + /** Prefix of v2's connected-account OAuth `state`; `/api/oauth/callback` + * requests whose state starts with it go to v2. */ + V2_OAUTH_STATE_PREFIX?: string; // Shared with frontend VITE_PUBLIC_SITE_URL?: string; diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index a9e98183fd..1a1a713c2d 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -318,8 +318,9 @@ const cloudflareHandler = { prewarmAppPlane(ctx); } - // Sign-up and the fixed list of v2 paths on `executor.sh` go to v2. - const v2 = v2EdgeResponse(request, env.V2, env.V2_SIGN_UP_URL); + // Sign-up, the fixed list of v2 paths and v2's connected-account + // callbacks on `executor.sh` go to v2. + const v2 = v2EdgeResponse(request, env); if (v2) return v2; const marketingRequest = marketingProxyRequest(request); diff --git a/apps/cloud/wrangler.jsonc b/apps/cloud/wrangler.jsonc index 44be69363a..9f7b22f258 100644 --- a/apps/cloud/wrangler.jsonc +++ b/apps/cloud/wrangler.jsonc @@ -142,6 +142,10 @@ "VITE_PUBLIC_SITE_URL": "https://executor.sh", // Where /sign-up and /signup redirect: v2's sign-up page. "V2_SIGN_UP_URL": "https://v2.executor.sh/login?mode=signup", + // v2 starts every connected-account OAuth `state` with this prefix, and + // /api/oauth/callback requests carrying it go to v2. It must equal the + // prefix v2 mints. + "V2_OAUTH_STATE_PREFIX": "x2.", // Keeps the /__sentry-otel-verify probe live in production: Sentry // delivery failed silently for weeks (zero events after ~Jul 30 with an // active DSN), and without this there is no way to test the pipeline From e47a9f18b83a1a870075c02027e3eed02e339bc5 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:44:31 -0700 Subject: [PATCH 9/9] Test v2's connected-account callbacks and the shipped v2 edge settings --- apps/cloud/src/edge/marketing.test.ts | 234 +++++++++++++++--- apps/cloud/src/edge/production-config.test.ts | 53 ++++ 2 files changed, 250 insertions(+), 37 deletions(-) create mode 100644 apps/cloud/src/edge/production-config.test.ts diff --git a/apps/cloud/src/edge/marketing.test.ts b/apps/cloud/src/edge/marketing.test.ts index d72ac1a06c..66645162dd 100644 --- a/apps/cloud/src/edge/marketing.test.ts +++ b/apps/cloud/src/edge/marketing.test.ts @@ -7,6 +7,7 @@ import { marketingProxyRequest, parseV2Edge, v2EdgeResponse, + type V2EdgeEnv, type V2Service, } from "./marketing"; @@ -151,8 +152,9 @@ describe("isV2Path", () => { "/github", "/.well-known/agent-skills", "/.well-known/agent-skillset/index.json", - // Not yet: connected-account callback and marketing. + // The connected-account callback goes by its state, not its path. "/api/oauth/callback", + // Not yet: marketing. "/pricing", // v1 dashboard, org pages and MCP, including org slugs that look similar. "/", @@ -184,32 +186,59 @@ describe("isSignUpPath", () => { describe("parseV2Edge", () => { const service: V2Service = { fetch: () => Promise.resolve(new Response(null)) }; + const settings = { + V2: service, + V2_SIGN_UP_URL: "https://app.executor.sh/login?mode=signup", + V2_OAUTH_STATE_PREFIX: "x2.", + }; + + it("is off when no setting is present", () => { + expect(parseV2Edge({})).toBeNull(); + }); - it("is off when neither setting is present", () => { - expect(parseV2Edge(undefined, undefined)).toBeNull(); + it("refuses any setting set without the others", () => { + expect(typeof parseV2Edge({ ...settings, V2: undefined })).toBe("string"); + expect(typeof parseV2Edge({ ...settings, V2_SIGN_UP_URL: undefined })).toBe("string"); + expect(typeof parseV2Edge({ ...settings, V2_OAUTH_STATE_PREFIX: undefined })).toBe("string"); + expect(typeof parseV2Edge({ V2_OAUTH_STATE_PREFIX: "x2." })).toBe("string"); }); - it("refuses a binding or sign-up URL set without the other", () => { - expect(typeof parseV2Edge(undefined, "https://v2.executor.sh/login?mode=signup")).toBe( + it("refuses a sign-up URL that is not absolute http(s)", () => { + expect(typeof parseV2Edge({ ...settings, V2_SIGN_UP_URL: "/login?mode=signup" })).toBe( + "string", + ); + expect(typeof parseV2Edge({ ...settings, V2_SIGN_UP_URL: "javascript:alert(1)" })).toBe( "string", ); - expect(typeof parseV2Edge(service, undefined)).toBe("string"); }); - it("refuses a sign-up URL that is not absolute http(s)", () => { - expect(typeof parseV2Edge(service, "/login?mode=signup")).toBe("string"); - expect(typeof parseV2Edge(service, "javascript:alert(1)")).toBe("string"); + // v1's states are base64url (raw, or the org-wrapped JSON encoding), so a + // prefix made only of base64url characters could claim a v1 callback. + it("refuses a state prefix that a v1 state could start with", () => { + for (const prefix of ["", "x2", "x2-", "x2_", "eyJ", "x2 .", "x2.%", "x2./"]) { + expect(typeof parseV2Edge({ ...settings, V2_OAUTH_STATE_PREFIX: prefix })).toBe("string"); + } }); - it("parses both settings", () => { - const edge = parseV2Edge(service, "https://v2.executor.sh/login?mode=signup"); + it("parses all settings", () => { + const edge = parseV2Edge(settings); if (edge === null || typeof edge === "string") return expect.unreachable("settings must parse"); - expect(edge.signUpUrl.href).toBe("https://v2.executor.sh/login?mode=signup"); + expect(edge.signUpUrl.href).toBe("https://app.executor.sh/login?mode=signup"); + expect(edge.oauthStatePrefix).toBe("x2."); + expect(typeof parseV2Edge({ ...settings, V2_OAUTH_STATE_PREFIX: "v2~" })).toBe("object"); }); }); describe("v2EdgeResponse", () => { const SIGN_UP_URL = "https://v2.executor.sh/login?mode=signup"; + const STATE_PREFIX = "x2."; + + /** The edge's settings with `service` as v2's Worker. */ + const settings = (service: V2Service, signUpUrl = SIGN_UP_URL): V2EdgeEnv => ({ + V2: service, + V2_SIGN_UP_URL: signUpUrl, + V2_OAUTH_STATE_PREFIX: STATE_PREFIX, + }); /** A v2 service that records what it receives and answers with `respond`. */ const recordingService = (respond: (request: Request) => Promise | Response) => { @@ -227,7 +256,7 @@ describe("v2EdgeResponse", () => { const { received, service } = recordingService(() => new Response(null)); for (const url of ["https://executor.sh/sign-up", "https://executor.sh/signup?ref=docs"]) { - const response = await v2EdgeResponse(new Request(url), service, SIGN_UP_URL); + const response = await v2EdgeResponse(new Request(url), settings(service)); expect(response?.status).toBe(302); expect(response?.headers.get("location")).toBe(SIGN_UP_URL); } @@ -237,8 +266,10 @@ describe("v2EdgeResponse", () => { it("follows the configured sign-up URL", async () => { const response = await v2EdgeResponse( new Request("https://executor.sh/signup"), - { fetch: () => Promise.resolve(new Response(null)) }, - "https://app.executor.sh/sign-up", + settings( + { fetch: () => Promise.resolve(new Response(null)) }, + "https://app.executor.sh/sign-up", + ), ); expect(response?.headers.get("location")).toBe("https://app.executor.sh/sign-up"); }); @@ -248,8 +279,7 @@ describe("v2EdgeResponse", () => { expect( v2EdgeResponse( new Request("https://executor.sh/sign-up", { method: "POST" }), - service, - SIGN_UP_URL, + settings(service), ), ).toBeNull(); }); @@ -258,32 +288,28 @@ describe("v2EdgeResponse", () => { const { received, service } = recordingService(() => new Response(null)); const response = await v2EdgeResponse( new Request("https://executor.sh/sign-up"), - service, - "/relative", + settings(service, "/relative"), ); expect(response?.status).toBe(500); expect( - v2EdgeResponse(new Request("https://executor.sh/acme/mcp"), service, "/relative"), + v2EdgeResponse(new Request("https://executor.sh/acme/mcp"), settings(service, "/relative")), ).toBeNull(); expect(received).toHaveLength(0); }); it("is off without settings", () => { - expect( - v2EdgeResponse(new Request("https://executor.sh/sign-up"), undefined, undefined), - ).toBeNull(); + expect(v2EdgeResponse(new Request("https://executor.sh/sign-up"), {})).toBeNull(); }); it("only acts on executor.sh", () => { const { service } = recordingService(() => new Response(null)); expect( - v2EdgeResponse(new Request("http://executor-cloud.localhost/sign-up"), service, SIGN_UP_URL), + v2EdgeResponse(new Request("http://executor-cloud.localhost/sign-up"), settings(service)), ).toBeNull(); expect( v2EdgeResponse( new Request("https://v2.executor.sh/api/auth/callback/google"), - service, - SIGN_UP_URL, + settings(service), ), ).toBeNull(); }); @@ -293,12 +319,11 @@ describe("v2EdgeResponse", () => { expect( v2EdgeResponse( new Request("https://executor.sh/api/auth/callback?code=c"), - service, - SIGN_UP_URL, + settings(service), ), ).toBeNull(); expect( - v2EdgeResponse(new Request("https://executor.sh/gitlab/x/info/refs"), service, SIGN_UP_URL), + v2EdgeResponse(new Request("https://executor.sh/gitlab/x/info/refs"), settings(service)), ).toBeNull(); expect(received).toHaveLength(0); }); @@ -314,8 +339,7 @@ describe("v2EdgeResponse", () => { const response = await v2EdgeResponse( new Request("https://executor.sh/api/auth/callback/google?code=c&state=s"), - service, - SIGN_UP_URL, + settings(service), ); expect(response?.status).toBe(302); @@ -340,8 +364,7 @@ describe("v2EdgeResponse", () => { "x-forwarded-proto": "http", }, }), - service, - SIGN_UP_URL, + settings(service), ); const forwarded = received[0]; @@ -362,8 +385,7 @@ describe("v2EdgeResponse", () => { const response = await v2EdgeResponse( new Request("https://executor.sh/git/acme/tools/info/refs"), - service, - SIGN_UP_URL, + settings(service), ); expect(response).toBe(upstream); @@ -402,8 +424,7 @@ describe("v2EdgeResponse", () => { // @ts-expect-error -- Node's fetch needs `duplex` for a stream body; workerd does not. duplex: "half", }), - service, - SIGN_UP_URL, + settings(service), ); expect(received[0]?.method).toBe("POST"); @@ -411,3 +432,142 @@ describe("v2EdgeResponse", () => { expect(await response?.text()).toBe("first-pack-chunk|second-pack-chunk|true"); }); }); + +// v1 and v2 share `/api/oauth/callback` on executor.sh. v2 starts its state +// with a fixed prefix; the edge reads only the query's `state` to decide. +describe("v2EdgeResponse connected-account callback", () => { + const settings = (service: V2Service): V2EdgeEnv => ({ + V2: service, + V2_SIGN_UP_URL: "https://app.executor.sh/login?mode=signup", + V2_OAUTH_STATE_PREFIX: "x2.", + }); + + const recordingService = () => { + const received: Request[] = []; + const service: V2Service = { + fetch: async (request) => { + received.push(request); + return new Response(null, { + status: 302, + headers: { location: `https://app.executor.sh${new URL(request.url).search}` }, + }); + }, + }; + return { received, service }; + }; + + it("forwards a callback whose state carries v2's prefix, query unchanged", async () => { + const { received, service } = recordingService(); + const callback = "https://executor.sh/api/oauth/callback?code=c&state=x2.abc123"; + + const response = await v2EdgeResponse( + new Request(callback, { headers: { cookie: "wos-session=sealed" } }), + settings(service), + ); + + expect(response?.status).toBe(302); + expect(received).toHaveLength(1); + expect(received[0]?.url).toBe(callback); + expect(received[0]?.redirect).toBe("manual"); + expect(received[0]?.headers.has("cookie")).toBe(false); + }); + + it("reads a percent-encoded prefix as the prefix", async () => { + const { received, service } = recordingService(); + + await v2EdgeResponse( + new Request("https://executor.sh/api/oauth/callback?state=x2%2Eabc&code=c"), + settings(service), + ); + + expect(received).toHaveLength(1); + }); + + it("forwards v2's provider errors, which carry the state but no code", async () => { + const { received, service } = recordingService(); + + await v2EdgeResponse( + new Request("https://executor.sh/api/oauth/callback?error=access_denied&state=x2.abc"), + settings(service), + ); + + expect(received).toHaveLength(1); + }); + + const v1Callbacks = [ + // v1's raw state and its org-wrapped base64url JSON state. + "https://executor.sh/api/oauth/callback?code=c&state=Q2xpZW50U3RhdGUxMjM0NTY3ODkw", + "https://executor.sh/api/oauth/callback?code=c&state=eyJzdGF0ZSI6InMiLCJvcmdTbHVnIjoiYWNtZSJ9", + // No state, or an empty one. + "https://executor.sh/api/oauth/callback?code=c", + "https://executor.sh/api/oauth/callback?code=c&state=", + "https://executor.sh/api/oauth/callback", + // Lookalikes: the prefix without its dot, another case, inside the + // value, or in another parameter. + "https://executor.sh/api/oauth/callback?code=c&state=x2abc", + "https://executor.sh/api/oauth/callback?code=c&state=x2-abc", + "https://executor.sh/api/oauth/callback?code=c&state=X2.abc", + "https://executor.sh/api/oauth/callback?code=c&state=ax2.abc", + "https://executor.sh/api/oauth/callback?code=c&state=%20x2.abc", + "https://executor.sh/api/oauth/callback?code=x2.abc&state=v1state", + "https://executor.sh/api/oauth/callback?code=c&xstate=x2.abc", + // Only the first state counts. + "https://executor.sh/api/oauth/callback?state=v1state&state=x2.abc", + // Other paths with a v2 state. + "https://executor.sh/api/oauth/callback/?state=x2.abc", + "https://executor.sh/api/oauth/callbacks?state=x2.abc", + "https://executor.sh/api/oauth/callback/extra?state=x2.abc", + "https://executor.sh/acme/api/oauth/callback?state=x2.abc", + ]; + for (const url of v1Callbacks) { + it(`leaves ${new URL(url).pathname}${new URL(url).search} with v1`, () => { + const { received, service } = recordingService(); + expect(v2EdgeResponse(new Request(url), settings(service))).toBeNull(); + expect(received).toHaveLength(0); + }); + } + + it("decides from the query without reading a posted body", () => { + const { received, service } = recordingService(); + const body = new ReadableStream({ + pull: () => expect.unreachable("the edge must not read the body"), + }); + + expect( + v2EdgeResponse( + new Request("https://executor.sh/api/oauth/callback", { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + body, + // @ts-expect-error -- Node's fetch needs `duplex` for a stream body; workerd does not. + duplex: "half", + }), + settings(service), + ), + ).toBeNull(); + expect(received).toHaveLength(0); + }); + + it("only acts on executor.sh", () => { + const { received, service } = recordingService(); + expect( + v2EdgeResponse( + new Request("https://v2.executor.sh/api/oauth/callback?state=x2.abc"), + settings(service), + ), + ).toBeNull(); + expect(received).toHaveLength(0); + }); + + it("leaves every callback with v1 when the settings are absent or broken", () => { + const { received, service } = recordingService(); + const request = () => new Request("https://executor.sh/api/oauth/callback?state=x2.abc"); + + expect(v2EdgeResponse(request(), {})).toBeNull(); + expect(v2EdgeResponse(request(), { ...settings(service), V2_SIGN_UP_URL: "/x" })).toBeNull(); + expect( + v2EdgeResponse(request(), { ...settings(service), V2_OAUTH_STATE_PREFIX: "x2" }), + ).toBeNull(); + expect(received).toHaveLength(0); + }); +}); diff --git a/apps/cloud/src/edge/production-config.test.ts b/apps/cloud/src/edge/production-config.test.ts new file mode 100644 index 0000000000..40010143f7 --- /dev/null +++ b/apps/cloud/src/edge/production-config.test.ts @@ -0,0 +1,53 @@ +import { fileURLToPath } from "node:url"; + +import { describe, expect, it } from "@effect/vitest"; +import { Schema } from "effect"; +import { unstable_readConfig } from "wrangler"; + +import { parseV2Edge, v2EdgeResponse, type V2EdgeEnv, type V2Service } from "./marketing"; + +// The deployed edge reads its v2 settings from wrangler.jsonc. These tests read +// that file the way wrangler does and run the edge with the values it ships. +// wrangler's config arrives untyped here, so it is decoded first. +const WranglerConfig = Schema.Struct({ + vars: Schema.Record(Schema.String, Schema.Unknown), + services: Schema.Array(Schema.Struct({ binding: Schema.String, service: Schema.String })), +}); +const config = Schema.decodeUnknownSync(WranglerConfig)( + unstable_readConfig({ config: fileURLToPath(new URL("../../wrangler.jsonc", import.meta.url)) }), +); + +const stringVar = (name: string): string | undefined => { + const value = config.vars[name]; + return typeof value === "string" ? value : undefined; +}; + +const standIn: V2Service = { fetch: () => Promise.resolve(new Response("v2")) }; + +/** The shipped settings, with a stand-in for v2's Worker. */ +const shipped: V2EdgeEnv = { + V2: standIn, + V2_SIGN_UP_URL: stringVar("V2_SIGN_UP_URL"), + V2_OAUTH_STATE_PREFIX: stringVar("V2_OAUTH_STATE_PREFIX"), +}; + +describe("production v2 edge settings", () => { + it("binds V2 to exactly one Worker", () => { + const bindings = config.services.filter((service) => service.binding === "V2"); + expect(bindings).toHaveLength(1); + expect(bindings[0]?.service).toMatch(/^[a-z0-9-]+$/); + }); + + it("redirects sign-up to v2's sign-up page on v2.executor.sh", async () => { + const response = await v2EdgeResponse(new Request("https://executor.sh/sign-up"), shipped); + + expect(response?.status).toBe(302); + expect(response?.headers.get("location")).toBe("https://v2.executor.sh/login?mode=signup"); + }); + + it("ships v2's connected-account state prefix", () => { + const edge = parseV2Edge(shipped); + if (edge === null || typeof edge === "string") return expect.unreachable("settings must parse"); + expect(edge.oauthStatePrefix).toBe("x2."); + }); +});