◉ANTIGRAVITY LABEN
記事一覧/アプリ開発
▣ アプリ開発/2026-10-18中級

エージェントに設定を任せる前に、.env* を無視していても .dev.vars が素通りだと分かり、点検スクリプトで先に塞ぎました

Cloudflare Workers のリポジトリを自分で点検すると、.gitignore の .env* では .dev.vars を守れていませんでした。git check-ignore を使い、エージェントに触らせる前の秘密の置き場を確かめる Python スクリプトの全文を残します。

Cloudflare Workers8wrangler.dev.vars.gitignore秘密情報3Python20個人開発111

✦ プレミアム記事

個人開発で運用している Web サイトの設定を、Antigravity のエージェントに整理してもらおうとしたときのことです。依頼文を打つ手前で、ふと手が止まりました。このリポジトリのどこに秘密が置かれているのか、自分の記憶だけで答えようとしていたからです。

.gitignore には .env* と書いてあります。だから大丈夫だろう、と思っていました。そこで、思い込みを確かめるために、点検用のスクリプトを書きました。

最初に結論をお伝えします。.env* は .env 系しか守りません。Cloudflare Workers のローカル用ファイル .dev.vars は、そのパターンの外にあります。 私自身のリポジトリでも、実際にそうなっていました。

点検の対象は三つだけにしました

エージェントに設定を任せる前に、私が知りたかったのは次の三点です。

  • コードが読んでいる環境変数の名前を知りたいです(値は読みません)
  • 平文でデプロイされる wrangler.toml の [vars] に、秘密らしいものが紛れていないかを確かめたいです
  • ローカル用の秘密ファイルが、git の目から本当に隠れているかを確かめたいです

三つ目が肝心です。.gitignore の書き方を目で追って判断すると、パターンの読み違いがそのまま見落としになります。そこで、判定は git 自身に任せることにしました。git check-ignore は、指定したパスが無視対象かどうかを、ファイルが実在しなくても答えてくれます。

点検スクリプトの全文

Python 3 の標準ライブラリだけで動きます。終了コードは 0 が問題なし、1 が要対応、2 が使い方の誤りです。

#!/usr/bin/env python3
"""Workers 設定の「秘密の置き場」点検。エージェントにリポジトリを渡す前に実行する。
終了コード: 0=問題なし / 1=要対応あり / 2=使い方の誤り
"""
import re, subprocess, sys
from pathlib import Path
 
repo = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
if not (repo / ".git").exists():
    print("git リポジトリではありません:", repo); sys.exit(2)
 
def git(*a):
    return subprocess.run(["git", "-C", str(repo), *a], capture_output=True, text=True)
 
problems, notes = [], []
 
# 1) コードが読む環境変数の名前を集める(値は読まない)
names = set()
for p in git("ls-files", "*.ts", "*.tsx", "*.js", "*.mjs").stdout.split():
    try:
        names |= set(re.findall(r"process\.env\.([A-Z][A-Z0-9_]+)", (repo / p).read_text(errors="ignore")))
    except OSError:
        pass
 
# 2) wrangler.toml の [vars](平文でデプロイされる側)に秘密らしい名前がないか
cfg = repo / "wrangler.toml"
plain = {}
if cfg.exists():
    # 3.10 でも動くよう、[vars] 節だけを素朴に読む(tomllib は 3.11 以降)
    sect = re.search(r"^\[vars\]\s*$(.*?)(?=^\[|\Z)", cfg.read_text(), re.M | re.S)
    for line in (sect.group(1).splitlines() if sect else []):
        m = re.match(r'\s*([A-Za-z0-9_]+)\s*=\s*"(.*)"\s*(#.*)?$', line)
        if m:
            plain[m.group(1)] = m.group(2)
