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