From f266c40de388f5089a4f24507066dd8f9a07baef Mon Sep 17 00:00:00 2001 From: Osvaldo Demo Date: Sun, 14 Jun 2026 16:40:33 -0300 Subject: [PATCH] Document VITE_* env vars must not be marked secret in Netlify MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While verifying the new GH Actions deploy pipeline we discovered the production bundle still shipped with empty VITE_POSTHOG_KEY, VITE_ANALYTICS_*, and VITE_POSTHOG_HOST values. Root cause: all four were flagged as "secret" Netlify env vars, which hides them from the build environment (secret values only reach Functions at request time). Vite reads env at build time only, so the bundle emitted empty strings and silently disabled PostHog analytics in production. Unmark the offending vars at the project level via `netlify env:unset` + `env:set` (no `--secret` flag). This change captures the lesson so future agents do not flag VITE_* vars secret again: - AGENTS.md §3: extends the deploy rules with the source-of-truth split between GH secrets (auth only) and Netlify project env (everything else), plus an explicit rule that VITE_* vars must be regular (non-secret) and a quick audit command. - docs/ARCHITECTURE.md §15.4: adds the same rule and the netlify env:list --plain audit pattern. No code or workflow changes; only docs. Co-authored-by: Cursor --- AGENTS.md | 29 +++++++++++++++++++++++++++-- docs/ARCHITECTURE.md | 10 ++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 049ab43..6caa55d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -122,9 +122,34 @@ Required GitHub secrets: - `NETLIFY_AUTH_TOKEN` (gates deploy) - `NETLIFY_SITE_ID` (gates deploy) +These three are the *only* env values that live in GitHub repo secrets. +Everything else needed by the build or runtime — `DATABASE_URL`, +`JWT_SECRET`, `SITE_URL`, `GOOGLE_*`, `RESULTS_IMPORT_API_KEY`, and the +`VITE_*` family — lives in the Netlify project env. The deploy job +shells out to `netlify deploy --prod --build`, which loads those values +into the Vite build and into the Functions runtime. + If `NETLIFY_AUTH_TOKEN` or `NETLIFY_SITE_ID` is missing, the deploy -job is skipped cleanly; the workflow stays green, and Netlify's own -git auto-deploy (still enabled as a fallback) takes over. +job is skipped cleanly; the workflow stays green. Netlify's git +auto-deploy is disabled (`stop_builds: true`), so a skipped deploy +means no production update — never let the workflow stay green and +silent in that state. + +#### `VITE_*` env vars must not be marked secret in Netlify + +Vite reads env vars at build time only. Netlify's "secret" flag hides +the value from the build environment (it is only injected into +Functions at request time). Marking a `VITE_*` value as secret is +therefore a foot-gun: the build silently emits an empty string and +the deployed bundle ships with that feature broken — analytics, the +PostHog client, the Google client id check, etc. + +Rule: every `VITE_*` env var in Netlify must be a regular env var. +Backend-only secrets (`DATABASE_URL`, `JWT_SECRET`, +`GOOGLE_CLIENT_SECRET`, `RESULTS_IMPORT_API_KEY`) should stay marked +secret. If you ever need to audit, run +`netlify env:list --context production --plain | grep '^VITE_'` — +any masked value (`****`) is a misconfiguration. ## 4. API And Data Safety diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index af36dd0..0b4f4da 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -776,6 +776,16 @@ Notes: - Source of truth for both frontend (`VITE_*`) and backend runtime env vars is Netlify's project env, not GitHub Actions secrets. GitHub secrets only carry the values needed to authenticate the deploy itself (`NETLIFY_AUTH_TOKEN`, `NETLIFY_SITE_ID`) plus `PRODUCTION_DATABASE_URL` for the migrate job. - Netlify's git auto-deploy is disabled (`stop_builds: true`) so the only deploy path is GitHub Actions. `[build.environment] NODE_VERSION = "24.1.0"` is still pinned in `netlify.toml` for defence in depth if auto-deploys are ever re-enabled. +#### `VITE_*` env vars must not be marked secret + +Netlify lets you mark an env var as containing secret values. Secret env vars are never exposed during a build — they only reach Functions at request time. That breaks any `VITE_*` value, because Vite *only* reads env at build time. A silent regression we hit during this work: the bundle shipped with empty `VITE_POSTHOG_KEY`, `VITE_ANALYTICS_ENABLED`, `VITE_ANALYTICS_PROVIDER`, and `VITE_POSTHOG_HOST` even though all four were set on the Netlify project, because all four had been flagged secret. PostHog analytics were effectively dead in prod with no error. + +Operational rules: + +- Any env var prefixed `VITE_` must be a regular (non-secret) Netlify env var. They are meant to be in the public browser bundle by design (PostHog uses a public client-side key, analytics flag is a boolean, etc.). +- Backend-only secrets (`DATABASE_URL`, `JWT_SECRET`, `GOOGLE_CLIENT_SECRET`, `RESULTS_IMPORT_API_KEY`) stay marked secret. They are read from `process.env` by the Lambda at request time and never need to appear in a build. +- Quick check during CI debugging: `netlify env:list --context production --plain | grep '^VITE_'`. If any value is masked with `****`, that variable is incorrectly flagged secret. + ### Destructive Migrations And The Deploy Race CI's `migrate-production` job and the deploy step are sequenced in the