AI Agent 時代のシークレット管理設計 — Vault・SSO・MCP の3層アーキテクチャ
AIエージェントが増えると秘密鍵の管理が破綻する。開発者個人・Agent・CI環境の3層に分けて設計する方法と、1Password/Vault/Doppler/AWS Secrets Managerのツール選定マトリクス、MCP×Vaultパターン、SSO+Token Lifecycle設計、監査ログ、鍵流出パターン3つを網羅した設計論。
エンジニアのゆとです。
Claude CodeのSubAgentを3本並列で動かしながら、ふと気づいた。
「今このエージェントたち、どのAPIキーを見ているんだろう」
プロジェクトルートに .env がある。SubAgentが自律的にファイルを読み書きする。そのとき、.env の中身がエージェントのコンテキストに乗っているかどうか、デフォルトでは制御できていない。しかも並列で3本動いているので、どれがどのキーに触ったかのログも残らない。
これが「AIエージェントが増えると秘密管理が破綻する」というやつだ。
1人のエンジニアが1台のマシンで1つのプロジェクトを動かすなら、.envで十分かもしれない。でも現実は違う。Claude Codeがバックグラウンドで動き続け、GitHub Actionsがデプロイを回し、複数のMCPサーバーがAPIを叩き、チームメンバーが同じインフラを共有している。そういう構成では「一か所に平文で書いておく」では絶対に破綻する。
この記事では、設計レイヤーの話をする。個別ツールのセットアップ手順ではなく、「誰が、どのシークレットに、どうアクセスするか」を整理する設計論だ。
エージェントが増えると何が起きるのか
まず問題を具体化しておく。
ローカルで開発している人間エンジニアなら、シークレット管理はこれで済む。
# .env
OPENAI_API_KEY=sk-xxxxx
GITHUB_TOKEN=ghp_xxxxx
STRIPE_SECRET_KEY=sk_test_xxxxx
source .env して作業する。.gitignore に入れてあれば(入れてなければ終わりだが)それなりに安全だ。
問題はAIエージェントが登場してからだ。
Claude Code が起動するとき、プロジェクトルートを丸ごとコンテキストに取り込もうとする。明示的に除外しない限り、.env の内容はLLMに渡る可能性がある。SubAgentsを使った並列実行では、子エージェントがどのファイルにアクセスしたか、デフォルトでは追跡できない。
CI/CDが加わると問題はさらに複雑になる。GitHub Actions のシークレットに30個のAPIキーが登録されていて、誰がいつ追加したかわからない。ローテーションのルールもない。開発者が退職したときに「あのキーは何に使ってたっけ」となる。
MCPサーバーを複数接続すれば、それぞれが別のサービスのAPIキーを要求する。設定ファイルに平文で書くか、環境変数に突っ込むか、という二択になりがちだ。
1. .envの平文コミット: .gitignoreを忘れた瞬間にGitHubに流出。AIエージェントがコンテキストに取り込む問題もある
2. SubAgentsの監査不能: 並列実行時にどのエージェントがどのキーに触ったか追跡できない
3. MFAバイパス: AIがMFAを通過できないため、エージェント用サービスアカウントのMFAを切ってしまう
4. キーの使い回し: 開発者個人・Agent・CIが同じAPIキーを共有して最小権限の原則が崩壊
5. ローテーションの放棄: 複数のAgent・CIにキーが散らばっていて更新コストが高くなりローテーションを諦める
これらを一気に解決しようとすると大掛かりになる。現実的なのは「3つのレイヤーに分けて考える」ことだ。
3層設計: 開発者個人 / Agent / CI環境
シークレット管理の設計で一番やりがちなミスは「全員が同じキーを使う」構成だ。
開発者、AIエージェント、CIパイプラインは、同じサービスにアクセスするとしても「別の主体」として扱う必要がある。それぞれに別のクレデンシャルを発行し、スコープも分けるのが原則だ。
Layer 1: 開発者個人
人間のエンジニアが使うシークレット。ローカル開発、スクリプト実行、管理系操作。
特性:
- MFAが使える(むしろ必須)
- キーの数は多い(全サービスのAPIを試したりする)
- ローテーション頻度は低くていい
- 個人のマシンに閉じている
推奨ツール: 1Password または Bitwarden
1Password の op run を使うと、.env に op://Vault/Item/field という参照パスを書いて、実行時に実値に展開できる。.env ファイルをGitにコミットしても平文キーが含まれなくなる。
# .env(参照パス版)
OPENAI_API_KEY=op://Personal/OpenAI API Key/credential
GITHUB_TOKEN=op://Personal/GitHub CLI Token/credential
# 実行時
op run --env-file=".env" -- python3 my_script.py
Claude Code で MCP サーバーのキーを管理する場合も同じアプローチが使える。claude_desktop_config.json の env フィールドに op:// 参照を書けば、config に平文が残らない。
Layer 2: Agent(自律実行する非人間主体)
Claude Code SubAgent、Devin、GitHub Copilot Workspace など、人間の代わりに自律的に動くもの。
特性:
- MFAが使えない(インタラクティブ操作が困難)
- アクセスは最小権限で十分(全APIが必要なわけじゃない)
- Just-in-Time アクセスが望ましい(タスク中だけ有効)
- 監査ログが必要(いつ、何にアクセスしたか)
推奨ツール: 1Password Service Account または HashiCorp Vault
1Password の Service Account を使う場合:
from onepassword import Client
# Service Account トークンで初期化(トークン自体は環境変数から)
client = await Client.authenticate(
auth=os.environ["OP_SERVICE_ACCOUNT_TOKEN"],
integration_name="claude-code-subagent",
integration_version="1.0.0",
)
# Vault から必要なキーだけ取得
stripe_key = await client.secrets.resolve("op://Agent Vault/Stripe Key/credential")
Service Account には特定のVaultだけへのアクセス権を付与する。「全Vaultに読み書きできるトークン」は発行しない。
HashiCorp Vault を使う場合は後述するMCPパターンで詳しく説明する。
Layer 3: CI/CD環境
GitHub Actions、CircleCI、Vercel Deploy Hooks など。
特性:
- 実行環境が使い捨て(コンテナが毎回新規起動)
- キーの有効期限を短くできる
- 環境ごとに分離が必要(dev/staging/prod)
- GitOpsとの相性を考える必要がある
推奨ツール: AWS Secrets Manager / GitHub Actions Secrets / Doppler
環境ごとに明示的にスコープを切る。
# GitHub Actions の例
jobs:
deploy:
environment: production # 環境名でシークレットを分離
steps:
- name: Deploy
env:
STRIPE_SECRET: ${{ secrets.STRIPE_SECRET_PROD }} # prodのみ参照
DB_URL: ${{ secrets.DATABASE_URL_PROD }}
environment: production を指定することで、stagingのシークレットをprodのジョブが誤って参照するリスクを減らせる。
ツール選定マトリクス
4つの主要ツールを3レイヤーごとに整理する。
| ツール | Layer 1(個人) | Layer 2(Agent) | Layer 3(CI) | 価格感 |
|---|---|---|---|---|
| 1Password | ◎ UX最高、op run便利 | ◯ Service Account | △ 使えるがCI向けではない | 個人$3/月〜 |
| HashiCorp Vault | △ 個人には重い | ◎ 動的シークレット、監査ログ | ◎ AppRole認証、環境分離 | OSS無料(運用コスト有) |
| Doppler | ◯ シンプル、UI良い | ◯ Service Token | ◎ CI/CD統合が強い | 無料〜$6/ユーザー/月 |
| AWS Secrets Manager | △ 個人には過剰 | ◯ IAMロールと組み合わせ | ◎ AWS環境なら最適 | $0.40/シークレット/月〜 |
個人エンジニア + Claude Code のローカル開発: 1Password 一択。UXが最も良く、MCP統合も進んでいる。
スタートアップでAgent + CIも使う: Doppler が導入コストとコストのバランスが良い。CI/CDとの統合が素直で、チームスケールしても破綻しにくい。
エンタープライズ or AWS依存が強いプロダクト: HashiCorp Vault + AWS Secrets Manager の組み合わせ。運用は重いがコントロールが最大。動的シークレット(短命なキーの自動発行)が使えるのはVaultだけ。
MCP × Vault パターン: AgentがVault経由で動的に秘匿情報を取得する
ここが設計の核心だ。
従来の発想: 「エージェントに環境変数でキーを渡す」 Vault思想: 「エージェントが必要なときに、必要なキーだけVaultに取りに行く」
この違いは大きい。環境変数は「起動時に全部渡してしまう」ので、エージェントは起動中ずっと全キーにアクセスできる状態になる。Vault経由なら「このタスクに必要なキーだけ、このタスクの間だけ」という制御が可能だ。
HashiCorp Vault + MCP Server パターン
HashiCorp Vault をMCPサーバーとして Claude Code に接続する構成。
まず Vault を起動する(開発環境では dev モードが手軽)。
vault server -dev -dev-root-token-id="dev-token"
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='dev-token'
# シークレットを登録
vault kv put secret/myapp/stripe secret_key=sk_test_xxxx publishable_key=pk_test_xxxx
vault kv put secret/myapp/openai api_key=sk-xxxx
Vault のアクセスポリシーを Agent 用に制限する。
# agent-policy.hcl
path "secret/data/myapp/stripe" {
capabilities = ["read"]
}
path "secret/data/myapp/openai" {
capabilities = ["read"]
}
# stripe や openai 以外へのアクセスは拒否(明示しないパスはデフォルト deny)
vault policy write agent-policy agent-policy.hcl
vault token create -policy=agent-policy -ttl=1h # 1時間で自動失効
Claude Code の claude_desktop_config.json に Vault MCP サーバーを追加する。
{
"mcpServers": {
"vault": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-vault"],
"env": {
"VAULT_ADDR": "http://127.0.0.1:8200",
"VAULT_TOKEN": "op://Dev/Vault Agent Token/credential"
}
}
}
}
VAULT_TOKEN は1Passwordの参照パスにしておく。Vault のトークン自体を1Passwordで管理する二重構造だ。
Claude Code(またはSubAgent)は、Vault MCPを通じてシークレットを取得する。
# Claude Code のプロンプト例
vault にある secret/myapp/stripe の secret_key を使って Stripe の残高を確認して
エージェントは vault://secret/myapp/stripe にアクセスし、secret_key の値を取得して Stripe API を叩く。キーの値はエージェントのコンテキストに一瞬乗るが、Vault の監査ログに「誰が、いつ、どのパスにアクセスしたか」が記録される。
動的シークレット(Vault の本領)
Vault の最も強力な機能が「動的シークレット」だ。
通常のシークレット管理は「キーを保存しておいて必要なときに渡す」。Vault の動的シークレットは「必要なときにキーを生成して、一定時間後に自動削除する」。
AWS IAM の一時クレデンシャルを例にすると:
# Vault の AWS Secrets Engine を有効化
vault secrets enable aws
# AWS 接続設定
vault write aws/config/root \
access_key=$AWS_ACCESS_KEY_ID \
secret_key=$AWS_SECRET_ACCESS_KEY \
region=ap-northeast-1
# Claude Code Agent 用のロールを定義(S3読み取りのみ)
vault write aws/roles/claude-agent \
credential_type=iam_user \
policy_document=-<<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::my-project-bucket/*"]
}]
}
EOF
このロールに対してシークレットを要求すると:
vault read aws/creds/claude-agent
# => access_key: AKIA...(新規発行)
# => secret_key: xxxxxxx(新規発行)
# => lease_duration: 1h(1時間後に自動削除)
Claude Code のSubAgentがタスクを受け取るたびに、新鮮な一時クレデンシャルが発行され、タスク完了から1時間後には自動で無効化される。常設のAPIキーは存在しない。
SSO + Token Lifecycle 設計
SSO(Single Sign-On)とシークレット管理の統合は、チーム運用になると必須になる。
OpenID Connect (OIDC) でCIを無人化する
GitHub Actions では、OIDC を使って「マシンに長期シークレットを渡さない」構成が作れる。
# GitHub Actions の OIDC 設定
jobs:
deploy:
permissions:
id-token: write # OIDC トークン発行を許可
contents: read
steps:
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
aws-region: ap-northeast-1
# access_key / secret_key は一切不要
GitHub が発行するOIDCトークンをAWSが検証し、一時クレデンシャルを返す。secrets.AWS_ACCESS_KEY_ID のような長期キーは GitHub Actions のシークレット設定に登録すら不要になる。
HashiCorp Vault でも同様のパターンが使える。
# Vault の JWT Auth Method を有効化
vault auth enable jwt
# GitHub Actions の OIDC と紐付け
vault write auth/jwt/config \
oidc_discovery_url="https://token.actions.githubusercontent.com" \
bound_issuer="https://token.actions.githubusercontent.com"
# リポジトリ単位でポリシーを割り当て
vault write auth/jwt/role/github-ci \
role_type="jwt" \
bound_audiences="https://github.com/my-org" \
bound_claims='{"repository": "my-org/my-repo"}' \
user_claim="repository" \
policies="ci-deploy-policy" \
ttl="1h"
Token Lifecycle の設計原則
発行時: 最小スコープ・最短TTLで発行
利用時: 使用ログを記録
更新時: 自動ローテーション(Vault で自動化可能)
失効時: TTL超過または手動revoke
漏洩時: Vault なら vault token revoke で即時無効化
TTL の目安:
| 用途 | 推奨 TTL |
|---|---|
| 人間のローカル開発 | セッション単位(8〜24h) |
| CI/CD ジョブ | ジョブ時間 + バッファ(1〜2h) |
| Agent サービスアカウント | タスク単位(15min〜1h) |
| 長期バッチ処理 | 最長でも24h、完了時に revoke |
「永続するトークン」を発行するのは、バックドアを開けっぱなしにするのと同じだ。
監査ログとアクセス制御
「誰が、いつ、何にアクセスしたか」を記録することは、セキュリティの事後対応に必須だ。
HashiCorp Vault の監査ログ
Vault は監査バックエンドを設定することで、全アクセスを syslog や JSON ファイルに記録できる。
vault audit enable file file_path=/var/log/vault/audit.log
出力されるログのイメージ:
{
"time": "2026-05-01T14:23:01.123Z",
"type": "request",
"auth": {
"token_type": "service",
"entity_id": "agent-subagent-01",
"policies": ["agent-policy"]
},
"request": {
"operation": "read",
"path": "secret/data/myapp/stripe"
}
}
entity_id でどのAgentが何のパスを読んだか、タイムスタンプで紐付けできる。
1Password のアクセスログ(Business プラン)
1Password Business では、Vaultへのアクセス、アイテムの閲覧・変更がすべてイベントログに記録される。
Service Account ごとに別のアクセスログが残るので、「どのAgentが何時にどのキーを取得したか」が追跡できる。SOC 2 監査のエビデンスとしても使える。
アクセス制御の実装
Vault のポリシーは HCL で記述し、最小権限を明示する。
# production-agent-policy.hcl
# 本番Stripeキーは読み取りのみ
path "secret/data/production/stripe" {
capabilities = ["read"]
}
# 本番DBは完全拒否(Agentがデータを直接読む必要はない)
path "secret/data/production/database" {
capabilities = ["deny"]
}
# メタデータ(キーの存在確認)は許可
path "secret/metadata/production/*" {
capabilities = ["read", "list"]
}
「何でも書けるポリシー」を発行しない。キーを追加・変更できるのは人間の管理者のみ。Agentは「読む」だけ。書けない。
ハマりどころ — 鍵流出パターン3つ
設計レベルで気をつけるべきパターンを、実際に起きやすいものに絞って紹介する。
パターン1: LLMコンテキスト経由の流出
何が起きるか: Claude Code が自律的にデバッグを進めるとき、エラーメッセージや環境変数をログに吐き出す。そのログに APIキーが含まれていると、プロンプトの context やチャット履歴に乗る。
なぜ起きるか: print(os.environ) 系のデバッグコードをAgentが自動生成したとき。subprocess.run(["env"], capture_output=True) の出力を Claude Code が読んだとき。
対策: .env に平文を書かない。CLAUDE.md の denylist に *.env と **/.env* を明示する。Claude Code の /permissions で .env の読み取りをブロックする。
<!-- CLAUDE.md -->
## 読み取り禁止ファイル
.env, .env.local, .env.production, .env.secrets は絶対に読まない。
環境変数を確認する必要があるときは `op run` 経由で実行する。
パターン2: SubAgent 間のシークレット漏洩
何が起きるか: 親Agentが持っているコンテキスト(APIキーを含む)を、子SubAgentにそのまま引き渡してしまう。
なぜ起きるか: Claude Code のSubAgent はデフォルトで親のコンテキストを継承する。親が .env を読んでいた場合、そのキーの情報が子にも渡る可能性がある。
対策: SubAgent に渡すコンテキストを明示的に絞る。CLAUDE.md の SubAgent セクションで「環境変数を引き渡さない」ことを明示する。
# claude-code-settings.json のイメージ
{
"subagents": {
"inherit_env": false, # 環境変数を継承させない
"allowed_tools": ["read", "write", "bash"] # 最小ツールセット
}
}
パターン3: CI シークレットの使い回しによるスコープ爆発
何が起きるか: ひとつの GITHUB_TOKEN を全 GitHub Actions ジョブで使い回す。そのトークンが repo スコープ全体を持っていると、どのジョブからも全リポジトリに書けてしまう。
なぜ起きるか: 「とりあえず全部許可で発行しておけば動く」という運用のまま放置されるから。
対策: GitHub Actions の permissions を明示的に絞る。
jobs:
test:
permissions:
contents: read # テストジョブは読み取りのみ
steps: ...
deploy:
permissions:
contents: read
deployments: write # デプロイジョブは deployments の書き込みのみ
id-token: write # OIDC トークン発行のみ
steps: ...
permissions を書かないと GitHub がデフォルトで広めのスコープを付与するので、書かないよりも書いて絞る方が安全だ。
まとめ — 「誰が、いつ、何を使えるか」を設計する
AIエージェントが増えた世界では、シークレット管理は「保存場所」の問題ではなく「アクセス設計」の問題だ。
どこに保存するかよりも、「誰が(主体)」「いつ(TTL)」「何に(スコープ)」「どのようにアクセスするか(プロトコル)」を先に決める。その答えを実装するツールを選ぶ順序が正しい。
個人のローカル開発から始めるなら 1Password の op run が最小コストで一番の改善になる。.env 平文をなくすだけで、Claude Code が .env を読むリスクが消える。
Agentが本番データに触れる構成になったら、Vault の動的シークレットを検討する。常設キーを持たないことで、漏洩してもダメージを最小化できる。
CI/CD は OIDC で長期シークレットを消す。「GitHubに保存するシークレットを増やさない」方向に設計する。
全部いきなりやらなくていい。まず Layer 1(個人)から手をつけて、Agentが増えたらLayer 2、CIを本番化するタイミングで Layer 3、という順で整備するのが現実的だ。
関連記事



この記事の情報は2026年4月時点のものです。各ツールの最新機能・料金は公式ドキュメントをご確認ください。