結論
-
.envをそのままsourceしただけでは、シェル変数には入っても export されないので、子プロセス(別のbashや Python)からは見えません(2026-11-07、bash 5.2.37・ダミー値で実機確認)。 - よく紹介される
set -a; source .env; set +aを使うと、代入した変数がすべて export 扱いになり、子プロセスにも API キーや DB パスワードが渡ります。 -
.envの各行に最初からexportを書いてある場合も、set -aなしで同じように子へ渡ります。 - AIエージェントに「
.envを読んでコマンドを実行して」と頼むと、set -a付きの source が使われることがあります(推測。今回はエージェント経由では試していません)。スクリプトに残っていないかは、下の1行で探せます。
この記事は「AIエージェントと秘密情報」の検証シリーズです。新しい検証は X(@supportapp3_lab)でもお知らせしています。
試してみる(set -a や source .env が残っていないか)
プロジェクトのフォルダで実行すると、set -a / set +a / allexport / source と .env が近い行のファイル名と行番号を出します。
grep -rnE --include='*.sh' --include='*.bash' --include='*.zsh' \
'set -a|set \+a|allexport|source[[:space:]]+.*\.env|\.[[:space:]]+.*\.env' .
出てきたスクリプトが、そのあと別コマンドや Python を起動していれば、鍵が子プロセスの環境に入ります。
.env 系のファイル名だけ先に見たい場合は、読み取り専用の CLI もあります。
npx github:supportapp3-lab/ai-secret-check scan
リポジトリ: https://github.com/supportapp3-lab/ai-secret-check
公式の説明
bash の help set(bash 5.2.37)には次のように書かれています。
-a Mark variables which are modified or created for export.
オプション名では allexport と同じです。つまり set -a のあいだに作られた・書き換えられた変数は、すべて export 属性が付きます。set +a でそのマークは止まりますが、すでに付いた export 属性は外れません(実機で確認)。
実機で確かめたこと
値はすべてダミーです。箱に入っている本物のトークンが混ざらないよう、env -i で最小限の環境から起動して試しました。
.env(export なし):
API_KEY=sk-dummy-0000-not-real
DB_PASSWORD=dummy-db-pass-1111
.env_export(各行に export):
export API_KEY=sk-dummy-export-2222
export DB_PASSWORD=dummy-db-export-3333
1. source だけ(set -a なし)→ 子には渡らない
set +a
source ./.env
echo "shell: API_KEY=${API_KEY-<empty>}"
printenv | grep -E '^(API_KEY|DB_PASSWORD)=' || echo "(none)"
# 子プロセス
env | grep -E '^(API_KEY|DB_PASSWORD)=' || echo "(none in child)"
declare -p API_KEY
shell: API_KEY=sk-dummy-0000-not-real
(none)
(none in child)
declare -- API_KEY="sk-dummy-0000-not-real"
シェル変数としては入りますが、declare --(export なし)なので、printenv にも子の env にも出ません。
2. set -a; source; set +a → 子にも渡る
set -a
source ./.env
set +a
printenv | grep -E '^(API_KEY|DB_PASSWORD)='
env | grep -E '^(API_KEY|DB_PASSWORD)='
declare -p API_KEY
DB_PASSWORD=dummy-db-pass-1111
API_KEY=sk-dummy-0000-not-real
DB_PASSWORD=dummy-db-pass-1111
API_KEY=sk-dummy-0000-not-real
declare -x API_KEY="sk-dummy-0000-not-real"
declare -x になり、子プロセスの環境にも同じ値が入りました。
3. Python の子プロセスでも同じ
set -a; source ./.env; set +a
python3 -c 'import os; print(os.environ.get("API_KEY","<missing>"))'
sk-dummy-0000-not-real
一方、set -a なしで source したあとの Python では <missing> でした。
4. .env に最初から export がある場合
set +a
source ./.env_export
printenv | grep -E '^(API_KEY|DB_PASSWORD)='
DB_PASSWORD=dummy-db-export-3333
API_KEY=sk-dummy-export-2222
set -a なしでも、行頭の export で子に渡ります。
昨日の printenv 記事との違い
printenv / env の記事は、「すでに export された環境を一覧で出すと鍵が見える」話でした。今回は その手前で、なぜ export されるのか(set -a や行頭の export)を切り分けています。
-
source .envだけ … シェル変数(子には渡らない) -
set -a; source .env; set +aまたはexport KEY=...… 環境変数(子にも渡る) - そのあと
printenv/env… 渡った値を一覧で出す
/proc/PID/environ の記事は「別プロセスから読める」話で、こちらは 自分が起動した子に渡してしまう話です。
AIエージェントとの関係(ここからは推測を含みます)
- 「
.envを読んでからテストを走らせて」と頼むと、エージェントがset -a; source .env; set +aやexport $(cat .env)系を使うことがあります(推測。今回はエージェント経由では未確認)。 - その場合、エージェントが起動した子コマンド(curl・テストランナー・別言語のプロセス)にも同じ鍵が渡ります(推測)。
- Codex の shell_environment_policy の記事は「親シェルがもともと持っている環境変数を渡す」話で、今回は source の瞬間に export してしまう話です。両方あると、子にも会話ログにも残りやすくなります(推測)。
対策
- 上の grep で、
set -a/source .envが残っていないか見る。 - どうしても
sourceするなら、必要な変数だけexport API_KEYのように明示する(全部まとめてset -aしない)。 - 一時的に
set -aした場合は、使い終わったらset +aしたうえで、渡したくない変数はunsetする(すでに子へ渡った分は戻らない)。 - AIに
.env読み込みを頼むときは、「子プロセスには渡さず、このシェル内だけで使う」と条件を付ける。
ai-secret-check について
ai-secret-check scan は、AIが読めそうな場所にある .env 系のファイル名を探します(中身は表示しません)。set -a の有無までは見ないので、上の grep とセットで見てください。
npx github:supportapp3-lab/ai-secret-check scan
リポジトリ: https://github.com/supportapp3-lab/ai-secret-check (Node.js 20以上、MIT)
参考
- bash
help set(-a/allexport) - bash
help declare(-x= export) - 関連: printenv / env で鍵が出る / /proc/PID/environ / Codex shell_environment_policy
※ 2026-11-07 に、Debian の使い捨てフォルダとダミー値で確かめました(bash 5.2.37)。env -i で隔離して実行しています。
更新情報やちょっとしたTipsは X(@supportapp3_lab)で発信しています。