Require explicit callback/ingest secrets and derive runtime callback workspace strictly from task run state. Internalize credential resolver queries and default MCP auth to enforced unless explicitly disabled.
Unified · Split
| @@ -749,9 +749,7 @@ const parseEndpoint = async (request: any, response: any) => { · 748 unmodified lines | |||
| 749 | || process.env.NEXT_PUBLIC_CONVEX_URL?.trim() | 749 | || process.env.NEXT_PUBLIC_CONVEX_URL?.trim() |
| 750 | || ""; | 750 | || ""; |
| 751 | const ingestToken = | 751 | const ingestToken = |
| 752 | process.env.OPENAPI_INGEST_SERVICE_TOKEN?.trim() | 752 | process.env.OPENAPI_INGEST_SERVICE_TOKEN?.trim() || ""; |
| 753 | || process.env.OPENAPI_PARSE_API_TOKEN?.trim() | ||
| 754 | || ""; | ||
| 755 | 753 | ||
| 756 | const shouldIngest = | 754 | const shouldIngest = |
| 757 | workspaceId.length > 0 | 755 | workspaceId.length > 0 |
| @@ -759,6 +757,14 @@ const parseEndpoint = async (request: any, response: any) => { · 1 unmodified line | |||
| 759 | && sourceName.length > 0 | 757 | && sourceName.length > 0 |
| 760 | && convexUrl.length > 0; | 758 | && convexUrl.length > 0; |
| 761 | 759 | ||
| 760 | if (shouldIngest && ingestToken.length === 0) { | ||
| 761 | json(response, 503, { | ||
| 762 | ok: false, | ||
| 763 | error: "OPENAPI_INGEST_SERVICE_TOKEN is required for ingest mode", | ||
| 764 | }); | ||
| 765 | return; | ||
| 766 | } | ||
| 767 | |||
| 762 | if (shouldIngest) { | 768 | if (shouldIngest) { |
| 763 | const ingested = await toIngestPayload(openApiSpec, { | 769 | const ingested = await toIngestPayload(openApiSpec, { |
| 764 | convexUrl, | 770 | convexUrl, |
| @@ -423,10 +423,13 @@ export const ingestOpenApiManifest = action({ · 422 unmodified lines | |||
| 423 | }, | 423 | }, |
| 424 | handler: async (ctx, args) => { | 424 | handler: async (ctx, args) => { |
| 425 | const expectedToken = | 425 | const expectedToken = |
| 426 | process.env.OPENAPI_INGEST_SERVICE_TOKEN?.trim() | 426 | process.env.OPENAPI_INGEST_SERVICE_TOKEN?.trim() || null; |
| 427 | || process.env.OPENAPI_PARSE_API_TOKEN?.trim() | 427 | |
| 428 | || ""; | 428 | if (!expectedToken) { |
| 429 | if (expectedToken.length > 0 && args.token.trim() !== expectedToken) { | 429 | throw new Error("OpenAPI ingest token is not configured"); |
| 430 | } | ||
| 431 | |||
| 432 | if (args.token.trim() !== expectedToken) { | ||
| 430 | throw new Error("Unauthorized OpenAPI ingest request"); | 433 | throw new Error("Unauthorized OpenAPI ingest request"); |
| 431 | } | 434 | } |
| 432 | 435 | ||
| @@ -18,10 +18,12 @@ import { v } from "convex/values"; · 17 unmodified lines | |||
| 18 | import * as Effect from "effect/Effect"; | 18 | import * as Effect from "effect/Effect"; |
| 19 | import * as Schema from "effect/Schema"; | 19 | import * as Schema from "effect/Schema"; |
| 20 | 20 | ||
| 21 | import { api } from "./_generated/api"; | 21 | import { internal } from "./_generated/api"; |
| 22 | import { query, type ActionCtx } from "./_generated/server"; | 22 | import { internalQuery, type ActionCtx } from "./_generated/server"; |
| 23 | import { decryptSecretValue } from "./credential_crypto"; | 23 | import { decryptSecretValue } from "./credential_crypto"; |
| 24 | 24 | ||
| 25 | const runtimeInternal = internal as any; | ||
| 26 | |||
| 25 | const decodeSourceCredentialBinding = Schema.decodeUnknownSync( | 27 | const decodeSourceCredentialBinding = Schema.decodeUnknownSync( |
| 26 | SourceCredentialBindingSchema, | 28 | SourceCredentialBindingSchema, |
| 27 | ); | 29 | ); |
| @@ -34,7 +36,7 @@ const stripConvexSystemFields = ( · 6 unmodified lines | |||
| 34 | return rest; | 36 | return rest; |
| 35 | }; | 37 | }; |
| 36 | 38 | ||
| 37 | export const listCredentialBindingsForSource = query({ | 39 | export const listCredentialBindingsForSource = internalQuery({ |
| 38 | args: { | 40 | args: { |
| 39 | workspaceId: v.string(), | 41 | workspaceId: v.string(), |
| 40 | sourceKey: v.string(), | 42 | sourceKey: v.string(), |
| @@ -69,7 +71,7 @@ export const listCredentialBindingsForSource = query({ · 28 unmodified lines | |||
| 69 | }, | 71 | }, |
| 70 | }); | 72 | }); |
| 71 | 73 | ||
| 72 | export const listOAuthTokensForSource = query({ | 74 | export const listOAuthTokensForSource = internalQuery({ |
| 73 | args: { | 75 | args: { |
| 74 | workspaceId: v.string(), | 76 | workspaceId: v.string(), |
| 75 | sourceId: v.string(), | 77 | sourceId: v.string(), |
| @@ -140,7 +142,7 @@ export const createConvexResolveToolCredentials = ( · 64 unmodified lines | |||
| 140 | const bindings = yield* runQueryEffect( | 142 | const bindings = yield* runQueryEffect( |
| 141 | "list_credential_bindings", | 143 | "list_credential_bindings", |
| 142 | () => | 144 | () => |
| 143 | ctx.runQuery(api.credential_resolver.listCredentialBindingsForSource, { | 145 | ctx.runQuery(runtimeInternal.credential_resolver.listCredentialBindingsForSource, { |
| 144 | workspaceId: context.workspaceId, | 146 | workspaceId: context.workspaceId, |
| 145 | sourceKey: context.sourceKey, | 147 | sourceKey: context.sourceKey, |
| 146 | organizationId: context.organizationId ?? undefined, | 148 | organizationId: context.organizationId ?? undefined, |
| @@ -161,7 +163,7 @@ export const createConvexResolveToolCredentials = ( · 14 unmodified lines | |||
| 161 | binding.provider === "oauth2" && sourceId | 163 | binding.provider === "oauth2" && sourceId |
| 162 | ? yield* runQueryEffect("list_oauth_tokens", () => | 164 | ? yield* runQueryEffect("list_oauth_tokens", () => |
| 163 | ctx | 165 | ctx |
| 164 | .runQuery(api.credential_resolver.listOAuthTokensForSource, { | 166 | .runQuery(runtimeInternal.credential_resolver.listOAuthTokensForSource, { |
| 165 | workspaceId: context.workspaceId, | 167 | workspaceId: context.workspaceId, |
| 166 | sourceId, | 168 | sourceId, |
| 167 | }) | 169 | }) |
| @@ -0,0 +1,69 @@ | |||
| 1 | type RequestDenyOptions = { | ||
| 2 | status: number; | ||
| 3 | message: string; | ||
| 4 | }; | ||
| 5 | |||
| 6 | type InternalSecretOptions = { | ||
| 7 | context: string; | ||
| 8 | envVarNames: ReadonlyArray<string>; | ||
| 9 | headerName?: string; | ||
| 10 | }; | ||
| 11 | |||
| 12 | const trim = (value: string | undefined): string | undefined => { | ||
| 13 | const candidate = value?.trim(); | ||
| 14 | return candidate && candidate.length > 0 ? candidate : undefined; | ||
| 15 | }; | ||
| 16 | |||
| 17 | const denyRequest = (options: RequestDenyOptions): Response => | ||
| 18 | Response.json( | ||
| 19 | { | ||
| 20 | error: options.message, | ||
| 21 | }, | ||
| 22 | { status: options.status }, | ||
| 23 | ); | ||
| 24 | |||
| 25 | const readEnvValue = (name: string): string | undefined => trim(process.env[name]); | ||
| 26 | |||
| 27 | const readConfiguredSecret = (envVarNames: ReadonlyArray<string>): string | null => { | ||
| 28 | for (const name of envVarNames) { | ||
| 29 | const value = readEnvValue(name); | ||
| 30 | if (value) { | ||
| 31 | return value; | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | return null; | ||
| 36 | }; | ||
| 37 | |||
| 38 | const readRequestHeader = (request: Request, name: string): string | null => | ||
| 39 | trim(request.headers.get(name) ?? undefined) ?? null; | ||
| 40 | |||
| 41 | export const enforceInternalSecret = ( | ||
| 42 | request: Request, | ||
| 43 | options: InternalSecretOptions, | ||
| 44 | ): Response | null => { | ||
| 45 | const expectedSecret = readConfiguredSecret(options.envVarNames); | ||
| 46 | if (expectedSecret === null) { | ||
| 47 | return denyRequest({ | ||
| 48 | status: 503, | ||
| 49 | message: `${options.context} authentication secret is not configured`, | ||
| 50 | }); | ||
| 51 | } | ||
| 52 | |||
| 53 | const providedSecret = readRequestHeader(request, options.headerName ?? "x-internal-secret"); | ||
| 54 | if (providedSecret === null) { | ||
| 55 | return denyRequest({ | ||
| 56 | status: 401, | ||
| 57 | message: `${options.context} authentication is required`, | ||
| 58 | }); | ||
| 59 | } | ||
| 60 | |||
| 61 | if (providedSecret !== expectedSecret) { | ||
| 62 | return denyRequest({ | ||
| 63 | status: 403, | ||
| 64 | message: `${options.context} authentication failed`, | ||
| 65 | }); | ||
| 66 | } | ||
| 67 | |||
| 68 | return null; | ||
| 69 | }; | ||
| @@ -21,26 +21,39 @@ const isTruthyEnvValue = (value: string | undefined): boolean => { · 20 unmodified lines | |||
| 21 | return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on"; | 21 | return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on"; |
| 22 | }; | 22 | }; |
| 23 | 23 | ||
| 24 | const mcpAuthRequired = (): boolean => { | 24 | const parseOptionalBooleanEnv = ( |
| 25 | const deploymentMode = (process.env.EXECUTOR_DEPLOYMENT_MODE ?? "").trim().toLowerCase(); | 25 | value: string | undefined, |
| 26 | if ( | 26 | ): boolean | null => { |
| 27 | deploymentMode === "cloud" | 27 | const normalized = value?.trim().toLowerCase(); |
| 28 | || deploymentMode === "hosted" | 28 | if (!normalized) { |
| 29 | || deploymentMode === "production" | 29 | return null; |
| 30 | || deploymentMode === "prod" | 30 | } |
| 31 | ) { | 31 | |
| 32 | if (["1", "true", "yes", "on"].includes(normalized)) { | ||
| 32 | return true; | 33 | return true; |
| 33 | } | 34 | } |
| 34 | 35 | ||
| 35 | if ( | 36 | if (["0", "false", "no", "off"].includes(normalized)) { |
| 36 | deploymentMode === "self-hosted" | ||
| 37 | || deploymentMode === "self_hosted" | ||
| 38 | || deploymentMode === "selfhosted" | ||
| 39 | ) { | ||
| 40 | return false; | 37 | return false; |
| 41 | } | 38 | } |
| 42 | 39 | ||
| 43 | return isTruthyEnvValue(process.env.EXECUTOR_ENFORCE_MCP_AUTH); | 40 | return null; |
| 41 | }; | ||
| 42 | |||
| 43 | const mcpAuthRequired = (): boolean => { | ||
| 44 | if (isTruthyEnvValue(process.env.EXECUTOR_ALLOW_UNAUTHENTICATED_MCP)) { | ||
| 45 | return false; | ||
| 46 | } | ||
| 47 | |||
| 48 | const explicitEnforcement = parseOptionalBooleanEnv( | ||
| 49 | process.env.EXECUTOR_ENFORCE_MCP_AUTH, | ||
| 50 | ); | ||
| 51 | |||
| 52 | if (explicitEnforcement !== null) { | ||
| 53 | return explicitEnforcement; | ||
| 54 | } | ||
| 55 | |||
| 56 | return true; | ||
| 44 | }; | 57 | }; |
| 45 | 58 | ||
| 46 | const getMcpAuthorizationServer = (): string | null => | 59 | const getMcpAuthorizationServer = (): string | null => |
| @@ -13,6 +13,7 @@ import * as Schema from "effect/Schema"; · 12 unmodified lines | |||
| 13 | 13 | ||
| 14 | import { internal } from "./_generated/api"; | 14 | import { internal } from "./_generated/api"; |
| 15 | import { httpAction, type ActionCtx } from "./_generated/server"; | 15 | import { httpAction, type ActionCtx } from "./_generated/server"; |
| 16 | import { enforceInternalSecret } from "./http_security"; | ||
| 16 | import { createConvexSourceToolRegistry } from "./source_tool_registry"; | 17 | import { createConvexSourceToolRegistry } from "./source_tool_registry"; |
| 17 | 18 | ||
| 18 | class RuntimeToolCallBadRequestError extends Data.TaggedError( | 19 | class RuntimeToolCallBadRequestError extends Data.TaggedError( |
| @@ -44,13 +45,6 @@ const RuntimeToolCallRequestSchema = Schema.Struct({ · 25 unmodified lines | |||
| 44 | 45 | ||
| 45 | const decodeRuntimeToolCallRequest = Schema.decodeUnknown(RuntimeToolCallRequestSchema); | 46 | const decodeRuntimeToolCallRequest = Schema.decodeUnknown(RuntimeToolCallRequestSchema); |
| 46 | 47 | ||
| 47 | const readConfiguredWorkspaceId = (value: string | undefined): string => { | ||
| 48 | const normalized = value?.trim(); | ||
| 49 | return normalized && normalized.length > 0 ? normalized : "ws_local"; | ||
| 50 | }; | ||
| 51 | |||
| 52 | const fallbackWorkspaceId = readConfiguredWorkspaceId(process.env.CONVEX_WORKSPACE_ID); | ||
| 53 | |||
| 54 | const badRequest = (message: string): Response => | 48 | const badRequest = (message: string): Response => |
| 55 | Response.json( | 49 | Response.json( |
| 56 | { | 50 | { |
| @@ -91,40 +85,31 @@ const decodeToolCallRequest = (request: Request): Effect.Effect<RuntimeToolCallR · 34 unmodified lines | |||
| 91 | ); | 85 | ); |
| 92 | }); | 86 | }); |
| 93 | 87 | ||
| 94 | const readWorkspaceIdFromCallbackUrl = (request: Request): string | null => { | ||
| 95 | const value = new URL(request.url).searchParams.get("workspaceId")?.trim(); | ||
| 96 | return value && value.length > 0 ? value : null; | ||
| 97 | }; | ||
| 98 | |||
| 99 | const resolveWorkspaceIdForToolCall = ( | 88 | const resolveWorkspaceIdForToolCall = ( |
| 100 | ctx: ActionCtx, | 89 | ctx: ActionCtx, |
| 101 | request: Request, | ||
| 102 | input: RuntimeToolCallRequest, | 90 | input: RuntimeToolCallRequest, |
| 103 | ): Effect.Effect<string, never> => | 91 | ): Effect.Effect<string, RuntimeToolCallBadRequestError> => |
| 104 | Effect.gen(function* () { | 92 | Effect.gen(function* () { |
| 105 | const contextWorkspaceId = input.credentialContext?.workspaceId?.trim(); | ||
| 106 | if (contextWorkspaceId) { | ||
| 107 | return contextWorkspaceId; | ||
| 108 | } | ||
| 109 | |||
| 110 | const callbackWorkspaceId = readWorkspaceIdFromCallbackUrl(request); | ||
| 111 | if (callbackWorkspaceId) { | ||
| 112 | return callbackWorkspaceId; | ||
| 113 | } | ||
| 114 | |||
| 115 | const runWorkspaceId = yield* Effect.tryPromise({ | 93 | const runWorkspaceId = yield* Effect.tryPromise({ |
| 116 | try: () => | 94 | try: () => |
| 117 | ctx.runQuery(internal.task_runs.getTaskRunWorkspaceId, { | 95 | ctx.runQuery(internal.task_runs.getTaskRunWorkspaceId, { |
| 118 | runId: input.runId, | 96 | runId: input.runId, |
| 119 | }), | 97 | }), |
| 120 | catch: () => null, | 98 | catch: (cause) => |
| 121 | }).pipe(Effect.orElseSucceed(() => null)); | 99 | new RuntimeToolCallBadRequestError({ |
| 100 | message: "Unable to resolve runtime callback workspace", | ||
| 101 | details: formatUnknownDetails(cause), | ||
| 102 | }), | ||
| 103 | }); | ||
| 122 | 104 | ||
| 123 | if (typeof runWorkspaceId === "string" && runWorkspaceId.trim().length > 0) { | 105 | if (typeof runWorkspaceId === "string" && runWorkspaceId.trim().length > 0) { |
| 124 | return runWorkspaceId.trim(); | 106 | return runWorkspaceId.trim(); |
| 125 | } | 107 | } |
| 126 | 108 | ||
| 127 | return fallbackWorkspaceId; | 109 | return yield* new RuntimeToolCallBadRequestError({ |
| 110 | message: "Runtime callback run is not associated with a workspace", | ||
| 111 | details: `runId=${input.runId}`, | ||
| 112 | }); | ||
| 128 | }); | 113 | }); |
| 129 | 114 | ||
| 130 | const handleToolCallHttpEffect = ( | 115 | const handleToolCallHttpEffect = ( |
| @@ -132,8 +117,16 @@ const handleToolCallHttpEffect = ( · 1 unmodified line | |||
| 132 | request: Request, | 117 | request: Request, |
| 133 | ): Effect.Effect<Response, never> => | 118 | ): Effect.Effect<Response, never> => |
| 134 | Effect.gen(function* () { | 119 | Effect.gen(function* () { |
| 120 | const blockedBySecret = enforceInternalSecret(request, { | ||
| 121 | context: "Runtime callback", | ||
| 122 | envVarNames: ["CLOUDFLARE_SANDBOX_CALLBACK_SECRET"], | ||
| 123 | }); | ||
| 124 | if (blockedBySecret) { | ||
| 125 | return blockedBySecret; | ||
| 126 | } | ||
| 127 | |||
| 135 | const input = yield* decodeToolCallRequest(request); | 128 | const input = yield* decodeToolCallRequest(request); |
| 136 | const workspaceId = yield* resolveWorkspaceIdForToolCall(ctx, request, input); | 129 | const workspaceId = yield* resolveWorkspaceIdForToolCall(ctx, input); |
| 137 | 130 | ||
| 138 | const toolRegistry = createConvexSourceToolRegistry(ctx, workspaceId); | 131 | const toolRegistry = createConvexSourceToolRegistry(ctx, workspaceId); |
| 139 | 132 | ||
| @@ -47,7 +47,7 @@ export const createExecuteRuntimeRunInConvex = ( · 46 unmodified lines | |||
| 47 | operation: "runtime_available", | 47 | operation: "runtime_available", |
| 48 | message: `Runtime '${convexRuntimeAdapter.kind}' is not configured in this convex process.`, | 48 | message: `Runtime '${convexRuntimeAdapter.kind}' is not configured in this convex process.`, |
| 49 | details: | 49 | details: |
| 50 | "Set CLOUDFLARE_SANDBOX_RUN_URL, CLOUDFLARE_SANDBOX_AUTH_TOKEN, and CLOUDFLARE_SANDBOX_CALLBACK_URL.", | 50 | "Set CLOUDFLARE_SANDBOX_RUN_URL, CLOUDFLARE_SANDBOX_AUTH_TOKEN, CLOUDFLARE_SANDBOX_CALLBACK_URL, and CLOUDFLARE_SANDBOX_CALLBACK_SECRET.", |
| 51 | }); | 51 | }); |
| 52 | } | 52 | } |
| 53 | 53 | ||
| @@ -36,6 +36,47 @@ const RuntimeToolCallRequestSchema = Schema.Struct({ · 35 unmodified lines | |||
| 36 | 36 | ||
| 37 | const decodeRuntimeToolCallRequest = Schema.decodeUnknown(RuntimeToolCallRequestSchema); | 37 | const decodeRuntimeToolCallRequest = Schema.decodeUnknown(RuntimeToolCallRequestSchema); |
| 38 | 38 | ||
| 39 | const trim = (value: string | undefined): string | undefined => { | ||
| 40 | const candidate = value?.trim(); | ||
| 41 | return candidate && candidate.length > 0 ? candidate : undefined; | ||
| 42 | }; | ||
| 43 | |||
| 44 | const readHeader = (request: Request, name: string): string | null => | ||
| 45 | trim(request.headers.get(name) ?? undefined) ?? null; | ||
| 46 | |||
| 47 | const readEnv = (name: string): string | undefined => trim(process.env[name]); | ||
| 48 | |||
| 49 | const runtimeCallbackSecret = (): string | null => | ||
| 50 | readEnv("CLOUDFLARE_SANDBOX_CALLBACK_SECRET") ?? null; | ||
| 51 | |||
| 52 | const toDeniedResult = (status: number, error: string): Response => | ||
| 53 | Response.json( | ||
| 54 | { | ||
| 55 | ok: false, | ||
| 56 | kind: "failed", | ||
| 57 | error, | ||
| 58 | } satisfies RuntimeToolCallResult, | ||
| 59 | { status }, | ||
| 60 | ); | ||
| 61 | |||
| 62 | const enforceToolCallSecurity = (request: Request): Response | null => { | ||
| 63 | const expectedSecret = runtimeCallbackSecret(); | ||
| 64 | if (!expectedSecret) { | ||
| 65 | return toDeniedResult(503, "Runtime callback secret is not configured"); | ||
| 66 | } | ||
| 67 | |||
| 68 | const providedSecret = readHeader(request, "x-internal-secret"); | ||
| 69 | if (!providedSecret) { | ||
| 70 | return toDeniedResult(401, "Runtime callback authentication is required"); | ||
| 71 | } | ||
| 72 | |||
| 73 | if (providedSecret !== expectedSecret) { | ||
| 74 | return toDeniedResult(403, "Runtime callback authentication failed"); | ||
| 75 | } | ||
| 76 | |||
| 77 | return null; | ||
| 78 | }; | ||
| 79 | |||
| 39 | const decodeRequestBodyError = (cause: unknown): PmToolCallHttpRequestError => | 80 | const decodeRequestBodyError = (cause: unknown): PmToolCallHttpRequestError => |
| 40 | new PmToolCallHttpRequestError({ | 81 | new PmToolCallHttpRequestError({ |
| 41 | message: "Invalid runtime callback request body", | 82 | message: "Invalid runtime callback request body", |
| @@ -79,6 +120,11 @@ export const createPmToolCallHttpHandler = ( · 37 unmodified lines | |||
| 79 | ): ((request: Request) => Promise<Response>) => { | 120 | ): ((request: Request) => Promise<Response>) => { |
| 80 | return async (request: Request) => { | 121 | return async (request: Request) => { |
| 81 | try { | 122 | try { |
| 123 | const blocked = enforceToolCallSecurity(request); | ||
| 124 | if (blocked) { | ||
| 125 | return blocked; | ||
| 126 | } | ||
| 127 | |||
| 82 | const input = await decodeToolCallRequest(request); | 128 | const input = await decodeToolCallRequest(request); |
| 83 | const result = await handleToolCall(input); | 129 | const result = await handleToolCall(input); |
| 84 | return Response.json(result, { status: 200 }); | 130 | return Response.json(result, { status: 200 }); |
| @@ -21,7 +21,7 @@ export type CloudflareWorkerLoaderConfig = { · 20 unmodified lines | |||
| 21 | runUrl: string; | 21 | runUrl: string; |
| 22 | authToken: string; | 22 | authToken: string; |
| 23 | callbackUrl: string; | 23 | callbackUrl: string; |
| 24 | callbackInternalSecret: string | null; | 24 | callbackInternalSecret: string; |
| 25 | requestTimeoutMs: number; | 25 | requestTimeoutMs: number; |
| 26 | }; | 26 | }; |
| 27 | 27 | ||
| @@ -126,10 +126,7 @@ const resolveConfig = ( · 98 unmodified lines | |||
| 126 | ); | 126 | ); |
| 127 | const callbackInternalSecret = | 127 | const callbackInternalSecret = |
| 128 | options.callbackInternalSecret ?? | 128 | options.callbackInternalSecret ?? |
| 129 | normalizeString( | 129 | normalizeString(env.CLOUDFLARE_SANDBOX_CALLBACK_SECRET) ?? |
| 130 | env.CLOUDFLARE_SANDBOX_CALLBACK_SECRET ?? | ||
| 131 | env.EXECUTOR_INTERNAL_TOKEN, | ||
| 132 | ) ?? | ||
| 133 | null; | 130 | null; |
| 134 | 131 | ||
| 135 | const requestTimeoutMs = | 132 | const requestTimeoutMs = |
| @@ -139,7 +136,7 @@ const resolveConfig = ( · 3 unmodified lines | |||
| 139 | defaultRequestTimeoutMs, | 136 | defaultRequestTimeoutMs, |
| 140 | ); | 137 | ); |
| 141 | 138 | ||
| 142 | if (!runUrl || !authToken || !callbackUrl) { | 139 | if (!runUrl || !authToken || !callbackUrl || !callbackInternalSecret) { |
| 143 | return null; | 140 | return null; |
| 144 | } | 141 | } |
| 145 | 142 | ||
| @@ -163,7 +160,7 @@ const makeDispatchRequest = ( · 17 unmodified lines | |||
| 163 | timeoutMs: input.timeoutMs, | 160 | timeoutMs: input.timeoutMs, |
| 164 | callback: { | 161 | callback: { |
| 165 | url: config.callbackUrl, | 162 | url: config.callbackUrl, |
| 166 | internalSecret: config.callbackInternalSecret ?? undefined, | 163 | internalSecret: config.callbackInternalSecret, |
| 167 | }, | 164 | }, |
| 168 | }; | 165 | }; |
| 169 | 166 | ||