結論
-
Vite では
VITE_で始まる変数は、ビルドした JS(dist/)にそのまま入ります(Vite公式)。.envにVITE_API_KEYと書いてコードで参照すると、値がdist/assets/*.jsに平文で入りました。.env.localに書いても同じでした(筆者の環境で確認)。 -
接頭辞のない
DB_PASSWORDなどは、import.meta.envでは入りません。ただしvite.config.jsでloadEnv(mode, process.cwd(), '')の結果をdefineでprocess.envに丸ごと渡すと、.envの全部の値と、ビルドしたシェルの環境変数までdist/に入りました(筆者の環境で確認)。 - フロントに置いた値は、閲覧者が誰でも読めます。秘密の鍵は
VITE_を付けずにサーバー側だけで使い、ビルド後にdist/に.envの値が入っていないかを確かめます(確認用のスクリプトを最後に載せます)。
この記事は「AIエージェントと秘密情報」の検証シリーズです。新しい検証は X(@supportapp3_lab)でもお知らせしています。
AIに作らせたフロントエンドで「APIキーは .env に入れたので安全」と考えてしまうことがあります。.env は Git には入れなくても、ビルドの結果に入れば公開した瞬間に誰でも読めます。どの書き方で入り、どの書き方なら入らないのかを Vite で確かめました。AIエージェントが実際にどの書き方をよく出すかは確かめていません。
検証環境と範囲
- Debian 13.7 / Node.js 20.19.2 / npm 11.21.0 / Vite 8.3.5
-
npx vite buildでdist/を作り、ダミーの値が含まれるかをgrep -rで調べました。公開やデプロイはしていません。 - 値はすべてダミーの文字列です。
.env は次のとおりです。
VITE_API_KEY=DUMMY_VITE_KEY_0001
DB_PASSWORD=DUMMY_DB_PASS_0002
SECRET_TOKEN=DUMMY_SECRET_0003
1. VITE_ の変数は参照すると dist/ に入る
console.log(import.meta.env.VITE_API_KEY)
| 書き方 | VITE_API_KEY |
DB_PASSWORD |
SECRET_TOKEN |
|---|---|---|---|
import.meta.env.VITE_API_KEY を参照 |
入った | 入らない | 入らない |
| 何も参照しない | 入らない | 入らない | 入らない |
console.log(import.meta.env) で丸ごと出力 |
入った | 入らない | 入らない |
import.meta.env.DB_PASSWORD を参照 |
入らない | 入らない(undefined) |
入らない |
.env.local に VITE_LOCAL=... を追加し丸ごと出力 |
入った | 入らない | 入らない |
.env.local の VITE_LOCAL も入りました。.env.local は「Git に入れないファイル」であって、「ビルドに入らないファイル」ではありません。
Vite 公式も、VITE_ で始まる変数はビルド時にクライアントのコードに入るので、APIキーのような秘密情報を入れてはいけない、と書いています(Env Variables and Modes)。
2. envPrefix: '' は Vite が止める
接頭辞をなくして全部の変数を import.meta.env で使おうとすると、ビルドがエラーで止まりました。
export default { envPrefix: '' }
Error: envPrefix option contains value '', which could lead unexpected exposure of sensitive information.
3. define で process.env に丸ごと渡すと、全部入る
process.env.XXX を使う古いコードを Vite で動かすために、次のように書く例があります。
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return { define: { 'process.env': JSON.stringify(env) } }
})
console.log(process.env.VITE_API_KEY)
参照しているのは VITE_API_KEY だけですが、dist/ には DB_PASSWORD と SECRET_TOKEN の値も入りました。define は process.env の場所に、オブジェクト全体を文字列として埋め込むためです(筆者の推測。結果は実機で確認)。
さらに、.env にない変数を付けてビルドしてみました。
HOST_ONLY_VAR=DUMMY_HOST_0005 npx vite build
DUMMY_HOST_0005 も dist/ に入りました。 loadEnv は .env を読んだうえで、すでに process.env にある変数のうち接頭辞に合うものを合わせます(Vite公式: loadEnv)。第3引数を '' にすると全部の変数が合うので、CI やサーバーでビルドすると、そこに設定されているトークンまで入るおそれがあります。
vite.config.js の書き方 |
VITE_API_KEY |
DB_PASSWORD |
シェルの変数 |
|---|---|---|---|
loadEnv(mode, cwd, '') を process.env に丸ごと |
入った | 入った | 入った |
同じ設定で、コードは process.env.NODE_ENV だけ参照 |
入らない | 入らない | - |
loadEnv(mode, cwd)(既定の VITE_)を丸ごと |
入った | 入らない | 入らない |
'process.env.VITE_API_KEY' だけを define
|
入った | 入らない | - |
process.env.NODE_ENV だけを参照したときは入りませんでした。ただし、コードのどこかで process.env をまるごと使うと入ります。AIにコードを足してもらううちに、そういう参照が増えることもあるので、丸ごと渡す書き方は避けるのが安全です。
4. ビルド後に dist/ を確かめる
.env の値が dist/ に入っていないかを、値を画面に出さずに調べるスクリプトです。プロジェクトのルートで、vite build のあとに実行します。
# .env* の各値が dist/ に入っていないかを、値を表示せずに調べる
for f in .env .env.*; do
[ -f "$f" ] || continue
grep -vE '^\s*(#|$)' "$f" | while IFS='=' read -r k v; do
v="${v%\"}"; v="${v#\"}"
[ ${#v} -ge 8 ] || continue
grep -rlqF -- "$v" dist/ && echo "dist に入っている: $k($f)"
done
done
3の設定で実行すると、次のように出ました。
dist に入っている: VITE_API_KEY(.env)
dist に入っている: DB_PASSWORD(.env)
8文字未満の短い値は、ほかの文字列と偶然一致しやすいので飛ばしています。シェルや CI の環境変数は調べないので、3の書き方をしている場合は設定のほうを直してください。
VITE_ の値が出るのは、公開してよい値(公開用のURLや、公開前提のキー)なら問題ありません。秘密の鍵が出た場合は、その鍵を使う処理をサーバー側(API や サーバーレス関数)に移し、すでに公開していたなら鍵を再発行します。
まとめ
-
VITE_の変数は、参照するとdist/の JS に平文で入る。.env.localでも同じ(実機・Vite公式)。 - 接頭辞のない変数は
import.meta.envでは入らない(実機・Vite公式)。envPrefix: ''はエラーで止まる(実機)。 -
loadEnv(mode, cwd, '')をdefineでprocess.envに丸ごと渡すと、.envの全部とシェルの環境変数まで入った(実機)。 - ビルド後に
dist/を調べる。秘密の鍵はサーバー側だけで使う。
AIエージェントの手元で .env が読めるかどうかは、ai-secret-check(npx github:supportapp3-lab/ai-secret-check scan、値は表示せず通信もしません)で点検できます。ビルド結果の中身はまだ点検対象ではありません。
関連記事:
- .npmignoreを置くと.gitignoreは使われない:.envと鍵がnpmパッケージに入るのを npm pack --dry-run で確認
- AIにコミットを任せる前に:.envと鍵をpre-commitで止める
- 過去にコミットした.envはgitの履歴から読める:見つけ方と git-filter-repo での消し方
参考(公式):
※ 仕様は 2026-10-22 時点の Vite 公式ドキュメントに基づきます。検証は Vite 8.3.5 で、Linux の使い捨て環境でのみ行いました。
更新情報やちょっとしたTipsは X(@supportapp3_lab)で発信しています。