import handler from "@tanstack/react-start/server-entry"; import { sweepOrphanedLfsBlobs } from "./server/lfs/objects"; import { sweepStuckProvisioning } from "./server/provisioning-sweep"; import { sweepOrphanedArtifacts } from "./server/git/artifacts-sweep"; import { pruneIdleActionArchives } from "./server/ci/actions/archive"; import { sweepImageBlobs } from "./server/ci/actions/images"; import { handleGitRequest, matchGitRoute } from "./server/git/proxy"; import { handleJobApi, handleRunnerApi } from "./server/ci/runner-api"; import { handleQueueBatch } from "./server/events/consumer"; import type { QueueMessage } from "./server/events/emit"; import { HttpError } from "./server/errors"; import { finishGithubLogin, startGithubLogin } from "./server/github-oauth"; import { handleRawRoutes } from "./server/raw"; import { handleLfsRequest, matchLfsRoute } from "./server/lfs/api"; import { handleSitesRequest } from "./server/sites/dispatch"; import { handleSitesForgeRoutes } from "./server/sites/forge"; import { backfillScratchExpiry, sweepScratchSites } from "./server/sites/scratch"; import { backfillInviters } from "./server/invitation-index"; import { backfillSiteDirectory } from "./server/sites/directory"; import { backfillUsageLeaderboard } from "./server/metering/leaderboard"; import { backfillCodeIndexSchema } from "./server/code/search-index"; import { backfillDomainSites, backfillOwnedSites } from "./server/sites/store"; import { reconcileSites } from "./server/sites/sync"; import { sweepCustomDomains } from "./server/sites/domain-sweep"; import { checkGlobalCeiling } from "./server/sites/caps"; import { collectHostingObjects } from "./server/sites/gc"; import { reconcileDomainAddonQuantities } from "./server/billing/domain-addon"; import { handleSecurityApi } from "./server/security/api"; import { handleSecurityBatch, SECURITY_QUEUE } from "./server/security/queue"; import { handleAttachmentRoutes } from "./server/attachments"; import { pruneHourlyLimits } from "./server/limits"; import { handleCamoRoutes } from "./server/camo"; import { handleUserContentOrigin, redirectToUserContent } from "./server/usercontent"; import { userContentOriginFor } from "./server/usercontent-origin"; import { handleProjectRoutes } from "./server/projects"; import { handleAvatarRoutes } from "./server/avatars"; import { handleVideoRoutes } from "./server/videos"; import { handleOgRoutes } from "./server/og/routes"; import { hasSessionCookie } from "./server/auth"; import { sweepUnverifiedAccounts } from "./server/accounts"; import { advanceWaitlist } from "./server/waitlist"; import { sweepStaleOAuthClients } from "./server/mcp/oauth-clients"; import { handleActionsRoutes } from "./server/ci/http"; import { runActionsCron } from "./server/ci/maintenance"; import { API_V1_PREFIX, handleApiV1 } from "./server/api/router"; import { handleEditorRoutes } from "./server/editor/api"; import { handleEditorOrigin } from "./server/editor/host"; import { handleIntegrationRoutes, INBOUND_HOOK_PREFIX } from "./server/integrations/http"; import { handleIntegrationBatch, INTEGRATIONS_DLQ, INTEGRATIONS_QUEUE, runIntegrationsCron, } from "./server/integrations/queue"; import { runMcpCron } from "./server/mcp/cron"; import type { IntegrationMessage } from "./server/integrations/queue"; import { handleMcpRoutes } from "./server/mcp/routes"; import { DEV_MAIL_MESSAGES_PATH, handleDevMailRoutes } from "./server/notify/dev-mail"; import { DEV_SCOPED_SQL_PATH, handleDevScopedSql } from "./server/dev-scoped-sql"; import { handleUnsubscribeRoute, UNSUBSCRIBE_PATH } from "./server/notify/unsubscribe"; import { routeInboundEmail } from "./server/inbound-email"; import { analyticsConfig, captureServerEvent, captureServerException } from "./server/analytics"; import { handleIngestProxy } from "./server/analytics-proxy"; import { handleGoGet } from "./server/go-get"; import { paymentErrorFor } from "./server/metering/access"; import { QuotaExceededError } from "./server/metering/quota"; import { handleStripeWebhook, STRIPE_WEBHOOK_PATH } from "./server/billing/webhook"; import { handleStripeDevRoutes, STRIPE_DEV_REPLAY_PATH } from "./server/billing/dev"; import { resealLegacyEndpoints } from "./server/ai/endpoint"; import { backfillDeployTokenIndex } from "./server/deploy-token-index"; import { armExistingAccounts, armExistingRepos } from "./server/account-state/arm-backfill"; import { backfillActivityIndex } from "./server/activity-index"; import { backfillAttachmentBytes } from "./server/attachment-bytes"; import { backfillScopeGauges } from "./server/metering/scope-gauges"; import { handleSecurityTxt } from "./server/legal"; import { bootProblems, isForgeRequestHost, misconfiguredResponse, requestProblems } from "./server/deployment"; import { pageCsp, withSecurityHeaders } from "./server/security-headers"; import { configurePerf, setPerfReporter, withJobPerf, withPerf } from "./server/perf"; import { routeTemplate } from "./lib/route-template"; export { Runner } from "./server/ci/runner-do"; export { JobApiGateway } from "./server/ci/job-api-gateway"; export { RepoProvisionWorkflow } from "./server/repo-provisioning"; export { GitTaskRunner, RunnerDind, RunnerLarge, RunnerLargeDind, RunnerXLarge, RunnerXLargeDind, } from "./server/ci/runner-variants"; export { CiRunWorkflow } from "./server/ci/run-workflow"; export { PushWorkflow } from "./server/events/push-workflow"; export { SitesDeployWorkflow } from "./server/sites/workflow"; export { SiteAssets, SiteOutbound, SiteTail } from "./server/sites/runtime"; export { MergeFixWorkflow } from "./server/mergefix/workflow"; export { ProjectRoom } from "./server/project-room"; export { EditorRoom } from "./server/editor/room"; export { EditorContainer } from "./server/editor/container-do"; export { MergeQueue } from "./server/merge-queue-do"; export { RepoInsights } from "./server/insights/repo-insights-do"; export { RepoSearch } from "./server/code/repo-search-do"; export { RepoState } from "./server/repo-state/repo-state-do"; export { AccountState } from "./server/account-state/account-state-do"; export { Counters } from "./server/counters-do"; export { RateLimiter } from "./server/rate-limiter-do"; export { UserInbox } from "./server/user-inbox-do"; export { WebhookLog } from "./server/webhook-log-do"; export { MeterBuffer } from "./server/metering/meter-buffer-do"; export { JobDoorbell } from "./server/ci/job-doorbell-do"; export { McpHub } from "./server/mcp/hub-do"; export { RepoMirror } from "./server/mirror/mirror-do"; setPerfReporter((event, properties) => captureServerEvent(event, null, properties)); const RUNNER_API_PREFIX = "/api/runner/"; const HTTP_INTERNAL = 500; /** Queue retries while misconfigured still count toward max_retries, so wait long between them. */ const MISCONFIGURED_RETRY_SECONDS = 15 * 60; const JOB_API_PREFIX = "/api/"; /** Origins other than the forge (editor, user content, sites) answer with their own header policies. */ async function routeHostedOrigin(request: Request, ctx: ExecutionContext): Promise { return ( (await handleEditorOrigin(request, new URL(request.url))) ?? (await handleUserContentOrigin(request, new URL(request.url))) ?? (await handleSitesRequest(request, ctx)) ); } async function route(request: Request, ctx: ExecutionContext): Promise { const url = new URL(request.url); const securityTxt = handleSecurityTxt(request, url); if (securityTxt) return securityTxt; const mcp = await handleMcpRoutes(request, url); if (mcp) return mcp; const ingest = await handleIngestProxy(request, url); if (ingest) return ingest; const goGet = handleGoGet(request, url); if (goGet) return goGet; const og = await handleOgRoutes(request, url, ctx); if (og) return og; const video = await handleVideoRoutes(request, url); if (video) return video; if (!isTokenOnlyPath(url) && !isSameOriginRequest(request)) { return errorResponse(request, 403, "Cross-origin request blocked"); } const gitRoute = matchGitRoute(url); if (gitRoute) return handleGitRequest(request, gitRoute, ctx); const lfsRoute = matchLfsRoute(url); if (lfsRoute) return handleLfsRequest(request, lfsRoute); if (url.pathname.startsWith(API_V1_PREFIX)) return handleApiV1(request, url); if (url.pathname === STRIPE_WEBHOOK_PATH) return handleStripeWebhook(request); const stripeDev = await handleStripeDevRoutes(request, url); if (stripeDev) return stripeDev; if (url.pathname.startsWith(RUNNER_API_PREFIX)) { return handleRunnerApi(request, url.pathname.slice(RUNNER_API_PREFIX.length)); } if (url.pathname.startsWith("/api/jobs/") || url.pathname.startsWith("/api/artifacts/")) { return handleJobApi(request, url.pathname.slice(JOB_API_PREFIX.length)); } const securityApi = await handleSecurityApi(request, url); if (securityApi) return securityApi; const actions = await handleActionsRoutes(request, url); if (actions) return actions; const integrations = await handleIntegrationRoutes(request, url); if (integrations) return integrations; const sites = await handleSitesForgeRoutes(request, url); if (sites) return sites; const notifyRoute = (await handleUnsubscribeRoute(request, url)) ?? (await handleDevMailRoutes(request, url)); if (notifyRoute) return notifyRoute; const devSql = await handleDevScopedSql(request, url); if (devSql) return devSql; if (url.pathname === "/auth/github") return startGithubLogin(request); if (url.pathname === "/auth/github/callback") return finishGithubLogin(request); const hop = await redirectToUserContent(request, url); if (hop) return hop; return ( (await handleEditorRoutes(request, url)) ?? (await handleAvatarRoutes(request, url)) ?? (await handleAttachmentRoutes(request, url)) ?? (await handleCamoRoutes(request, url)) ?? (await handleProjectRoutes(request, url)) ?? handleRawRoutes(request, url) ); } const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]); const TOKEN_ONLY_PREFIXES = [ RUNNER_API_PREFIX, "/api/jobs/", "/api/artifacts/", "/api/actions/", INBOUND_HOOK_PREFIX, API_V1_PREFIX, "/api/v1/scratch", UNSUBSCRIBE_PATH, DEV_MAIL_MESSAGES_PATH, DEV_SCOPED_SQL_PATH, STRIPE_WEBHOOK_PATH, STRIPE_DEV_REPLAY_PATH, ]; const SARIF_API_PATH = /^\/api\/repos\/[^/]+\/[^/]+\/code-scanning\/sarifs(\/|$)/; const BEARER_SCHEMES = new Set(["bearer", "token"]); /** Git, LFS, runner/job/actions and SARIF APIs never read the session cookie, so cross-site requests carry no ambient authority. */ function isTokenOnlyPath(url: URL): boolean { return ( matchGitRoute(url) !== null || matchLfsRoute(url) !== null || SARIF_API_PATH.test(url.pathname) || TOKEN_ONLY_PREFIXES.some((prefix) => url.pathname.startsWith(prefix)) ); } /** * State-changing requests must come from the forge's own pages. A bearer token can't be attached by a * cross-site form, and a cross-site fetch carrying one is preflighted, so token API clients pass. Basic is * only exempt without a session cookie, since browsers replay cached Basic credentials on their own. */ function isSameOriginRequest(request: Request): boolean { if (SAFE_METHODS.has(request.method)) return true; const scheme = (request.headers.get("authorization") ?? "").split(" ", 1)[0].toLowerCase(); if (BEARER_SCHEMES.has(scheme)) return true; if (scheme === "basic" && !hasSessionCookie(request)) return true; const fetchSite = request.headers.get("sec-fetch-site"); if (fetchSite) return fetchSite === "same-origin"; const origin = request.headers.get("origin"); return origin !== null && origin === new URL(request.url).origin; } /** API clients get JSON; browsers following a download or raw link get readable text. */ function errorResponse(request: Request, status: number, message: string, detail: object | null = null): Response { const path = new URL(request.url).pathname; const wantsJson = path.startsWith("/api/") || (request.headers.get("accept") ?? "").includes("application/json"); if (wantsJson) return Response.json({ error: message, ...detail }, { status }); return new Response(`${message}\n`, { status, headers: { "content-type": "text/plain; charset=utf-8" }, }); } /** * Rendered markdown routes every external image through /camo, so pages never load third-party pixels. App * pages are never framed and never post or set a base URL off-origin (DEPLOY-P7). */ function withPageCsp(response: Response, url: URL, signedIn = false): Response { const type = response.headers.get("content-type") ?? ""; if (!type.startsWith("text/html") || response.headers.has("content-security-policy")) return response; const headers = new Headers(response.headers); headers.set("content-security-policy", pageCsp(userContentOriginFor(url), analyticsConfig()?.host)); // Signed-in pages carry per-viewer data; never let a back/forward navigation replay a stale cached copy. if (signedIn && !headers.has("cache-control")) headers.set("cache-control", "private, no-store"); return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); } /** * Handlers that reject a request early (auth, 404) may never read its body. Leaving it unread can poison a * reused keep-alive connection for the next client, so drain it once the response is decided. */ function discardUnreadBody(request: Request, ctx: ExecutionContext): void { if (!request.body || request.bodyUsed || request.body.locked) return; ctx.waitUntil(drain(request.body).catch(() => undefined)); } const MAX_DRAINED_BYTES = 8 * 1024 * 1024; /** Reads (and discards) a small unread body so the connection stays reusable; larger ones are cancelled. */ async function drain(body: ReadableStream): Promise { const reader = body.getReader(); let read = 0; while (read <= MAX_DRAINED_BYTES) { const { done, value } = await reader.read(); if (done) return; read += value.byteLength; } await reader.cancel(); } /** Which part of the forge served a request, for metrics. */ function surfaceOf(request: Request, url: URL): string { const path = url.pathname; if (!isForgeRequestHost(request)) return "hosted"; if (matchGitRoute(url)) return "git"; if (matchLfsRoute(url)) return "lfs"; if (path.startsWith(API_V1_PREFIX)) return "api"; if (path === "/mcp" || path.startsWith("/mcp/")) return "mcp"; if (path.startsWith("/api/")) return "runner"; if (path.startsWith("/_serverFn/")) return "fn"; return "page"; } async function handleFetch(request: Request, ctx: ExecutionContext): Promise { const problems = [...bootProblems(), ...requestProblems(request)]; if (problems.length > 0) return misconfiguredResponse(problems); let hostedOrigin = true; try { const hosted = await routeHostedOrigin(request, ctx); if (hosted) { discardUnreadBody(request, ctx); return hosted; } hostedOrigin = false; if (!isForgeRequestHost(request)) { discardUnreadBody(request, ctx); return new Response("Not found\n", { status: 404, headers: { "content-type": "text/plain; charset=utf-8" } }); } const response = await route(request, ctx); if (response) { discardUnreadBody(request, ctx); return withSecurityHeaders(response, request); } } catch (thrown) { discardUnreadBody(request, ctx); const error = thrown instanceof QuotaExceededError ? thrown.toHttpError() : thrown; const status = error instanceof HttpError ? error.status : HTTP_INTERNAL; const deliberate = error instanceof HttpError && status !== HTTP_INTERNAL; const message = deliberate ? error.message : "Internal error"; if (!(error instanceof HttpError)) { console.error("Unhandled API error", error); captureServerException(error, "worker.fetch"); } // No session here (token APIs): a 402 says what ran out, not whose account or where to pay. const detail = deliberate ? (await paymentErrorFor(error, null)).detail : null; const failure = errorResponse(request, status, message, detail); return hostedOrigin ? failure : withSecurityHeaders(failure, request); } const page = withPageCsp(await handler.fetch(request), new URL(request.url), hasSessionCookie(request)); return withSecurityHeaders(page, request); } export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { configurePerf((env as { PERF_SAMPLE_RATE?: string }).PERF_SAMPLE_RATE); const url = new URL(request.url); const surface = surfaceOf(request, url); return withPerf( (response) => ({ event: "server_request", properties: { // Sites and other hosted origins serve arbitrary paths; their shape says nothing about the forge. route: surface === "hosted" ? "hosted" : routeTemplate(url.pathname), method: request.method, status: response?.status ?? 500, surface, }, failed: (response?.status ?? HTTP_INTERNAL) >= HTTP_INTERNAL, }), () => handleFetch(request, ctx), ); }, async queue(batch: MessageBatch, env: Env): Promise { configurePerf((env as { PERF_SAMPLE_RATE?: string }).PERF_SAMPLE_RATE); if (bootProblems().length > 0) { batch.retryAll({ delaySeconds: MISCONFIGURED_RETRY_SECONDS }); return; } try { await withJobPerf(`queue.${batch.queue}`, async () => { if (batch.queue === SECURITY_QUEUE) return await handleSecurityBatch(batch); if (batch.queue === INTEGRATIONS_QUEUE || batch.queue === INTEGRATIONS_DLQ) { return await handleIntegrationBatch(batch as unknown as MessageBatch); } await handleQueueBatch(batch); }); } catch (error) { captureServerException(error, "worker.queue"); throw error; } }, async email(message: ForwardableEmailMessage): Promise { if (bootProblems().length > 0) { message.setReject("Temporarily unavailable"); return; } await routeInboundEmail(message); }, async scheduled(_controller: ScheduledController, env: Env, ctx: ExecutionContext): Promise { if (bootProblems().length > 0) return; configurePerf((env as { PERF_SAMPLE_RATE?: string }).PERF_SAMPLE_RATE); ctx.waitUntil(withJobPerf("cron.sweepUnverifiedAccounts", sweepUnverifiedAccounts)); ctx.waitUntil(withJobPerf("cron.advanceWaitlist", advanceWaitlist)); ctx.waitUntil(withJobPerf("cron.sweepStaleOAuthClients", sweepStaleOAuthClients)); ctx.waitUntil(withJobPerf("cron.runActionsCron", runActionsCron)); ctx.waitUntil(withJobPerf("cron.sweepOrphanedLfsBlobs", sweepOrphanedLfsBlobs)); ctx.waitUntil(withJobPerf("cron.sweepStuckProvisioning", sweepStuckProvisioning)); ctx.waitUntil(withJobPerf("cron.sweepOrphanedArtifacts", sweepOrphanedArtifacts)); ctx.waitUntil(withJobPerf("cron.pruneIdleActionArchives", pruneIdleActionArchives)); ctx.waitUntil(withJobPerf("cron.sweepImageBlobs", () => sweepImageBlobs())); ctx.waitUntil(withJobPerf("cron.runIntegrationsCron", runIntegrationsCron)); ctx.waitUntil(withJobPerf("cron.runMcpCron", runMcpCron)); ctx.waitUntil(withJobPerf("cron.sweepScratchSites", sweepScratchSites)); ctx.waitUntil(withJobPerf("cron.reconcileSites", reconcileSites)); ctx.waitUntil(withJobPerf("cron.sweepCustomDomains", sweepCustomDomains)); ctx.waitUntil(withJobPerf("cron.reconcileDomainAddonQuantities", reconcileDomainAddonQuantities)); ctx.waitUntil(withJobPerf("cron.checkGlobalCeiling", checkGlobalCeiling)); ctx.waitUntil(withJobPerf("cron.collectHostingObjects", collectHostingObjects)); ctx.waitUntil(withJobPerf("cron.pruneHourlyLimits", pruneHourlyLimits)); ctx.waitUntil(withJobPerf("cron.resealLegacyEndpoints", resealLegacyEndpoints)); ctx.waitUntil(withJobPerf("cron.backfillDeployTokenIndex", backfillDeployTokenIndex)); ctx.waitUntil(withJobPerf("cron.armExistingAccounts", armExistingAccounts)); ctx.waitUntil(withJobPerf("cron.armExistingRepos", armExistingRepos)); ctx.waitUntil(withJobPerf("cron.backfillAttachmentBytes", backfillAttachmentBytes)); ctx.waitUntil(withJobPerf("cron.backfillScopeGauges", backfillScopeGauges)); ctx.waitUntil(withJobPerf("cron.backfillActivityIndex", backfillActivityIndex)); ctx.waitUntil(withJobPerf("cron.backfillOwnedSites", backfillOwnedSites)); ctx.waitUntil(withJobPerf("cron.backfillDomainSites", backfillDomainSites)); ctx.waitUntil(withJobPerf("cron.backfillScratchExpiry", backfillScratchExpiry)); ctx.waitUntil(withJobPerf("cron.backfillInviters", backfillInviters)); ctx.waitUntil(withJobPerf("cron.backfillSiteDirectory", backfillSiteDirectory)); ctx.waitUntil(withJobPerf("cron.backfillUsageLeaderboard", backfillUsageLeaderboard)); ctx.waitUntil(withJobPerf("cron.backfillCodeIndexSchema", backfillCodeIndexSchema)); }, } satisfies ExportedHandler;