From 0d76868a42b6d96923070874602e6ea2b1d237a0 Mon Sep 17 00:00:00 2001 From: kcar Date: Thu, 28 May 2026 13:51:46 +0100 Subject: [PATCH] security: remove VITE_TLSYNC_SECRET from frontend bundle - Remove import.meta.env.VITE_TLSYNC_SECRET from syncService.ts - Add optional token parameter to SyncConnectionOptions interface (caller passes token fetched from API, not a baked-in secret) - Add clientEnvBlocklist in vite.config.ts to prevent sensitive VITE_ env vars (TLSYNC_SECRET, MICROSOFT_CLIENT_SECRET*) from reaching bundle - Remove VITE_TLSYNC_SECRET from .env.example - Token auth for TLSync now handled server-side or via API endpoint Ref: t_99d166c6 --- .env.example | 1 - src/services/tldraw/syncService.ts | 9 ++++++--- vite.config.ts | 9 ++++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index 775b7af..8a73503 100644 --- a/.env.example +++ b/.env.example @@ -27,7 +27,6 @@ VITE_SUPABASE_ANON_KEY=your-supabase-anon-key # TLSync (TLDraw Sync) Configuration # ============================================================================= VITE_TLSYNC_URL=https://app.classroomcopilot.ai/tldraw -VITE_TLSYNC_SECRET=your-tlsync-secret # ============================================================================= # WhisperLive (Transcription) Configuration diff --git a/src/services/tldraw/syncService.ts b/src/services/tldraw/syncService.ts index 21c31e2..71a873a 100644 --- a/src/services/tldraw/syncService.ts +++ b/src/services/tldraw/syncService.ts @@ -15,6 +15,8 @@ export interface SyncConnectionOptions { color: string; roomId?: string; baseUrl: string; + /** Optional TLSync auth token (fetched from API server-side, never baked into bundle) */ + token?: string; } export function createSyncConnectionOptions(options: SyncConnectionOptions) { @@ -22,8 +24,8 @@ export function createSyncConnectionOptions(options: SyncConnectionOptions) { userId, displayName, roomId = 'multiplayer', - baseUrl - + baseUrl, + token = '' } = options; // Ensure we have valid user info @@ -72,7 +74,8 @@ export function createSyncConnectionOptions(options: SyncConnectionOptions) { roomId: effectiveRoomId }); - const token = import.meta.env.VITE_TLSYNC_SECRET ?? '' + // Token is provided by the caller (e.g. fetched from API server-side). + // Never embed a shared secret in the frontend bundle. const tokenParam = token ? `?token=${encodeURIComponent(token)}` : '' return { diff --git a/vite.config.ts b/vite.config.ts index 121250e..5e57502 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -14,9 +14,16 @@ export default defineConfig(({ mode }) => { // Determine client-side env vars to expose const envPrefix = 'VITE_' + // Blocklist: sensitive env vars that must NEVER reach the client bundle. + const clientEnvBlocklist = new Set([ + 'VITE_TLSYNC_SECRET', + 'VITE_MICROSOFT_CLIENT_SECRET', + 'VITE_MICROSOFT_CLIENT_SECRET_ID', + ]) + const clientEnv = Object.fromEntries( Object.entries(env) - .filter(([key]) => key.startsWith(envPrefix)) + .filter(([key]) => key.startsWith(envPrefix) && !clientEnvBlocklist.has(key)) .map(([key, value]) => [`import.meta.env.${key}`, JSON.stringify(value)]) )