From 61f8ce78550e405dddd2faa3e5264841e6a85fde Mon Sep 17 00:00:00 2001 From: Matt Brockman Date: Thu, 25 Jun 2026 20:15:49 -0700 Subject: [PATCH 1/5] Gate terminal templates by trusted providers --- src/app/sbx/new/route.ts | 15 +---- src/core/modules/feature-flags/definitions.ts | 10 +++ .../feature-flags/feature-flags.client.tsx | 18 ++++- .../dashboard-terminal-command-dialog.tsx | 65 ++++++++++++------- .../dashboard/terminal/dashboard-terminal.tsx | 50 +++++++++++--- src/features/dashboard/terminal/template.ts | 36 ++++++++++ src/features/dashboard/terminal/types.ts | 3 +- tests/unit/dashboard-terminal.test.ts | 33 ++++++++++ tests/unit/feature-flags.test.ts | 27 +++++++- tests/unit/sbx-new-route.test.ts | 52 +++++++++++++++ 10 files changed, 259 insertions(+), 50 deletions(-) create mode 100644 tests/unit/sbx-new-route.test.ts diff --git a/src/app/sbx/new/route.ts b/src/app/sbx/new/route.ts index 26710beb1..907289cf7 100644 --- a/src/app/sbx/new/route.ts +++ b/src/app/sbx/new/route.ts @@ -1,6 +1,4 @@ -import Sandbox from 'e2b' import { type NextRequest, NextResponse } from 'next/server' -import { authHeaders } from '@/configs/api' import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls' import { getAuthContext } from '@/core/server/auth' import { resolveUserTeam } from '@/core/server/functions/team/resolve-user-team' @@ -39,14 +37,6 @@ export const GET = async (req: NextRequest) => { return NextResponse.redirect(new URL(req.url).origin) } - const sbx = await Sandbox.create(template, { - apiUrl: process.env.NEXT_PUBLIC_INFRA_API_URL, - domain: process.env.NEXT_PUBLIC_E2B_DOMAIN, - apiHeaders: { - ...authHeaders(authContext.accessToken, team.id), - }, - }) - const terminalParams = new URLSearchParams({ template }) const command = requestUrl.searchParams.get('command')?.trim() @@ -54,10 +44,7 @@ export const GET = async (req: NextRequest) => { terminalParams.set('command', command) } - const terminalUrl = PROTECTED_URLS.SANDBOX_TERMINAL( - team.slug, - sbx.sandboxId - ) + const terminalUrl = PROTECTED_URLS.TERMINAL(team.slug) return NextResponse.redirect( new URL(`${terminalUrl}?${terminalParams.toString()}`, req.url) diff --git a/src/core/modules/feature-flags/definitions.ts b/src/core/modules/feature-flags/definitions.ts index c858ceca7..be6461ea6 100644 --- a/src/core/modules/feature-flags/definitions.ts +++ b/src/core/modules/feature-flags/definitions.ts @@ -1,3 +1,4 @@ +import { z } from 'zod' import type { FeatureFlagDefinition } from '@/core/modules/feature-flags/types' export const FEATURE_FLAGS = { @@ -31,6 +32,15 @@ export const FEATURE_FLAGS = { 'Disables provisioning of e2b access tokens via generateE2BUserAccessToken. When enabled, the legacy CLI flow shows an upgrade prompt and the createAccessToken tRPC mutation returns an error.', exposure: 'server', }, + trustedTemplateProviders: { + kind: 'payload', + key: 'trusted_template_providers', + defaultValue: [], + schema: z.array(z.string()), + description: + 'Template providers whose namespaced templates can auto-start dashboard terminals.', + exposure: 'both', + }, } as const satisfies Record export type FeatureFlagId = keyof typeof FEATURE_FLAGS diff --git a/src/core/modules/feature-flags/feature-flags.client.tsx b/src/core/modules/feature-flags/feature-flags.client.tsx index cd5d7a2b5..6e747ad2c 100644 --- a/src/core/modules/feature-flags/feature-flags.client.tsx +++ b/src/core/modules/feature-flags/feature-flags.client.tsx @@ -10,11 +10,13 @@ import { import { type BooleanFeatureFlagId, FEATURE_FLAGS, + type PayloadFeatureFlagId, } from '@/core/modules/feature-flags/definitions' import type { EvaluatedFeatureFlag } from '@/core/modules/feature-flags/types' type FeatureFlagsContextValue = { flags: EvaluatedFeatureFlag[] + getPayload(flagId: PayloadFeatureFlagId): unknown isEnabled(flagId: BooleanFeatureFlagId): boolean } @@ -47,9 +49,17 @@ export function FeatureFlagsProvider({ [flagsById] ) + const getPayload = useCallback( + (flagId: PayloadFeatureFlagId) => { + const evaluatedFlag = flagsById.get(flagId) + return evaluatedFlag?.value ?? FEATURE_FLAGS[flagId].defaultValue + }, + [flagsById] + ) + const value = useMemo( - () => ({ flags: initialFlags, isEnabled }), - [initialFlags, isEnabled] + () => ({ flags: initialFlags, getPayload, isEnabled }), + [initialFlags, getPayload, isEnabled] ) return ( @@ -72,3 +82,7 @@ export function useFeatureFlags() { export function useFeatureFlag(flagId: BooleanFeatureFlagId) { return useFeatureFlags().isEnabled(flagId) } + +export function useFeatureFlagPayload(flagId: PayloadFeatureFlagId) { + return useFeatureFlags().getPayload(flagId) +} diff --git a/src/features/dashboard/terminal/dashboard-terminal-command-dialog.tsx b/src/features/dashboard/terminal/dashboard-terminal-command-dialog.tsx index 34c6e902d..c4de9cd44 100644 --- a/src/features/dashboard/terminal/dashboard-terminal-command-dialog.tsx +++ b/src/features/dashboard/terminal/dashboard-terminal-command-dialog.tsx @@ -17,7 +17,7 @@ import type { PendingTerminalLaunch } from './types' interface DashboardTerminalCommandDialogProps { launch: PendingTerminalLaunch | null onCancel: () => void - onConfirm: (command: string) => void + onConfirm: (command?: string) => void } export default function DashboardTerminalCommandDialog({ @@ -28,6 +28,8 @@ export default function DashboardTerminalCommandDialog({ const commandInputId = useId() const [command, setCommand] = useState('') const normalizedCommand = command.trim() + const hasCommand = launch?.command !== undefined + const untrustedTemplateProvider = launch?.untrustedTemplateProvider useEffect(() => { setCommand(launch?.command ?? '') @@ -40,10 +42,15 @@ export default function DashboardTerminalCommandDialog({
- Review terminal command + + {hasCommand + ? 'Review terminal command' + : 'Review terminal template'} + - This command will run inside a persistent E2B sandbox after the - terminal opens. + {hasCommand + ? 'This command will run inside a persistent E2B sandbox after the terminal opens.' + : 'This terminal will start from a template published by an untrusted provider.'} @@ -63,21 +70,33 @@ export default function DashboardTerminalCommandDialog({ {launch.target?.template ?? 'base'} -
- -