From 904ae0948624c913d19cf13778971c32194b22a5 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Sun, 13 Sep 2026 20:08:06 +0200 Subject: [PATCH] LLM: keep API key in localStorage only, default to muse-spark-1.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Secrets must never be baked into the static build. Stop reading VITE_AI_API_KEY from env — key comes only from localStorage via the ⚙️ config dialog (flow-state:llm). Gateway URL / model may still come from env as non-secret defaults. - src/llm/config.js: DEFAULTS model → meta/muse-spark-1.3-contributor (cheap, per user request), readEnv no longer touches apiKey, getLlmConfig resolves key only from stored.apiKey. - .env.example: drop VITE_AI_API_KEY=, document browser-only key flow. - src/main.js: dialog copy now says key is browser-only, never baked. Build + lint:scenes + tests still green. No VITE_AI_API_KEY string remains in source. Co-Authored-By: Claude --- flow-state/.env.example | 8 ++++---- flow-state/src/llm/config.js | 21 +++++++++++---------- flow-state/src/main.js | 4 ++-- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/flow-state/.env.example b/flow-state/.env.example index d9ca802..31b9c7c 100644 --- a/flow-state/.env.example +++ b/flow-state/.env.example @@ -1,5 +1,5 @@ -# Vercel AI Gateway — copy to .env.local and fill VITE_AI_API_KEY -# Get a token at https://vercel.com/dashboard/ai-gateway +# Vercel AI Gateway (non-secret defaults). The API key is NOT set here — +# it lives only in the browser's localStorage, entered via the ⚙️ dialog. +# Copy to .env.local to override defaults for gateway / model if needed. VITE_AI_GATEWAY_URL=https://ai-gateway.vercel.sh/v1 -VITE_AI_MODEL=openai/gpt-4o-mini -VITE_AI_API_KEY= +VITE_AI_MODEL=meta/muse-spark-1.3-contributor diff --git a/flow-state/src/llm/config.js b/flow-state/src/llm/config.js index 3b06153..200724a 100644 --- a/flow-state/src/llm/config.js +++ b/flow-state/src/llm/config.js @@ -1,14 +1,16 @@ -// LLM gateway config — localStorage overrides > VITE env > defaults. +// LLM gateway config — localStorage for the secret, env only for non-secrets. // -// The app is a static Vite site with no server; the gateway credential lives -// in the browser. Never commit a key — paste it in the panel (→ localStorage) -// or in an untracked .env.local. +// The app is a static Vite site with no server. The API key is a secret and +// must never be baked into the build — it lives only in the browser's +// localStorage (→ ⚙️ dialog). Gateway URL and model are non-secret and may +// come from VITE env as defaults with a localStorage override; the key never +// does. const STORAGE_KEY = 'flow-state:llm'; const DEFAULTS = { gatewayUrl: 'https://ai-gateway.vercel.sh/v1', - model: 'openai/gpt-4o-mini', + model: 'meta/muse-spark-1.3-contributor', apiKey: '', }; @@ -18,10 +20,10 @@ function readEnv() { return { gatewayUrl: env.VITE_AI_GATEWAY_URL || null, model: env.VITE_AI_MODEL || null, - apiKey: env.VITE_AI_API_KEY || null, + // apiKey intentionally NOT read from env — see module comment. }; } catch { - return { gatewayUrl: null, model: null, apiKey: null }; + return { gatewayUrl: null, model: null }; } } @@ -36,14 +38,13 @@ function readStorage() { } } -/** Resolved config — localStorage wins, then env, then defaults. */ +/** Resolved config — key from localStorage only (never env); URL/model from localStorage > env > defaults. */ export function getLlmConfig() { const env = readEnv(); const stored = readStorage(); const gatewayUrl = stored.gatewayUrl || env.gatewayUrl || DEFAULTS.gatewayUrl; const model = stored.model || env.model || DEFAULTS.model; - const apiKey = stored.apiKey != null ? stored.apiKey : (env.apiKey != null ? env.apiKey : DEFAULTS.apiKey); - const key = typeof apiKey === 'string' ? apiKey.trim() : ''; + const key = typeof stored.apiKey === 'string' ? stored.apiKey.trim() : ''; return { gatewayUrl: String(gatewayUrl || DEFAULTS.gatewayUrl).trim() || DEFAULTS.gatewayUrl, model: String(model || DEFAULTS.model).trim() || DEFAULTS.model, diff --git a/flow-state/src/main.js b/flow-state/src/main.js index 18612f8..77afc2e 100644 --- a/flow-state/src/main.js +++ b/flow-state/src/main.js @@ -902,8 +902,8 @@ function openLlmConfigDialog() {
Stored in localStorage (flow-state:llm). Never committed. Leave API key empty to disable.
- -
Env fallback: VITE_AI_GATEWAY_URL / VITE_AI_MODEL / VITE_AI_API_KEY in .env.local. See .env.example.
+ +
Gateway URL / model may also come from .env.local (VITE_AI_GATEWAY_URL / VITE_AI_MODEL); API key is browser-only via this dialog and never baked into the build. See .env.example.
Budget: ${counts.loadCount}/${counts.perLoad} this load · ${counts.sessionCount}/${counts.perSession} this session. Counters reset on reload / tab close.