1341 字
7 分鐘

Vite 的 VITE_ 環境變數會外洩嗎?從 bundle 找出不該公開的 secret

如果你把 API_KEY 改成 VITE_API_KEY,前端就能讀到它;但同一個動作也代表這個值會進入瀏覽器可以下載的 HTML、JavaScript 或 source map。Vite 官方文件明確提醒,VITE_* 不應放敏感資訊,因為它們會在建置時被打包進 client source。

因此,問題不是「怎麼把 .env 藏得更深」,而是先判斷這個值是否本來就能讓每位訪客看到。只要它會讓你不敢貼到公開 issue 或 gist,就不該放在前端的 VITE_ 變數裡。

先把公開設定與私密 secret 分開#

下面的命名方式刻意把邊界寫在檔案裡:

# 可以讓每個瀏覽器知道的公開設定
VITE_API_ORIGIN=https://api.example.com
# 只給伺服器或 edge function 使用
PAYMENTS_SECRET=replace-me-on-the-server
DATABASE_URL=postgres://user:[email protected]/app

前端可以讀取公開值:

const apiOrigin = import.meta.env.VITE_API_ORIGIN;
fetch(`${apiOrigin}/public-config`);

VITE_ 不是權限系統,也不是加密。它只是告訴 Vite:「這個變數預期會被送到 client」。沒有 VITE_ 前綴的變數不會因此自動變成伺服器 API;要使用私密值,仍要由 server、serverless function 或 edge function 讀取,再提供受驗證的 API。

如果你使用 Cloudflare Workers,可以對照 Workers 本機 .dev.vars.env 與 secrets 的分工,把「建置時公開值」和「runtime secret」放在不同邊界。

先在 dist 找出可能被打包的值#

建置後檢查實際輸出,不要只看 .env 是否被 .gitignore 排除:

Terminal window
pnpm exec vite build
rg -n --hidden 'PAYMENTS_SECRET|DATABASE_URL|PRIVATE_TOKEN' dist

如果變數名稱沒有被保留,改用一小段非敏感的測試標記搜尋;不要把 live token 直接放進 shell history、CI log 或文章範例。也要檢查已部署的 JavaScript 與 source map,因為舊 deployment 或 CDN cache 可能仍保留先前的輸出。

若確認真的有 secret 進入 bundle,處理順序應該是:

  1. 先撤銷或輪替已暴露的金鑰,降低舊 bundle 的影響時間。
  2. 從 client 設定移除私密值,改由 server 或 edge endpoint 代為呼叫上游 API。
  3. 從乾淨的設定來源重新建置與部署,必要時清理 hosting 平台的舊產物或 cache。
  4. 再次下載正式環境的資產,確認新 bundle 不含不該公開的值。

私密 API 呼叫要移到 server 邊界#

瀏覽器只送出必要的業務資料,server 端才讀取 secret 並呼叫上游:

browser -> /api/report -> server 或 edge function -> private API
讀取 PAYMENTS_SECRET

前端只需要呼叫自己的受控路徑:

const response = await fetch('/api/report', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ range: 'week' }),
});

這個 endpoint 仍要驗證使用者、權限、輸入和上游回應。把 token 移到 server 不代表可以做一個任意轉發 URL 的 proxy,否則只是把憑證藏起來,沒有縮小上游 API 的操作範圍。

envPrefix 要縮小公開契約,不要放寬#

Vite 預設只公開 VITE_ 前綴。若專案要使用其他公開命名,可以在 vite.config.ts 指定更清楚的前綴:

import { defineConfig } from 'vite';
export default defineConfig({
envPrefix: ['PUBLIC_'],
});

這仍然是「公開給瀏覽器」的清單,不是 secret store。不要把 envPrefix 設成空字串,也不要用過寬的前綴讓整個 process environment 進入 bundle;Vite 官方 shared options 甚至會針對空字串設定直接丟錯,避免意外洩漏。

如果只是因為某個值在 production 變成 undefined,不要用加上 VITE_ 來掩蓋問題。先確認 mode、.env.production、執行建置的工作目錄和 hosting 的 build environment;真的屬於私密值時,正確修法是改成 server runtime。

靜態 Vite 部署要記住建置時機#

Vite 會在 vite build 時把 client env 替換成靜態資產:

build environment -> vite build -> HTML / JavaScript -> CDN

因此,部署平台在 build 完成後才改 dashboard 的環境變數,不會回頭修改已產生的 JavaScript。安全的公開設定需要重新建置並部署;私密設定則不應加入前端 bundle,而要透過 runtime endpoint 或 server-rendered response 提供必要且可公開的部分。

每次合併前可以用三個問題做快速檢查:

  1. 這個值是每位訪客都可以知道的公開設定,還是只供 server 使用的 secret?
  2. 變數名稱是否落在預期的公開前綴與 mode?
  3. 產出的 dist 和正式部署資產是否只包含可公開的資料?

結論很簡單:VITE_ 的意思接近「把這個值送到瀏覽器」。如果值被複製到公開程式碼時會造成安全事件,就不要用前端環境變數解決它。

常見問題#

Q: VITE_API_KEY 算是 secret 嗎?#

A: 不應把它當成 secret。VITE_ 變數會被 Vite 替換進 client source,訪客可以從瀏覽器資產取得。只有本來就能公開的識別值或受限公開金鑰,才適合放在這個前綴下;權限、來源和額度仍要在服務端限制。

Q: 拿掉 VITE_ 後為什麼變成 undefined#

A: 這是 Vite 的公開邊界在運作。沒有公開前綴的值不會提供給 client,應改由 server 或 edge function 讀取;不要為了讓瀏覽器取得它,又把 secret 改回 VITE_

Q: 自訂 envPrefix 能把 secret 藏起來嗎?#

A: 不能。自訂前綴只會改變哪些變數被公開,凡是命中該前綴的值仍會進入 client source。把它限制成明確的 public prefix,並將私密值留在 server runtime。

參考資料:

Vite:Env Variables and Modes

Vite:Shared Options — envPrefix

Vite 的 VITE_ 環境變數會外洩嗎?從 bundle 找出不該公開的 secret
https://laplusda.com/posts/vite-env-secret-exposure/
作者
Zero
發佈於
2026-08-18
許可協議
CC BY-NC-SA 4.0
這篇文章有幫助嗎?

回報錯字、失效連結,或告訴我你想看的延伸主題。