SECRETISH = re.compile(r"(SECRET|TOKEN|PASSWORD|PRIVATE|API_?KEY|WEBHOOK)", re.I)
for k, v in plain.items():
    if SECRETISH.search(k) and not k.startswith("NEXT_PUBLIC_"):
        problems.append(f"[vars] に秘密らしい名前: {k}(wrangler secret put へ移す)")
    elif str(v).startswith(("sk_", "whsec_", "ghp_")):
        problems.append(f"[vars] の {k} が秘密の接頭辞で始まっています")
 
# 3) 秘密を置くはずの変数が [vars] にないこと=Secret 側にある前提を確認
for n in sorted(names):
    if SECRETISH.search(n) and n not in plain:
        notes.append(f"{n}: コードは読むが [vars] には無い → Secret 側(ダッシュボード/secret put)にある想定")
 
# 4) ローカル用ファイルが ignore されているか(git の判定をそのまま使う)
for f in (".env", ".env.local", ".dev.vars", ".dev.vars.production"):
    r = git("check-ignore", "-q", f)
    if r.returncode != 0:
        problems.append(f"{f} が .gitignore の対象外(作った瞬間に git add -A で入る)")
 
# 5) すでに追跡されていないか
tracked = [p for p in git("ls-files").stdout.split() if re.search(r"(^|/)(\.env|\.dev\.vars)", p)]
for p in tracked:
    problems.append(f"追跡済み: {p}")
 
print(f"コードが読む環境変数: {len(names)} 個 / [vars]: {len(plain)} 個")
for n in notes: print("  ・", n)
for p in problems: print("  ✗", p)
print("結果:", "要対応 %d 件" % len(problems) if problems else "問題なし")
sys.exit(1 if problems else 0)

設計の理由を、順にお伝えします。

値を読まない。 変数の名前だけを集め、.env や .dev.vars の中身は開きません。点検のために秘密を画面やログへ出してしまっては、本末転倒だからです。

[vars] は名前で疑う。 [vars] の値は、設定ファイルにも管理画面にも平文で残ります。SECRET・TOKEN・WEBHOOK などを含む名前が見つかったら、wrangler secret put で Secret 側へ移す合図にしています。NEXT_PUBLIC_ で始まる名前は、もともと公開前提なので外しました。

無視の判定は git に聞く。 check-ignore -q は、無視対象なら 0、そうでなければ 1 を返します。.gitignore の細かな文法を自分で再実装しないので、読み違いが入り込みません。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦エージェントにリポジトリを開かせる前に、秘密がどこに置かれ、どれが git に入りうる状態なのかを、自分の環境で数分のうちに言い切れるようになります
✦「.env* を無視しているから大丈夫」という思い込みを、パターンを目で追わず git 自身の判定で確かめ、同じ見落としを繰り返さない形にできるようになります
✦wrangler.toml の [vars] に秘密を平文で置いてしまう事故を、コミットの前に終了コードで止められるようになります
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

▣ アプリ開発2026-09-19
見積書 PDF の自動化を一度にまとめて頼み、4 つに割り直すまで
受託の見積書を毎回コピーして作っていた手作業を、Antigravity に頼んで小さな PDF 出力ツールへ移した記録です。最初に全部をまとめて頼んで作り直すことになった理由と、データ・計算・見た目・保存の 4 つに割って渡し直した順番を、動くコードと一緒に書き残します。
◈ Agents & Manager2026-10-16
エージェントに .zip を渡す前に、.git を除いて 29 MB を 15 MB にし、秘密情報の警告 28 件を見分けました
Antigravity アプリ v2.23.0 で .zip を添付できるようになりました。自分のリポジトリを実際に固めて .git の重さと秘密情報検査の誤検知を数え、除外・検査・マニフェストを一度に行う Python スクリプトの全文を残します。
▣ アプリ開発2026-10-15
immutable を付けた資産を同じ URL で差し替えたら、利用者のブラウザだけが古いままでした
長期キャッシュを付けた JS を同じ URL で差し替えても、CDN をパージしてもブラウザは取り直しません。同じ URL・no-cache・クエリ付き・内容ハッシュ名の 4 方式を Chromium で並べて比べ、ビルドの最後にハッシュを付ける完全なスクリプトまでをまとめました。