Windows Hello+WSL構成で1Password上のシークレット情報をclaude codeに安全に読み込ませる
はじめに
複数のプロジェクトを並行運用していると、API キーが .env ファイル、~/.config/、~/.bashrc などに分散してコピーされていきます。漏洩時に「全コピー先を漏れなく書き換える」運用は、量が増えるほど次第に厳しくなっていきます。
この問題を解決するには、シークレットを1つの保管庫(Vault)に集約し、各プロジェクト・各コマンドはそこを「参照」するだけにする構造が必要です。本記事では、それを 1Password CLI とデスクトップアプリ統合だけで実現する方法を、ディレクトリに紐づかない形で整理します。
グローバルに参照できる仕組みを選ぶ
シークレット集約の解説では、direnv(ディレクトリ移動時に環境変数を注入するツール)を組み合わせる方法もよく紹介されます。便利な仕組みですが、私の運用では次のような場面で少し合わない感覚がありました。
- Claude Code を任意のディレクトリから起動したい: 作業中のディレクトリは流動的で、特定のプロジェクトディレクトリに必ず居るとは限らない
- OpenClaw のような常駐コマンドをワンライナーで起動したい: cd してから起動する手間は省きたい
-
シェルセッション全体でシークレットが使えてほしい: systemd ユーザーサービスから呼ばれるスクリプトでは
cdの前提が崩れる
プロジェクトごとに異なるシークレットを扱う場合は、.envrc のようなディレクトリ単位の仕組みが活きると思います。一方、複数プロジェクトで同じキーを使っている今回のような構成では、もう少しグローバルに参照できる仕組みのほうが扱いやすそうです。
求める要件と 1Password が満たす理由
シークレット管理に求める要件は3つです。
- 集約: 1ヶ所の変更で全プロジェクトに反映
- 自動運用: 寝ている間もスケジュール処理が動き続けられること
- ローカル平文/セッション平文を残さない: ディスク窃取単独で復号できないこと
1Password はこれらを次のように満たします。
| 要件 | 1Password での実現方法 |
|---|---|
| 集約 | Vault にアイテムとして保管。op read "op://Vault/Item/credential" で参照 |
| 自動 | デスクトップアプリの 自動ロック を「しない」に設定 + OS ログイン中は常時 unlock 状態 |
| 平文なし | 復号鍵は TPM(Trusted Platform Module、改ざん耐性のあるセキュリティチップ)/ Secure Enclave / PAM+TPM2 にバインド、ディスクに平文鍵が残らない |
アーキテクチャ
[Windows Hello / Touch ID / Linux PAM] ← OS 認証 = TPM/Secure Enclave
│
▼
[1Password デスクトップアプリ] ← OS ログイン中は常時 unlock
│
└─ Windows+WSL: Desktop ── Named Pipe ── op.exe ── WSL Interop ──> [WSL shell]
│
▼
op read / op run / op inject
│
▼
[Claude Code / OpenClaw / 任意のコマンド]
ディレクトリに紐づく仕組みを挟まず、op コマンド自体がどこからでも 保管庫 にアクセスできる点が肝になります。
Windows + WSL での導入手順
前提として、Windows Hello(PIN・指紋・顔のいずれか)が Windows 側でセットアップ済みである必要があります。未設定なら 設定 → アカウント → サインインオプション から、最低でも PIN を登録しておきます。
Step 1: 1Password 8 デスクトップアプリを Windows にインストール
- 1Password 公式サイトから 1Password 8 for Windows をインストール(14日間無料トライアルあり)
- アプリを起動して自分のアカウントでサインインし、一度マスターパスワードでロック解除しておきます
Step 2: デスクトップアプリで Windows Hello 連携 + 自動ロック設定
- デスクトップアプリの 設定 → セキュリティ を開く
- 「Windows Helloでロックを解除」 にチェック
- 自動ロック → システムがアイドル状態になるとロック: "しない"(寝ている間のバッチ処理を止めないため)
Step 3: Windows 側に op.exe をインストール
WSL2 の中ではなく、Windows 側に op.exe を入れる点に注意します。winget が手軽です。
PowerShell(管理者)で:
winget install AgileBits.1Password.CLI
新しい PowerShell ウィンドウを開き、op --version でバージョンが表示されればインストール成功です。
※環境変数が追加されている関係で、新しい PowerShell ウィンドウでないとコマンドが出てきてくれませんでした。
Step 4: デスクトップアプリで CLI 連携をON
- デスクトップアプリの 設定 → 開発者 を開く
- 「1Password CLI と連携」 にチェック → Windows Hello プロンプトが出る場合は認証
この時点で PowerShell から op vault list を実行すると、Windows Hello の認証プロンプトが出て 保管庫 一覧が返ってくるはずです。先に Windows 側で動くことを確認しておくと、後の切り分けが楽になります。
Step 5: 既存シークレットを 保管庫 に集約
新規アイテム の 認証情報 として登録します。命名規則を最初に固定すると、後の参照が一貫します。
私の場合は、1Passwordの保管庫に Business を作り、API認証情報を選択、API認証情報という名前を1カラム目の名前に変え、ユーザー名は空欄、認証情報に値を入力します。
api-openai ← OpenAI API キー
api-anthropic ← Anthropic API キー
api-notion ← Notion API トークン
Step 6: WSL から op.exe を呼ぶ
WSL2が起動している場合は、PowerShell で wsl --shutdown してから WSL2 を再起動します。
WSL2 から WSL Interop 経由で Windows 側の op.exe を直接呼び出します。
# WSL から動作確認
op.exe vault list
Windows 側で Windows Hello プロンプトが出て、認証成功後に WSL のターミナル内に Vault 一覧が表示されれば、WSL2 → WSL Interop → op.exe → 1Password アプリ → Windows Hello という認証チェーンが繋がっています。
素の op として呼べるよう、シンボリックリンクを張っておくと、対話シェル・スクリプトのいずれからも使えます(alias は対話シェルでしか効かないため)。
sudo ln -s "$(which op.exe)" /usr/local/bin/op
op --version
op run でコマンドをラップする
特定のコマンドを実行する時だけ環境変数を展開する方式です。シークレットがそのコマンドのプロセス内にのみ存在し、シェル全体には漏れないため、比較的安全に運用できる方法だと感じています。
~/.config/op/.env.template(実値ではなく参照を記述):
OPENAI_API_KEY=op://Business/api-openai/credential
ANTHROPIC_API_KEY=op://Business/api-anthropic/credential
NOTION_API_TOKEN=op://Business/api-notion/credential
op:// のあとに保管庫名、次にアイテムの名前を入力します。
ただし、WSL2 + op.exe 構成では、op run -- claude がそのままでは動きません。
$ op run --env-file ~/.config/op/.env.template -- claude
[ERROR] 2026/05/21 13:03:18 error while starting process:
exec: "claude": executable file not found in %PATH%
エラー末尾の %PATH% が Windows 表記である点に注目します。op.exe 自体が Windows ネイティブプロセスのため、その子プロセスとして起動される claude も Windows プロセス文脈で生成され、Linux 側にインストールした /usr/local/bin/claude を見つけられません。これは WSL Interop の構造上の制約で、設定で切り替えられる類のものではありません。
回避策として、op inject で Linux プロセスとして対象コマンドを起動する 方式を使います。op run と違い op inject は子プロセスを起動しない(標準出力または -o で指定したファイルにシークレットを書き出すだけ)ため、Windows プロセス境界の影響を受けません。
~/.config/op/op-run.sh:
#!/usr/bin/env bash
set -euo pipefail
TMPL="${OP_TEMPLATE:-$HOME/.config/op/.env.tmpl}"
if [[ ! -f "$TMPL" ]]; then
echo "template not found: $TMPL" >&2
exit 1
fi
# inject の結果を直接シェル変数に取り込む
INJECTED="$(op.exe inject -i "$TMPL")"
# 1行ずつ KEY=VALUE をパースして export
while IFS='=' read -r key value; do
[[ -z "$key" || "$key" =~ ^# ]] && continue
# クォート除去
value="${value%\"}"; value="${value#\"}"
value="${value%\'}"; value="${value#\'}"
export "$key=$value"
done <<< "$INJECTED"
exec "$@"
実行権限を付与し、シェル関数で op-run として呼べるようにしておきます。
chmod +x ~/.config/op/op-run.sh
# ~/.bashrc に追加
op-run() { $HOME/.config/op/op-run.sh "$@"; }
# 使い方: op-run claude
これで Windows Hello 認証 1 回 → シークレット解決 → claude を Linux プロセスとして起動、までが一気通貫で繋がります。
運用感と現実的な落としどころ
機能としては成立しますが、op-run claude のように毎回ラップする手数が継続的に発生します。Claude Code を 1 日に何度も起動する使い方では、この手間は地味にストレスでした。
またいざ試してみると、CLIセッションが10分程度で切れて毎回 Windows Hello を求められてしまい、これもストレスに感じます。
自動実行(cron や systemd timer から claude を呼ぶケース)まで含めて整理すると、選択肢は実質2つに集約されます。
| 方式 | 認証 | 強度 | 手間 | WSL2 適性 |
|---|---|---|---|---|
| Service Account Token を保管 | 永続トークン + 再認証なし | 中 | 低 | ◎ |
systemd-creds + TPM2 暗号化 |
TPM 紐付け復号 + 再認証なし | 高 | 低 | ❌(TPM passthrough なし) |
WSL2 では TPM(Trusted Platform Module)の passthrough が提供されていない ため、Linux サーバ標準である systemd-creds + TPM2 暗号化は利用できません。実機で確認すると次のように返ります。
$ ls /dev/tpm*
ls: cannot access '/dev/tpm*': No such file or directory
$ systemd-creds has-tpm2
partial
-firmware
-driver
+system
-subsystem
-libraries
デバイスもなく、firmware, driverが-だと成す術なしです。
Service Account Tokenを使い、再認証を回避する方針
そこで私の今回の選択は、TPM2 認証は妥協して、Service Account Token を WSL ファイルシステム上に保管する 方式としました。いろいろわかっていれば初めからこの路線で進めました。。
Windows 側で BitLocker を有効化 しているため、ノートPC 盗難時もディスクごと暗号化されていて物理的な情報流出リスクは抑えられています。
ちなみに、Service Account は 1Password Teams / Business / Enterprise プラン専用機能です。個人プラン(Individual / Families)では利用できない点はご注意ください。
※このためにBusinessプランを契約しました...
設定手順
ビジネスプラン用のURL https://team-[チーム名].1password.com にアクセスし、開発者 -> アクセストークン からサービスアカウントをクリック。
サービスアカウント名、保管庫の選択(個人用、共有以外)してアカウントを作成します。環境アクセスの選択画面が出ますが、同じシークレット名を検証環境、本番環境で使い分ける用途ですので、今回は選択しません。
# Service Account Token を保管
mkdir -p ~/.config/op
chmod 700 ~/.config/op
vi ~/.config/op/token
chmod 600 ~/.config/op/token
WSL2 で Service Account Token を使う場合は Linux 版 op をインストールすることをおすすめします。op.exe(Windows版)は Service Account モードと併用できるようですが、ややこしくなるので Linux 版 op を入れた方がシンプルです。
sudo rm /usr/local/bin/op
# GPG キーを登録
curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg
#リポジトリを追加
echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/amd64 stable main' \
| sudo tee /etc/apt/sources.list.d/1password.list
# インストール
sudo apt update && sudo apt install 1password
hash -d op
op --version
op vault list
No accounts configured for use with 1Password CLI.
- Turn on the 1Password desktop app integration to sign in with the accounts you've added to the app: https://developer.1password.com/docs/cli/app-integration/ for details.
- Add an account manually with 'op account add' and sign in by entering your password on the command line. See 'op account add --help' for details.
- Authenticate using a 1Password service account by setting the 'OP_SERVICE_ACCOUNT_TOKEN' environment variable to your service account token. Learn more: https://developer.1password.com/docs/service-accounts/
- Use 1Password CLI with a Connect server by setting the 'OP_CONNECT_HOST' and 'OP_CONNECT_TOKEN' environment variables to your Connect host and token, respectively. Learn more: https://developer.1password.com/docs/connect/
Do you want to add an account manually now? [Y/n] Y
Enter your sign-in address (example.1password.com): team-xxxxxxx.1password.com
Enter the email address for your account on team-xxxxxx.1password.com:
Enter the password for abcde@xxxxx.co.jp at team-xxxxxx.1password.com:
Now run 'eval $(op signin)' to sign in.
# Claude用環境変数を設定
mkdir -p $HOME/.config/Claude/
echo NOTION_API_TOKEN=op://Business/api-notion/credential > $HOME/.config/Claude/.env
# .bashrcに以下の設定を追加
cat >> ~/.bashrc <<'EOF'
claude() {
local env_file="$HOME/.config/Claude/.env"
local token_file="$HOME/.config/op/token"
[[ -r "$env_file" && -r "$token_file" ]] || { command claude "$@"; return; }
# トークンは op inject の実行時だけ読み込み
local injected
injected=$(OP_SERVICE_ACCOUNT_TOKEN="$(<"$token_file")" op inject -i "$env_file") || return
(
set -a
eval "$injected"
set +a
# 念のため明示的にunset
unset OP_SERVICE_ACCOUNT_TOKEN
command claude "$@"
)
}
EOF
# claude コマンドにのみ目的の変数を読み込ませ、標準入出力も op に取られないよう調整しています
source ~/.bashrc
動作確認
# 1. トークンがグローバルにセットされていないこと
echo "${OP_SERVICE_ACCOUNT_TOKEN:0:10}"
echo "${NOTION_API_TOKEN:0:10}"
# → 空行であれば OK
# 2. op が Service Account として動いているか
op whoami
# → User Type: SERVICE_ACCOUNT であればOK
# 3. claude を起動
claude
# 4. claude に "curl で NOTION_API_TOKEN を使って notion API を叩いてみて" と伝えて実データが返ってくることを確認。
# 伝え方は読み込ませた変数の使い方に合わせてください。
散在シークレットの片付け
機密情報の 1Passwordの保管庫 への移行と動作確認が済んだら、既存ファイルを段階的に削除していきます。
# まずはリネームして1週間様子見
mv ~/.config/Claude/secret.env ~/.config/Claude/secret.env.bk
OAuth refresh token を持つ設定ファイルは形式的に残します。また1Password にはトークンファイルは移行せずそのまま運用継続します。
機密情報のローテーション手順
- 発行側で旧キーを失効させ、再発行
- 1Password に再発行した値を設定
- WSL2環境は何もしない(次回の実行で新しい値が返ります)
1ヶ所の変更で全プロジェクトに反映 という目的が、実現できる形になりました。
残るリスクと緩和策
ここまでの構成でいくつか残るリスクがあります。「無料以外の要件は満たす」と言っても、ノーリスクではない点は前提として整理しておきたいところです。
セキュリティ上の制約
「自動ロック = しない」 に伴うメモリ常駐
自動運用のために 自動ロック を「しない」に設定している以上、1Password の復号鍵はプロセスメモリ上に常駐します。OS そのものが侵害されて root 権限でメモリダンプを取られた場合は、保管庫 の機密情報を読み取られる可能性があります。
これは常時 unlock 方式全てに共通する物理的な制約で、Bitwarden の常時セッション保存や、~/.bashrc に平文で書く方法等でも同じ性質を持ちます。緩和策としては OS 自体の防御(自動アップデート、不要サービスの停止、不審なバイナリを起動しない)が主軸になります。
保管庫 マスターパスワード / Secret Key の漏洩
1Password の 保管庫 自体は、マスターパスワードと Secret Key の組で守られています。両方が漏れた場合は 保管庫 全体が復号可能になるため、マスターパスワードは他サービスとの使い回しを避け、Secret Key は Emergency Kit として紙や別環境に保管しておくのが安心です。漏洩が疑われたら、マスターパスワードを変更することで全クライアントを強制ログアウトさせられます。
散在シークレットの「忘れ物」
保管庫 に集約したつもりでも、古い .env.bk、過去のバックアップ等に古い値が残っていることがあります。ローテーションを実施する際は、あわせて古い値の検索と削除を一度通しておくと、後々の心配が減ります。
AI コーディングツール側で揃えておくこと
シークレットを 保管庫 に集約した後は、それを受け取る側(Claude Code 等の AI コーディングツール)でも適切な準備をしておくことを推奨します。ここでは私が普段利用している Claude Code をベースに、Anthropic が公式に公開しているドキュメントの内容を踏まえて、シークレット管理の視点で押さえておきたい点を整理します。
CLAUDE.md の書き方
リポジトリ直下に置く CLAUDE.md は、Claude が会話の起点で必ず読み込む特殊ファイルです。Anthropic はこのファイルの書き方を細かく整理していて、「簡潔さを保ち、Claude がコードを読んでも分からないことだけ書く」ことが基本方針として示されています。
シークレット管理のコンテキストとしては、 「このスクリプトは $NOTION_API_KEY を環境変数で要求する」「APIキーを平文で書かない」 といった「Claude が推測できない開発環境固有の前提」を簡潔に書くべきです。
# 環境変数
- 連携テスト: `npm run test:notion`(`$NOTION_API_KEY` を要求)
- DB 同期: `python scripts/sync_notion.py`
# Rules
- APIキーの値を平文で出力しない
- APIキーを埋め込んだスクリプトを開発しない
- 言語標準の環境変数取得(`process.env`、`os.environ` 等)を使う
「必ず守らせたい」ことは Hooks で
Anthropic は CLAUDE.md と Hooks の役割を明確に分けています。CLAUDE.md は 助言的 で、Claude が従うのは概ね 80% 程度とされています。一方で Hooks は 決定論的 に動き、100% 実行されます。
If something must happen every time without exception (formatting, linting, security checks), make it a hook.
「絶対に毎回守らせたい」処理は、CLAUDE.md に書くよりも .claude/settings.json に組み込むほうが確実です。例えば、APIキーを使って実行してほしい場合はMCPツールの使用を deny に設定し、シークレット情報の流出防止のために PreToolUseイベントで許可していないフォルダに書き込ませない、といった使い方です。
APIキーは最小権限で発行する
1Password で管理する前段の話ですが、APIキー発行時には 「Admin 権限」「全権限」を持つキーを使い回さない のが原則です。Notion では接続するページ範囲を絞れますし、Google 系の Service Account など、各サービスが用意しているスコープ限定の発行手段を活用すると、漏洩時の影響範囲を狭く抑えられます。
.env のコミット事故を物理的に防ぐ
人が誤って .env をコミットしてしまう事故は、いまだに頻発します。リポジトリ側に Gitleaks などのシークレットスキャンを pre-commit hook や CI に組み込んでおくと、平文のトークン文字列がコミットに混入しても早期に検知できます。Vault に集約した後でも、過去の git 履歴に古い値が残るリスクは続くので、組み込んでおくと安心材料が増えます。
まとめ
1Password CLI とデスクトップアプリだけでシークレット管理を完結させると、次のような運用に近づけられます。
- 任意のディレクトリからコマンドを実行しても環境変数が自動で読み込まれる
- 漏洩時のローテーションは、各種設定ファイルを触らず1Password側で1つのアイテムを更新するだけ
年間のコストは発生しますが、複数プロジェクトを並行運用している場合、セキュリティ強化の対価としては、わりと割の合う投資かなと感じています。「無料」を絶対条件にしないなら、現実的な集約方法のひとつとして検討する価値はあると思います。
ただし、前章でまとめた通り、「自動ロック = しない」 に伴うメモリ常駐や Service Account Token の流出など、トレードオフとして残るリスクは存在します。ご自身の運用環境のリスク許容度と照らし合わせて、パターンの組み合わせを調整するのが安心かなと思います。
参考リンク
本記事の Claude Code・Anthropic 関連の記述は、以下の公式ドキュメントを参照しています。
-
Authentication - Claude Code Docs — 認証方法の優先順位、
apiKeyHelper、CLAUDE_CODE_OAUTH_TOKEN等の公式仕様 -
Best practices for Claude Code —
CLAUDE.mdの書き方、Include / Exclude 基準、Hooks との使い分け -
1Password CLI Documentation —
op run、op read、op inject、Service Account 等の公式リファレンス - 1Password CLI app integration — デスクトップアプリ連携の公式仕様
- 1Password Service Accounts — 無人実行向けトークンの発行・スコープ設計
- 1Password System Authentication on Linux — Linux ネイティブで PAM 経由の OS 認証を使う設定
- Use 1Password SSH agent in WSL — WSL から SSH キーを使う場合の別設定
-
oprun.sh(MykalMachon 氏) —
op injectベースのサードパーティ製ラッパー(Pattern A' の参考実装) -
WSL2 + 1Password CLI コミュニティ議論 — WSL2 における
op runの制約に関する公式フォーラム - Gitleaks — シークレット検出用 OSS ツール
Discussion