結論
- Python の
python-dotenvのload_dotenv()は、引数なしだとスクリプトのあるフォルダから上の階層へ順に.envを探し、最初に見つかった1つを読みます。 - そのため、プロジェクトに
.envが無くても、親フォルダに置いた別の.envの値がos.environに入りました(2026-11-04、python-dotenv 1.2.4・Python 3.13.5、ダミー値で実機確認)。 - Node.js の
node --env-file=.envは、指定したパスだけを読み、親フォルダは探しませんでした(Node.js v24.22.1)。 - AIエージェントにプロジェクトのフォルダだけを渡しても、そこで動かした Python スクリプトが上の階層の
.envを読むことがあります。親フォルダに.envが無いかは、下の1行で確かめられます。
この記事は「AIエージェントと秘密情報」の検証シリーズです。新しい検証は X(@supportapp3_lab)でもお知らせしています。
試してみる(親フォルダの .env を探す)
プロジェクトのフォルダで実行すると、そこから / までの各階層にある .env のパスだけを出します(中身は表示しません)。
d=$PWD; while [ "$d" != / ]; do ls -1 "$d"/.env 2>/dev/null; d=$(dirname "$d"); done
2つ以上出たら、Python スクリプトの置き場所によっては上の .env が読まれることがあります。
公式ドキュメントの説明
python-dotenv の README には、load_dotenv() は既定で次のように動くと書かれています。
Look for a
.envfile in the same directory as the Python script (or higher up the directory tree).
Not override existing environment variables (override=False).
ソースの find_dotenv() の説明も「Search in increasingly higher folders for the given file」です。基準はカレントディレクトリではなくスクリプトのある場所です(対話モードなどでは usecwd の扱いが変わります)。
実機で確かめたこと
用意したフォルダ(値はすべてダミー)
demo/
├── .env # DUMMY_PARENT_KEY=parent-dummy-0000
└── project/
├── .env # DUMMY_PROJ_KEY=proj-dummy-1111
└── app/sub/run.py
run.py は次のとおりです。
import os
from dotenv import load_dotenv, find_dotenv
print("find_dotenv():", find_dotenv(usecwd=False))
print("load_dotenv():", load_dotenv())
print("DUMMY_PROJ_KEY =", os.getenv("DUMMY_PROJ_KEY"))
print("DUMMY_PARENT_KEY =", os.getenv("DUMMY_PARENT_KEY"))
1. project/.env がある場合
find_dotenv(): /tmp/demo/project/.env
load_dotenv(): True
DUMMY_PROJ_KEY = proj-dummy-1111
DUMMY_PARENT_KEY = None
2階層上の project/.env が見つかり、そこで探すのをやめました。さらに上の demo/.env は読まれていません。
2. project/.env を消した場合
find_dotenv(): /tmp/demo/.env
load_dotenv(): True
DUMMY_PROJ_KEY = None
DUMMY_PARENT_KEY = parent-dummy-0000
プロジェクトの外にある demo/.env の値が入りました。エラーも警告も出ません。
3. 別のフォルダから実行しても同じ
.env の無い /tmp に移動してから python /path/to/run.py で実行しても、1と同じく project/.env が読まれました。探す起点はスクリプトの場所です。
4. すでにある環境変数は上書きしない
$ DUMMY_PROJ_KEY=from-shell python run.py
DUMMY_PROJ_KEY = from-shell
README のとおり、既定の override=False では、シェル側の値が優先されました。
5. dotenv_path を相対パスで書くと、カレントディレクトリ基準になる
app/sub で load_dotenv(dotenv_path="./.env") を実行すると False が返り、どちらの値も入りませんでした。この場合は上の階層を探しません。
6. Node.js の --env-file は親を探さない
$ node --env-file-if-exists=.env -e 'console.log(process.env.DUMMY_PROJ_KEY, process.env.DUMMY_PARENT_KEY)'
.env not found. Continuing without it.
undefined undefined
$ node --env-file=.env -e ''
node: .env: not found
同じ app/sub で実行すると、--env-file は指定したパスだけを見て、見つからなければエラーになりました。Node.js の公式ドキュメントにも、環境とファイルの両方にある変数は環境の値が優先されると書かれています。
AIエージェントとの関係(ここからは推測を含みます)
- Claude Code の公式ドキュメント(Permissions)には、Read・Edit の deny ルールは組み込みのファイル操作や Bash の
catなどには効くが、「Python や Node のスクリプトが自分でファイルを開く」ような間接的な読み取りには効かない、と書かれています。load_dotenv()が親フォルダの.envを開くのはこの形なので、deny 設定では止まらないと考えられます(推測。今回は Claude Code 経由では試していません)。公式はすべてのプロセスを止めたい場合にサンドボックスを案内しています。 - 一方 Codex の permission profile の deny はサンドボックスで効くので、記事7では
codex sandbox -- cat .envが Permission denied になりました。ただし deny の対象を:workspace_rootsの下に書いた場合、その外にある親フォルダの.envは対象外になると考えられます(推測、未確認)。 - 読み込まれた値はそのプロセスの環境変数になるので、ログやエラー出力に出せば画面に出ます(/proc/PID/environ の記事で確かめたとおり、環境変数は同じユーザーから読めます)。
- 親フォルダに
.envを置く使い方(複数プロジェクトで共有するなど)をしている場合、プロジェクトのフォルダだけを点検しても見落とします。
対策
- 上の1行で、親フォルダに
.envが無いか確かめる。 - Python では、読む
.envのパスを決めて書く。スクリプトの場所を基準にするなら次のようにします。指定したファイルが無ければfrom pathlib import Path from dotenv import load_dotenv load_dotenv(dotenv_path=Path(__file__).resolve().parent / ".env")Falseが返るので、上の階層を読みにいきません。 - 共有用の
.envをホームなど上の階層に置かない。
ai-secret-check で見られる範囲
筆者が作った読み取り専用の CLI ai-secret-check scan は、起動したフォルダより下の .env 系ファイル名を探します。今回の demo/project で実行すると、project/.env は検出しましたが、親の demo/.env は対象外でした。project/.env を消した状態では「読めそうなファイルは見つかりませんでした。」と出ます。親フォルダは、上の1行で別に確かめてください。
npx github:supportapp3-lab/ai-secret-check scan
リポジトリ: https://github.com/supportapp3-lab/ai-secret-check (Node.js 20以上、MIT。値は表示しません)
参考
- python-dotenv(README)
- python-dotenv
find_dotenv(src/dotenv/main.py) - Configure permissions – Claude Code Docs
- Node.js Command-line API(--env-file)
※ 2026-11-04 に、Debian の使い捨てフォルダとダミー値で確かめました(python-dotenv 1.2.4、Python 3.13.5、Node.js v24.22.1)。出力中の /tmp/demo は実際のパスを置き換えたものです。
更新情報やちょっとしたTipsは X(@supportapp3_lab)で発信しています。