1Password CLI × Claude Code で .env を Vault化する完全ガイド — op run・シェル統合・MCP の3パターン
.envファイルの平文管理から脱却するための実践ガイド。1Password CLIを使ったop runによる実行時注入・fish/zshシェル統合・MCPサーバー経由の3パターンを解説。Claude Code SubAgentsとの組み合わせ、Doppler/Infisicalとの比較まで網羅。
エンジニアのゆとです。
告白しておく。
去年の今頃、僕のプロジェクトルートには .env ファイルが7個あった。OPENAI_API_KEY=sk-... が平文で書いてあって、.gitignore に入れてあるから大丈夫——そう思っていた。
Gitにコミットする事故は起きなかった。でも Claude Code を本格的に使い始めて、考え方が変わった。
AIエージェントが自律的にファイルを読み、コマンドを実行する世界では「.envを.gitignoreで守る」は最低限のラインにもなってない。Claude Codeがプロジェクト全体をコンテキストに取り込む時、.envはまるごとLLMに渡っている。SubAgentsが並列で動く構成では、どのエージェントがどのクレデンシャルにアクセスしたか追跡できない。
この記事では、1Password CLI を使って .env の平文管理から完全に脱却する方法を3つのパターンで解説する。
なぜ .env 平文管理は「今」特に危ないのか
.envリポジトリ漏洩の話は何年も前からある。それでもまだ多くのプロジェクトが .env 平文運用なのは「リスクをわかった上で許容している」からだ。でも Claude Code 時代はその計算式が変わっている。
具体的に何が問題か、整理しておく。
1. AIエージェントのコンテキスト汚染Claude Code は CLAUDE.md で除外を設定しない限り、プロジェクトルートの .env を読む。/permissions で read:.env を拒否することはできるが、デフォルトでは開いている。つまり「APIキーがLLMのコンテキストに乗る」状態が継続する。
Claude Code でSubAgentsを使うと、親エージェントが子エージェントを起動してタスクを並列化できる。このとき、各子エージェントが「どのファイルにアクセスしたか」のログはデフォルトでは残らない。.envに平文でAPIキーがあれば、SubAgentが触れた可能性を排除できない。
3. シェル履歴への漏洩export OPENAI_API_KEY=sk-xxx をシェルで実行した瞬間、~/.zsh_history にキーが残る。tmuxのスクロールバックにも残る。macOSのクラッシュレポートにプロセス環境変数が含まれることもある。
APIキーが漏洩した可能性があるとき、.envを使っている場合はどのファイルにどのキーが書いてあるかを手動で探す必要がある。1Password Vaultに集約していれば、Vault側でキーをローテーションすれば全プロジェクトに即時反映される。
1Password CLI とは — 5分セットアップ
1Password CLI(op コマンド)は、1Password Vault に保管されたシークレットをコマンドラインから取得・注入するためのツール。GUI不要で動くので、CI/CDや自動化スクリプトで使いやすい。
インストール(macOS):
brew install 1password-cli
インストール後、1Passwordデスクトップアプリとの連携を有効化する。これにより、op コマンド実行時にデスクトップアプリが認証を代行してくれる(Touch IDで解除できる)。
# 1Passwordアプリ側で「CLIとの統合を許可」を有効化後
op signin
動作確認:
op vault list
# Vault一覧が表示されればOK
シークレットの保存:
CLIからアイテムを作成する場合:
op item create \
--category login \
--title "OpenAI API Key" \
--vault "Development" \
--field "api_key[password]=sk-your-actual-key"
GUIで作ってCLIで参照するのが実際には楽。アイテムへの参照パスは op://Vault名/アイテム名/フィールド名 の形式になる。
op://Development/OpenAI API Key/api_key
パターンA: op run で実行時注入
最もシンプルな統合方法。.env ファイルを残しつつ、値の部分だけを op:// 参照に置き換える。
設定ファイルの変換:
従来の .env:
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxx
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
.env.vault として書き直す(このファイルはGitにコミットしてOK):
OPENAI_API_KEY=op://Development/OpenAI API Key/api_key
ANTHROPIC_API_KEY=op://Development/Anthropic API/api_key
GITHUB_TOKEN=op://Development/GitHub Token/credential
実行時の使い方:
# 従来
python my_script.py
# op run 経由
op run --env-file=.env.vault -- python my_script.py
op run が .env.vault を読み込み、op:// 参照を実際の値に解決してから子プロセスに環境変数として渡す。シークレット本体はディスクに書き込まれない。
Claude Code との統合:
Claude Code を起動するときも同じ原理で使える:
op run --env-file=.env.vault -- claude
または package.json に組み込む:
{
"scripts": {
"ai": "op run --env-file=.env.vault -- claude",
"dev": "op run --env-file=.env.vault -- npm run dev"
}
}
これで npm run ai で起動した Claude Code セッションには、Vaultから取得したAPIキーが環境変数として渡される。
ハマりポイント: --no-masking の罠
op run はデフォルトでシークレット値をログ出力でマスクする。これは安全だが、デバッグ時に echo $OPENAI_API_KEY が [MASKED] と表示されて困ることがある。
マスクを外したい場合(ローカルデバッグ時のみ推奨):
op run --no-masking --env-file=.env.vault -- env | grep API_KEY
本番環境では絶対に --no-masking を使わない。
パターンB: シェル統合(fish/zsh)
op run を毎回書くのが面倒な場合、シェルのプラグインで透過的に解決する方法がある。
fish shell の場合:
# fish用1Password CLIプラグインをインストール
op plugin init fish
これにより、特定のコマンド(gh, aws, kubectl 等)を実行すると自動的にVaultからクレデンシャルを取得してくれる。
fish の config.fish に追加:
# 1Password CLI 統合
if test -f ~/.config/op/plugins.sh
source ~/.config/op/plugins.sh
end
zsh の場合:
# zsh用プラグインの初期化
op plugin init zsh
~/.zshrc に追加:
# 1Password シェル統合
source ~/.config/op/plugins.sh
動作確認:
# gh コマンドが自動的にGitHub Tokenを1Passwordから取得するか確認
gh auth status
Touch IDが要求されてVaultアンロック後にコマンドが実行されれば成功。
カスタム関数でのラップ:
プロジェクト固有の設定は ~/.zshrc にラッパー関数として定義しておくと便利:
# Claude Codeをプロジェクトのシークレット付きで起動
function claude-dev() {
local project_dir=${1:-$(pwd)}
local env_file="${project_dir}/.env.vault"
if [[ -f "$env_file" ]]; then
op run --env-file="$env_file" -- claude "${@:2}"
else
echo "⚠️ .env.vault not found. Running without secret injection."
claude "${@:2}"
fi
}
これで claude-dev コマンドを実行するだけで、.env.vault があれば自動的にVault注入付きで起動する。
シェル統合の限界:
シェル統合は「インタラクティブセッションで快適に使う」ためのもの。LaunchAgentやcronで自動実行するスクリプトには向かない。そういう場合は後述の op run を明示的に使うか、パターンCのMCP経由にする。
パターンC: MCP サーバー経由
Claude Codeセッション内でシークレットを動的に取得したい場合は、1Password MCPサーバー経由が適している。MCPサーバー自体の詳細なセットアップは別記事で解説しているので、ここではCLI統合との使い分けに絞って書く。
op run との違い:
| 観点 | op run | 1Password MCP |
|---|---|---|
| シークレット取得のタイミング | プロセス起動時(静的) | Claude Codeセッション中(動的) |
| 用途 | スクリプト・バッチ処理 | インタラクティブなエージェント操作 |
| アクセス粒度 | .env.vaultで指定したもの全部 | MCPツール経由で個別取得 |
| 監査ログ | op CLIのログ | 1Password Businessのイベントログ |
どちらを使うか:
claude my_script.pyのように特定スクリプトを実行させる →op run- セッション中にClaudeが「このAPIキーが必要だ」と判断して取得する → MCP
実運用では両方を組み合わせることになる。セッション起動は op run で基本的なシークレットを渡し、セッション中の動的な取得は MCPで補完するイメージ。
実践チュートリアル: Claude Code SubAgents との組み合わせ
単一の Claude Code セッションより、SubAgents を使った並列処理の場合にシークレット管理の設計が重要になる。
SubAgentsへのシークレット伝播:
Claude Code の SubAgents(Task tool 経由で起動されるサブエージェント)は、親プロセスの環境変数を継承する。つまり op run で親プロセスを起動していれば、SubAgentsも自動的に同じシークレットを使える。
# この1コマンドで起動すれば、配下のSubAgentsまで全部カバーされる
op run --env-file=.env.vault -- claude
プロジェクト別Vault分離:
複数プロジェクトを掛け持ちしている場合、Vault単位で分離しておくと監査・ローテーションが楽になる。
1Password Vault 構成例:
├── Development(共通開発ツール)
│ ├── GitHub Token
│ ├── Anthropic API Key
│ └── OpenAI API Key
├── Project-Alpha(案件A専用)
│ ├── Client DB Password
│ └── Staging Credentials
└── Project-Beta(案件B専用)
└── Production API Keys
.env.vault をプロジェクトルートに置き、参照するVaultを明示する:
# Project-Alpha/.env.vault
ANTHROPIC_API_KEY=op://Development/Anthropic API/api_key
DB_PASSWORD=op://Project-Alpha/Client DB/password
STAGING_TOKEN=op://Project-Alpha/Staging Credentials/token
CLAUDE.md でのシークレット保護設定:
.env.vault 自体はコミットしてOKだが、解決された実際の値をClaude Codeが扱わないように CLAUDE.md で明示しておく:
# セキュリティポリシー
- 環境変数のAPIキーを出力・ログ・コードにハードコードしない
- .envファイルが存在してもコンテキストに読み込まない
- シークレットの解決は op run に任せる
これは完全な保護ではないが、Claude Codeが「うっかり値をechoする」ようなパターンを減らす効果がある。
ハマりどころ・トラブルシュート
実際に使っていて引っかかったポイントをまとめた。
1. op run が遅い
Vault解決のたびにTouchIDが要求されてセッションがタイムアウトする問題。
原因: 1Passwordのセッション有効期限が短い(デフォルト10分)。
対処: op.conf または環境変数で延長する:
# セッション有効期限を120分に設定(`~/.config/op/config` または環境変数)
export OP_SESSION_TIMEOUT=7200
または 1Password アプリ設定の「セキュリティ > 自動ロック」でロック時間を延長する。
2. CI/CD 環境で使う場合
GitHub Actions など、デスクトップアプリがない環境では op signin がインタラクティブに動かない。
対処: サービスアカウントトークンを使う:
# サービスアカウントトークンを生成(1Password Business が必要)
op service-account create "GitHub Actions" --expires-in 30d \
--vault Development:read
# 生成されたトークンをGitHub Secretsに追加
# GitHub Actions workflow:
- name: Run with 1Password secrets
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
run: |
op run --env-file=.env.vault -- python my_script.py
サービスアカウントは特定Vaultへの読み取り権限のみ付与できるので、最小権限の原則を守りやすい。
3. op:// の参照パスが見つからない
[ERROR] 2026/04/25 op: could not find item "OpenAI API Key" in vault "Development"
スペース・大文字小文字・Vault名の不一致が原因になることが多い。確認コマンド:
# Vault内のアイテム一覧
op item list --vault Development
# 特定アイテムのフィールド一覧
op item get "OpenAI API Key" --vault Development --format json | jq '.fields[].label'
スペースを含む名前はダブルクォートで囲む必要がある。
4. macOS 以外の環境(WSL2 / Linux)
WSL2ではTouchID連携が使えないため、op signin が毎回マスターパスワードを要求する。
対処: WSL2 から macOS 側の 1Password デスクトップアプリと連携するには、1Password for Linux を使うか、WSL2 側でも op signin のセッション管理をシェルに組み込む:
# .bashrc / .zshrc に追加
function op-refresh() {
eval $(op signin)
}
# セッション開始時に一度だけ実行
他ツールとの比較
1Password CLI と同じ用途で使えるツールを整理しておく。
Doppler
Doppler は「シークレット管理のクラウドSaaS」で、doppler run -- コマンド という使い方は op run と似ている。
違い: Doppler は SaaS なのでクレデンシャルがDoppler社のサーバーに保存される。1Password はVaultをローカル(または1Passwordのサーバー)に保持しつつ、CLIが橋渡しをする構造。「データをどこに置くか」のポリシーで選ぶことになる。
フリーランスやソロ開発者で既に1Passwordを使っている場合、追加コストなしに使えるのが1Password CLIの強み。Dopplerの無料プランはプロジェクト数制限がある。
Infisical
OSSのシークレット管理ツール。セルフホストができるので「どのクラウドにも置きたくない」ケースに向く。
違い: 使い始めのセットアップがやや重い。infisical run -- コマンド の動作原理は op run と同じ。エンタープライズでのセルフホスト要件がある場合に検討する。
HashiCorp Vault
エンタープライズ向けのシークレット管理基盤。動的シークレット生成(使うたびに新しいクレデンシャルを発行して後で失効)などが強力。
違い: 個人開発やフリーランス規模では過剰。セットアップ・運用コストが高い。「とりあえず.envをVault化したい」のであれば1Password CLIで十分。
まとめると:
- 個人 / フリーランス / 小チームで既に1Passwordを使っている → 1Password CLI一択
- チームでSaaSシークレット管理を導入したい → Doppler を評価
- セルフホスト要件あり → Infisical
- エンタープライズ大規模 → HashiCorp Vault
まとめ
.env 平文管理から 1Password CLI への移行は、設定ファイルを .env.vault に変換して op run を前に付けるだけで始められる。
段階的な移行ステップ:
brew install 1password-cliでCLIをインストール- 既存の
.envを.env.vaultに変換(op://参照に置き換え) op run --env-file=.env.vault -- claudeで Claude Code を起動- シェル統合(zsh/fish)を設定して日常的な操作を楽にする
- 必要であれば MCP サーバーと組み合わせてセッション内での動的取得を追加
「全部一気にやらなくていい」がポイントで、op run を前に付けるだけなら既存のワークフローをほとんど変えずに始められる。
Claude Code を中心とした AI 開発に本腰を入れるなら、クレデンシャル管理の土台を固めておく価値がある。SubAgents が並列で動く構成が増えてくると、後から「あのエージェントがどのAPIキーにアクセスしたか」を追跡できない状態は地味にストレスになる。
関連記事
記事が見つかりません:
記事が見つかりません:
記事が見つかりません:
記事が見つかりません: