API キーやデータベースのパスワードを、ついソースコードに直接書いてしまうことがあります。
動くものを早く作りたい場面ほど起きやすく、一度コミットした鍵は、消したつもりでも履歴に残り続けます。
CI/CD やクラウドへのデプロイを組み始めて、「この鍵、どこに置けばいいのか」と手が止まったことはありませんか?
置き場所の選択肢は、環境変数からシークレットストア、そもそも鍵を持たない方式まで段階的に並んでいます。
私も DevOps を学んでいる途中なので、OWASP の資料や GitHub の公式ドキュメントを足場に、それぞれの向き不向きを整理しました。
シークレットはなぜコードに書いてはいけないのか
ここでいうシークレットとは、システムに入るための鍵にあたる情報のことです。
OWASP のSecrets Management Cheat Sheetは、API キー、データベースの認証情報、SSH 鍵、証明書、トークンなどを代表例に挙げています。
設定をコードから切り離す考え方でよく引かれるのが、The Twelve-Factor App の「設定」の章です。
同章が示す目安は、認証情報を漏らさずに、コードベースを今すぐオープンソースにできるかどうかというリトマス試験です。
コードに鍵が 1 つでも書かれていれば、この試験には通りません。
消したつもりでも履歴に残る
直書きが厄介なのは、次のコミットで消しても、過去のコミットには鍵が残る点にあります。
手元の Git 2.48.1(Windows の Git Bash)で、ダミーの値を入れた .env をコミットしたあとに追跡を外す流れを試すと、次のようになりました。
$ git ls-files
.env
.gitignore
$ git check-ignore -v .env # 追跡済みのファイルは無視の対象にならない(何も出ない)
$ git rm --cached .env # 追跡だけ外す(手元のファイルは残る)
rm '.env'
$ git commit -q -m "untrack .env"
$ git check-ignore -v .env
.gitignore:1:.env .env
$ git grep -n "dummy-token" $(git rev-list --all --abbrev-commit)
c88c51c:.env:1:API_TOKEN=dummy-token-5678
0da0b60:.env:1:API_TOKEN=dummy-token-5678
後から .gitignore に書き足しても、追跡済みのファイルは無視の対象になりません。
追跡を外したあとでも、全コミットを探せば元の値が出てきます。
GitGuardian の年次レポートThe State of Secrets Sprawl 2026によると、2025 年に公開 GitHub のコミットへ新たに書き込まれたシークレットは約 2,865 万件(前年比 34% 増)でした。
2022 年に有効と確認された鍵の 64% が、2026 年 1 月時点でもまだ使える状態だったという数字も並んでいます。
漏れること自体より、漏れたまま無効化されないことが被害を長引かせている、と読めるのではないでしょうか。
置き場所の選択肢を段階で比べる
コードに書かないと決めたら、次は置き場所を選ぶ番です。
環境変数と .env ファイル
The Twelve-Factor App が勧めているのは、コードを変えずにデプロイごとに差し替えられる環境変数に設定を入れる方法です。
ローカル開発では、値を .env ファイルに書いて .gitignore で除外する形がよく使われます。
私自身も、このブログへの自動投稿に使う WordPress の認証情報は、Git の管理外に置いたファイルから読むようにしています。
ただし OWASP のチートシートは、コンテナの節で環境変数はすべてのプロセスから読めることが多く、ログやダンプに紛れ込むおそれがあると注意しています。
環境変数は「コードに書かない」ための第一歩で、保管や配布までは面倒を見てくれないのです。
シークレットストア
チームやサービスが増えてくると、鍵を 1 か所に集める専用の保管庫が候補に上がります。
OWASP のチートシートが勧めているのも、組織として一元化したシークレット管理の仕組みを持つことです。
保管庫を使えば、アクセスの制限、誰がいつ読んだかの記録、定期的な差し替え(ローテーション)を 1 か所でそろえられます。
代表例は HashiCorp Vault や、各クラウドの管理サービス(AWS の Secrets Manager など)です。
望む状態をすべて Git に置く GitOps では、こうした外部の保管庫から鍵を取り込む形がとくに効いてきます。
GitOps でシークレットをどう扱うかという論点は、こちらの記事でも触れました。
CI/CD ツールのシークレット機能
デプロイのパイプラインで使う鍵は、CI/CD ツール側の保管機能に置くのが一般的です。
GitHub Actions のシークレットは、Libsodium の sealed box という方式でGitHub に届く前の時点で暗号化されます。
環境(environment)単位のシークレットなら、レビュアーの承認が下りるまでジョブから読めないようにもできます。
ログに出た値は自動で伏せ字になりますが、公式ドキュメント自身が「伏せ字は保証されない」と書いている点は覚えておいてください。
いちばん安全なのは「鍵を持たない」こと
ここまでの選択肢は、どれも「長く使える鍵をどこかに置く」という点では変わりません。
OWASP のチートシートは、可能なら使うたびに発行されて自動で失効する「動的なシークレット」を選ぶよう勧めています。
CI/CD でこの考え方を形にしたのが、OpenID Connect(OIDC)による認証です。
GitHub のOpenID Connect の解説によると、ワークフローはジョブごとに発行されるトークン(JWT)をクラウドに提示し、そのジョブの間だけ有効な短命のアクセストークンを受け取ります。
クラウドの長期の鍵を GitHub に複製しなくて済むことが、同ドキュメントの挙げる最初の利点です。
私の個人開発でも、GitHub Actions から AWS へのデプロイはこの方式にしてあり、長期のアクセスキーを GitHub には置いていません。
ワークフロー側は、例えば次のような書き方になります(AWS 向けの一部を抜き出した書式の例)。
permissions:
id-token: write # OIDC のトークンを要求するための権限
contents: read
# 略
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }} # 渡すのは鍵ではなく、引き受けるロールの指定
GitHub の OIDC リファレンスによれば、id-token: write はトークンの取得だけを許す設定で、ほかのリソースへの書き込み権限は与えません。
置き場所を工夫するより、置く鍵そのものを減らすほうが、漏れたときの被害を根元から小さくできます。
漏れる前提で備える
どれだけ気をつけても、鍵が漏れる可能性はゼロになりません。
プッシュの前に止める
GitHub のプッシュ保護は、シークレットを含むプッシュをリポジトリに届く前にブロックする機能です。
個人アカウント向けのプッシュ保護は既定で有効になっていて、公開リポジトリへの鍵のプッシュを止めてくれます。
すでにプッシュされたものを見つける側にはシークレットスキャンがあり、全ブランチの Git 履歴までさかのぼって鍵の直書きを探します。
OWASP のチートシートも、コミット前のフック(pre-commit)や CI での検出を勧めていました。
漏れたら「消す」より先に「無効化」
鍵を漏らしたとき、真っ先にやりたくなるのは履歴からの削除かもしれません。
しかし GitHub の機密データの削除に関するドキュメントは、最初の手順としてその鍵を無効化するか、差し替えることを挙げています。
履歴を書き換えても、既存のクローンやフォーク、参照しているプルリクエストには古いコミットが残りうるからです。
すぐに差し替えられる状態を普段から作っておくことが、いちばん効く備えになります。
最後に
シークレット管理の選択肢は、書かない、分けて置く、集めて管理する、そもそも持たないの 4 段階で整理できそうです。
どの段階を選んでも、漏れる前提で検出と差し替えの手順をそろえておくことは欠かせません。
秘密情報をコードに書かないことは、インフラをコードで管理するときの約束ごとのひとつでもあります。
IaC の基本と、つまずきやすいところは、こちらの記事で整理しました。
私もまずは、自分のリポジトリで長く生きている鍵がどこに何本あるかを棚卸しするところから始めたいと思います。
以上です。













コメントを残す