sec: reject production VITE_DEV_MODE=true (FE-01) #695

Closed
opened 2026-08-28 09:24:19 +00:00 by PlasticDigits · 14 comments
PlasticDigits commented 2026-08-28 09:24:19 +00:00 (Migrated from gitlab.com)

Gate

P1 supply-chain / Chain B. Internal audit INTERNAL_GROK46_1787908099 FE-01 (still active from INTERNAL_KIMIK3_1785897304 FE-01 / F389-08). Production vite build already rejects an inlined mnemonic (#118 / #378) and requires VITE_WC_PROJECT_ID. It does not reject VITE_DEV_MODE=true. Combined with GitLab only_allow_merge_if_pipeline_succeeds=false (sister ticket), a Coolify/env mistake can ship Simulated Wallet chrome on dex.cl8y.com.

Related: #118, #378. Do not reopen mnemonic or WC-id guards.

Current codebase

function assertBuildEnvGuards(command: string, mode: string, env: Record<string, string>): void {
  if (command !== 'build') return
  // rejects VITE_DEV_MNEMONIC unless development or VITE_ALLOW_DEV_MNEMONIC=local-only
  // rejects production build without VITE_WC_PROJECT_ID
}
  • frontend-dapp/src/utils/constants.ts: DEV_MODE = import.meta.env.VITE_DEV_MODE === 'true'
  • frontend-dapp/src/services/terraclassic/devWallet.ts: Simulated Wallet / local sign only when VITE_DEV_MODE=true and mnemonic present
  • frontend-dapp/src/viteConfig.build.test.ts: covers mnemonic + WC id, not VITE_DEV_MODE
  • Vitest/Playwright set VITE_DEV_MODE=true on purpose (vitest.config.ts, playwright.config.ts)
  • vite build / Coolify production uses mode=production

A production bundle with VITE_DEV_MODE=true and no mnemonic does not leak a seed, but it can show Simulated Wallet UI, local-sign paths, and “dev network” affordances. That is a phishing / wrong-network surface if the env flag is set in Coolify.

Why the new implementation is needed

Build guards exist so production cannot depend on operator memory. Mnemonic is already fail-closed. VITE_DEV_MODE is the remaining flag that turns on the entire dev-wallet feature gate. One-line reject closes FE-01 without changing LocalTerra / Playwright.

Constraints / guardrails

  1. Do not break vite/vitest/playwright in development or localterra. Those must keep VITE_DEV_MODE=true.
  2. Do not treat VITE_ALLOW_DEV_MNEMONIC=local-only as permission to ship VITE_DEV_MODE in production mode. Production reject is unconditional.
  3. Staging / non-production vite build --mode staging may keep VITE_DEV_MODE if needed for QA Simulated Wallet — document the choice. Recommend: reject VITE_DEV_MODE=true for mode === 'production' only (same pattern as WC id).
  4. Do not inline or log mnemonics. Existing #118 guard stays first.
  5. Do not change CSP, WalletConnect, or clickwrap in this ticket.
  6. Error text must name the variable and tell the operator to unset it in Coolify / .env.production.

Relevant files

Path Role
frontend-dapp/vite.config.ts assertBuildEnvGuards
frontend-dapp/src/viteConfig.build.test.ts Build-guard unit tests
frontend-dapp/src/utils/constants.ts DEV_MODE
frontend-dapp/src/services/terraclassic/devWallet.ts Runtime gate (unchanged)
frontend-dapp/vitest.config.ts Must keep VITE_DEV_MODE=true
frontend-dapp/playwright.config.ts Must keep local VITE_DEV_MODE=true
docs/frontend.md Trust-boundary note

In assertBuildEnvGuards, after the WC-id check:

  • If mode === 'production' && env.VITE_DEV_MODE === 'true' → throw a clear error (same style as mnemonic / WC).
  • Add a vitest case next to the mnemonic tests: production load rejects; mode=development / vitest env still loads; staging without the flag still builds.
  • One sentence in docs/frontend.md § trust boundaries / Simulated Wallet.
  • make verify-issue-<N>: grep the throw + the new test name.

Acceptance criteria

  • vite build --mode production with VITE_DEV_MODE=true fails closed (no bundle).
  • vite build --mode production with flag unset still succeeds (existing WC id required).
  • LocalTerra / make test-frontend / Playwright e2e still run with VITE_DEV_MODE=true.
  • viteConfig.build.test.ts asserts the production reject; no mnemonic logged.
  • make verify-issue-<N> + Makefile / AGENTS.md frontend table.
  • Docs mention the production reject.

Test plan — happy / bad path

Path Expect
loadConfigFromFile({ command: 'build', mode: 'production' }) + VITE_DEV_MODE=true Throws /VITE_DEV_MODE/
Same, flag unset / false Config loads (WC id present)
{ command: 'build', mode: 'development' } + flag true Loads (local)
{ command: 'serve', mode: 'production' } Guard does not run (command !== 'build') — unchanged
make test-frontend Existing suite green
Simulated Wallet on LocalTerra Still appears when .env.development sets both flags

Test plan — attack / abuse vectors

Vector Expect
Coolify sets VITE_DEV_MODE=true on production build Build fails; old bundle is not replaced
Attacker lands VITE_DEV_MODE=true in .env.production on main CI test-frontend-build / this guard fails the pipeline
Flag true + mnemonic also set Mnemonic error still fires (order either way; both reject)
Staging QA wants Simulated Wallet Use non-production mode; production still blocked
Bypass via import.meta.env default Production define is 'true' only if env set — guard reads the same env as Vite

Verification criteria

  1. cd frontend-dapp && VITE_DEV_MODE=true VITE_WC_PROJECT_ID=x npx vite build --mode production exits non-zero.
  2. make test-frontend and src/viteConfig.build.test.ts include the new case.
  3. make verify-issue-<N> green.
  4. Coolify production env has VITE_DEV_MODE unset (operator check; record on the issue).
  5. Audit FE-01 closable when this lands.
## Gate **P1 supply-chain / Chain B.** Internal audit `INTERNAL_GROK46_1787908099` **FE-01** (still active from `INTERNAL_KIMIK3_1785897304` FE-01 / F389-08). Production `vite build` already rejects an inlined mnemonic (#118 / #378) and requires `VITE_WC_PROJECT_ID`. It does **not** reject `VITE_DEV_MODE=true`. Combined with GitLab `only_allow_merge_if_pipeline_succeeds=false` (sister ticket), a Coolify/env mistake can ship Simulated Wallet chrome on `dex.cl8y.com`. Related: [#118](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/118), [#378](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/378). Do not reopen mnemonic or WC-id guards. ## Current codebase ```25:43:frontend-dapp/vite.config.ts function assertBuildEnvGuards(command: string, mode: string, env: Record<string, string>): void { if (command !== 'build') return // rejects VITE_DEV_MNEMONIC unless development or VITE_ALLOW_DEV_MNEMONIC=local-only // rejects production build without VITE_WC_PROJECT_ID } ``` - `frontend-dapp/src/utils/constants.ts`: `DEV_MODE = import.meta.env.VITE_DEV_MODE === 'true'` - `frontend-dapp/src/services/terraclassic/devWallet.ts`: Simulated Wallet / local sign only when `VITE_DEV_MODE=true` **and** mnemonic present - `frontend-dapp/src/viteConfig.build.test.ts`: covers mnemonic + WC id, **not** `VITE_DEV_MODE` - Vitest/Playwright set `VITE_DEV_MODE=true` on purpose (`vitest.config.ts`, `playwright.config.ts`) - `vite build` / Coolify production uses `mode=production` A production bundle with `VITE_DEV_MODE=true` and **no** mnemonic does not leak a seed, but it can show Simulated Wallet UI, local-sign paths, and “dev network” affordances. That is a phishing / wrong-network surface if the env flag is set in Coolify. ## Why the new implementation is needed Build guards exist so production cannot depend on operator memory. Mnemonic is already fail-closed. `VITE_DEV_MODE` is the remaining flag that turns on the entire dev-wallet feature gate. One-line reject closes FE-01 without changing LocalTerra / Playwright. ## Constraints / guardrails 1. **Do not** break `vite`/`vitest`/`playwright` in `development` or `localterra`. Those must keep `VITE_DEV_MODE=true`. 2. **Do not** treat `VITE_ALLOW_DEV_MNEMONIC=local-only` as permission to ship `VITE_DEV_MODE` in **production** mode. Production reject is unconditional. 3. Staging / non-production `vite build --mode staging` may keep `VITE_DEV_MODE` if needed for QA Simulated Wallet — document the choice. Recommend: reject `VITE_DEV_MODE=true` for `mode === 'production'` only (same pattern as WC id). 4. **Do not** inline or log mnemonics. Existing #118 guard stays first. 5. **Do not** change CSP, WalletConnect, or clickwrap in this ticket. 6. Error text must name the variable and tell the operator to unset it in Coolify / `.env.production`. ## Relevant files | Path | Role | |------|------| | `frontend-dapp/vite.config.ts` | `assertBuildEnvGuards` | | `frontend-dapp/src/viteConfig.build.test.ts` | Build-guard unit tests | | `frontend-dapp/src/utils/constants.ts` | `DEV_MODE` | | `frontend-dapp/src/services/terraclassic/devWallet.ts` | Runtime gate (unchanged) | | `frontend-dapp/vitest.config.ts` | Must keep `VITE_DEV_MODE=true` | | `frontend-dapp/playwright.config.ts` | Must keep local `VITE_DEV_MODE=true` | | `docs/frontend.md` | Trust-boundary note | ## Recommended direction In `assertBuildEnvGuards`, after the WC-id check: - If `mode === 'production' && env.VITE_DEV_MODE === 'true'` → throw a clear error (same style as mnemonic / WC). - Add a vitest case next to the mnemonic tests: production load **rejects**; `mode=development` / vitest env **still loads**; staging without the flag still builds. - One sentence in `docs/frontend.md` § trust boundaries / Simulated Wallet. - `make verify-issue-<N>`: grep the throw + the new test name. ## Acceptance criteria - [ ] `vite build --mode production` with `VITE_DEV_MODE=true` fails closed (no bundle). - [ ] `vite build --mode production` with flag unset still succeeds (existing WC id required). - [ ] LocalTerra / `make test-frontend` / Playwright e2e still run with `VITE_DEV_MODE=true`. - [ ] `viteConfig.build.test.ts` asserts the production reject; no mnemonic logged. - [ ] `make verify-issue-<N>` + Makefile / AGENTS.md frontend table. - [ ] Docs mention the production reject. ## Test plan — happy / bad path | Path | Expect | |------|--------| | `loadConfigFromFile({ command: 'build', mode: 'production' })` + `VITE_DEV_MODE=true` | Throws `/VITE_DEV_MODE/` | | Same, flag unset / `false` | Config loads (WC id present) | | `{ command: 'build', mode: 'development' }` + flag true | Loads (local) | | `{ command: 'serve', mode: 'production' }` | Guard does not run (`command !== 'build'`) — unchanged | | `make test-frontend` | Existing suite green | | Simulated Wallet on LocalTerra | Still appears when `.env.development` sets both flags | ## Test plan — attack / abuse vectors | Vector | Expect | |--------|--------| | Coolify sets `VITE_DEV_MODE=true` on production build | Build fails; old bundle is not replaced | | Attacker lands `VITE_DEV_MODE=true` in `.env.production` on `main` | CI `test-frontend-build` / this guard fails the pipeline | | Flag true + mnemonic also set | Mnemonic error still fires (order either way; both reject) | | Staging QA wants Simulated Wallet | Use non-production mode; production still blocked | | Bypass via `import.meta.env` default | Production define is `'true'` only if env set — guard reads the same env as Vite | ## Verification criteria 1. `cd frontend-dapp && VITE_DEV_MODE=true VITE_WC_PROJECT_ID=x npx vite build --mode production` exits non-zero. 2. `make test-frontend` and `src/viteConfig.build.test.ts` include the new case. 3. `make verify-issue-<N>` green. 4. Coolify production env has `VITE_DEV_MODE` unset (operator check; record on the issue). 5. Audit FE-01 closable when this lands.
PlasticDigits commented 2026-08-28 09:24:20 +00:00 (Migrated from gitlab.com)

marked as related to #118

marked as related to #118
PlasticDigits commented 2026-08-28 09:24:21 +00:00 (Migrated from gitlab.com)

marked as related to #378

marked as related to #378
PlasticDigits commented 2026-08-28 09:39:04 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1192

mentioned in merge request !1192
PlasticDigits commented 2026-08-28 09:39:10 +00:00 (Migrated from gitlab.com)

Implementation is in !1192 (feat/695-reject-prod-vite-dev-mode).

Production vite build --mode production now rejects VITE_DEV_MODE=true (FE-01). LocalTerra / Vitest / Playwright keep the flag. Operator follow-up: confirm Coolify production env has VITE_DEV_MODE unset.

Verify: make verify-issue-695

Implementation is in !1192 (`feat/695-reject-prod-vite-dev-mode`). Production `vite build --mode production` now rejects `VITE_DEV_MODE=true` (FE-01). LocalTerra / Vitest / Playwright keep the flag. Operator follow-up: confirm Coolify production env has `VITE_DEV_MODE` unset. Verify: `make verify-issue-695`
PlasticDigits commented 2026-08-28 11:04:04 +00:00 (Migrated from gitlab.com)

mentioned in commit 489268eb91

mentioned in commit 489268eb91625b15263c2485a8de811e21e14cb3
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-28 11:04:04 +00:00
PlasticDigits commented 2026-08-28 11:07:02 +00:00 (Migrated from gitlab.com)

Merge sanity (!1192 landed on main)

Conflict: Makefile help vs origin/main (plus !1193). Resolved by keeping main Cloud Agent / Frontend lists and adding verify-issue-695. Then make verify-issue-695 7/7 PASS (12 Vitest cases including production reject, no local-only mnemonic bypass, LocalTerra/Vitest/Playwright keep the flag).

Do not reopen this issue unless a merged invariant is wrong. Operator leftovers moved to a post-merge ticket.

Residual (non-blocking)

  • Operator: Coolify production env must have VITE_DEV_MODE unset (record after next frontend rebuild). A mistaken true now fails the production build (desired).
  • LocalTerra Simulated Wallet still appears when .env.development sets both flags (not re-run in this pass; config unchanged).
  • Full make test-frontend / Playwright e2e were not re-run here; verify harness greps + Vitest cover the guard. CI/test-frontend on main is the runtime proof.
  • Optional hygiene: scripts/qa/README.md has no #695 index row.
## Merge sanity (!1192 landed on main) Conflict: `Makefile` help vs `origin/main` (plus !1193). Resolved by keeping main Cloud Agent / Frontend lists and adding `verify-issue-695`. Then `make verify-issue-695` **7/7 PASS** (12 Vitest cases including production reject, no local-only mnemonic bypass, LocalTerra/Vitest/Playwright keep the flag). Do **not** reopen this issue unless a merged invariant is wrong. Operator leftovers moved to a post-merge ticket. ### Residual (non-blocking) - Operator: Coolify production env must have `VITE_DEV_MODE` unset (record after next frontend rebuild). A mistaken `true` now fails the production build (desired). - LocalTerra Simulated Wallet still appears when `.env.development` sets both flags (not re-run in this pass; config unchanged). - Full `make test-frontend` / Playwright e2e were not re-run here; verify harness greps + Vitest cover the guard. CI/`test-frontend` on `main` is the runtime proof. - Optional hygiene: `scripts/qa/README.md` has no #695 index row.
PlasticDigits commented 2026-08-28 11:07:53 +00:00 (Migrated from gitlab.com)

mentioned in issue #698

mentioned in issue #698
PlasticDigits commented 2026-08-28 11:07:54 +00:00 (Migrated from gitlab.com)

marked as related to #698

marked as related to #698
PlasticDigits commented 2026-08-28 11:13:03 +00:00 (Migrated from gitlab.com)

mentioned in issue #699

mentioned in issue #699
PlasticDigits commented 2026-08-28 11:13:05 +00:00 (Migrated from gitlab.com)

marked as related to #699

marked as related to #699
PlasticDigits commented 2026-08-28 11:15:58 +00:00 (Migrated from gitlab.com)

marked as related to #700

marked as related to #700
PlasticDigits commented 2026-08-28 11:15:59 +00:00 (Migrated from gitlab.com)

mentioned in issue #700

mentioned in issue #700
PlasticDigits commented 2026-08-28 11:17:26 +00:00 (Migrated from gitlab.com)

Post-merge leftovers tracked on #700.

Post-merge leftovers tracked on #700.
PlasticDigits commented 2026-08-30 02:48:10 +00:00 (Migrated from gitlab.com)

mentioned in issue #706

mentioned in issue #706
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#695
No description provided.