1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BitwardenのAgent Accessで、クラウドのClaude Codeから安全に認証情報を受け取る

1
Posted at

この記事は何

最近、1PasswordからAIエージェント向けの連携機能「1Password for Agents」が出ました。

ClaudeやChatGPTなどのエージェントがログイン画面にたどり着くと、1Passwordに認証情報を要求し、ユーザーが自分のデバイスで承認するとパスワードやワンタイムコードをフォームに自動入力してくれる、という機能です。
エージェントはパスワード自体を受け取らないようになっています。

エージェントにブラウザ操作を任せていると、ログインのところで毎回止まってしまうので、これは便利そうだなと思いました。

ただ、僕はプライベートではBitwardenを使っています。
「Bitwardenでも同じことができないのか...!?」と思って調べてみたところ、Bitwardenから「Agent Access SDK」というものが公開されていました。

この記事では、

  1. Agent Access SDKを使って、ウェブ上のClaude Code(クラウド環境)から手元のBitwardenの認証情報を安全に受け取れるか試した
  2. 試した内容をもとに、Playwright MCPでログインするためのSkillを作った

という話を紹介します。

Agent Accessは執筆時点(2026年10月)でearly previewの段階で、APIやプロトコルは今後変わる可能性があります。この記事はaac v0.11.0で試した内容です。
公式も本番の認証情報をそのまま使うことは推奨していないので、試す場合はご注意ください。

Agent Access SDKとは

Agent Accessは、パスワードマネージャーの認証情報を、保管庫全体を渡さずにリモートのシステムへ渡すためのプロトコルと、そのCLI・SDKです。

仕組みは以下のような形です。

  • 手元のPCでaac listenを起動しておく(Bitwarden CLIのbwを使って保管庫を読む側)
  • エージェント側ではaac connectやaac runで認証情報を要求する
  • 両者は中継サーバーを経由した、エンドツーエンドで暗号化されたトンネル(Noise Protocol)でつながる
  • 要求が来るたびに手元のCLIに承認画面が出て、承認したものだけが返る

特に便利なのがaac runというサブコマンドです。
取得した認証情報を標準出力に出さず、子プロセスの環境変数として渡してくれます。

aac run --domain example.com --env DB_USER=username --env DB_PASSWORD=password -- psql

エージェントがechoなどで中身を出してしまうと、その時点でパスワードが会話のコンテキストに残ってしまいます。
aac runを使えば、エージェントはパスワードを見ずに、パスワードを使ったコマンドだけを実行できます。

クラウドのClaude Codeから試してみる

1Password for Agentsは対応しているエージェント向けの連携で、ウェブ上のClaude Codeのようなクラウド環境のコーディングエージェントは対応先に入っていません。
一方でAgent Accessは中継サーバー経由でつながるので、クラウドからでも使えるのでは?と思い、試してみました。

今回は以下のようなステップで進めます。

  1. クラウド環境にaacを入れる
  2. 手元でaac listenを起動する
  3. ペアリングして認証情報を受け取る

1. クラウド環境にaacを入れる

まずはクラウドのセッションにaacを入れます。
リリースされているバイナリを持ってくるだけです。

curl -sSL https://github.com/bitwarden/agent-access/releases/download/v0.11.0/aac-linux-x86_64.tar.gz | tar xz -C ~/.local/bin
aac --version

なお、後で紹介するSkillではダウンロードしたファイルのSHA-256を検証してから展開するようにしています。

ウェブ上のClaude Codeは外向きの通信がプロキシ経由になっています。
aacはWebSocketで中継サーバー(wss://ap.lesspassword.dev)に接続するので、プロキシ越しでつながるか少し心配でしたが、特に何も設定せずにつながりました。

2. 手元でaac listenを起動する

次に手元のPCでaac listenを起動します。
いきなり本物の保管庫で試すのは怖かったので、まずはサンプルの認証情報だけを返す--provider exampleで試しました。

aac listen --provider example

起動すると、ABC-DEF-GHIのような形のペアリングトークンが表示されます。

3. ペアリングして認証情報を受け取る

クラウド側で、ペアリングトークンを使って接続します。

aac connect --token <ペアリングトークン> --output json

一度ペアリングすると接続情報が~/.access-protocol/に保存されるので、以降はトークンなしで要求できます。

あとはaac runで認証情報を要求します。
今回は中身を出さずに、文字数だけを出して取得できたかを確認しました。

aac run --domain example.com --env-all -- \
  sh -c 'echo "username_len=${#AAC_USERNAME} password_len=${#AAC_PASSWORD}"'

--env-allを付けると、AAC_USERNAME、AAC_PASSWORD、AAC_TOTPなどがAAC_付きの環境変数として渡されます。

その後、本物の保管庫(--providerなしでaac listenを起動)に切り替えて試したところ、以下のように取得できました。

username_len=27 password_len=14 totp=no

手元で承認するとクラウド側に認証情報が届き、パスワードは会話にもファイルにも残りません。
クラウドのClaude Codeから、手元のBitwardenの認証情報を受け取ることができました!

詰まった点

ここまでさらっと書いていますが、実際は結構詰まりました。

どのサイトを要求しても「Request denied」になる

本物の保管庫に切り替えたところ、どのドメインを要求しても数秒でRequest deniedが返ってくるようになりました。
手元に承認画面も出てきません。

aacのソースを読んでみると、手元のaac listenは要求を受け取るとまずbw get item <ドメイン>で保管庫を検索し、見つかったときだけ承認画面を出す作りになっていました。
見つからなかったときは承認画面を出さずに自動で拒否するのですが、リモート側にはどちらも同じRequest deniedで返ってきます。

原因は2つありました。

1つ目はbw get itemが「ちょうど1件だけ一致したとき」しか成功しないことです。
同じサービスのログインを複数登録していると、以下のようなエラーになります。

More than one result was found. Try getting a specific object by `id` instead.

この場合は、ドメインではなく保管庫の項目IDで要求すれば大丈夫です。

aac run --id <保管庫の項目ID> --env-all -- ./deploy.sh

項目IDは、ブラウザ版のBitwarden(ウェブ保管庫)でアイテムを選択したときのURLから確認できます。
URLに含まれるitemId=の後ろの値が項目IDです。

https://vault.bitwarden.com/#/vault?itemId=<保管庫の項目ID>

2つ目はaac listenが持っている保管庫のセッションキーが古くなっていたことです。
この状態のbwは「保管庫がロックされている」ではなくNot foundを返すので、aacも「該当する項目がない」と判断していました。
ID指定でも拒否されたことで、ようやくこの原因に気づきました。

手元で保管庫をロック解除し直したら、無事に取得できるようになりました。
aac listenは以下のように、BW_SESSIONを設定した同じターミナルで起動しておくのが確実です。

export BW_SESSION=$(bw unlock --raw)
aac listen

なお、Request deniedが返ってくるまでの時間で、原因をある程度切り分けられます。

症状 原因
数秒でRequest denied 手元のbw get itemが失敗して自動で拒否された
約120秒でタイムアウト 手元で承認されていない

パスキーは取れない

Agent Accessでやり取りできるのはusername、password、totp、uri、notesなどで、パスキーは扱えません。
ちなみにtotpにはワンタイムコードそのものではなく、コードを生成するためのシークレットが入っています。

Playwright MCPでログインする

認証情報を受け取れるようになったので、次はPlaywright MCPでブラウザを操作しているときに、ログインでどう使うかを考えました。

browser_typeでパスワードを入力するのはNG

一番素直なのは、Playwright MCPのbrowser_typeツールでパスワードを入力する方法です。
ただ、この方法だとツールの引数としてパスワードが会話に残ってしまいます。
せっかくaac runでパスワードを見ないようにしたのに、これでは台無しです。

同じブラウザをCDPで共有する

そこで、以下のように役割を分けることにしました。

  • ログインだけをaac runの中のスクリプトで行う
  • Playwright MCPはChrome DevTools Protocol(CDP)で同じブラウザにつないで、ログイン済みのタブを操作する

まず、CDPを有効にしてChromiumを起動しておきます。

"$CHROME" --headless=new --remote-debugging-port=9222 \
  --user-data-dir="${TMPDIR:-/tmp}/agent-access-cdp-profile" about:blank &

次に、aac runの中でログイン用のスクリプトを実行します。
スクリプトはCDPでブラウザにつなぎ、ログインフォームを埋めて送信したら切断するだけです。タブはブラウザに残ります。

cdp-login.cjs
const browser = await chromium.connectOverCDP(CDP_ENDPOINT);
const context = browser.contexts()[0] ?? await browser.newContext();
const page = await context.newPage();
try {
  await page.goto(url);
  await page.fill(userSel, AAC_USERNAME);
  await page.fill(passSel, AAC_PASSWORD);
  await submit(page, submitSel);
  const { origin, pathname } = new URL(page.url());
  console.log(`after login: ${origin}${pathname}`);
} finally {
  await clearPassword(page);
  await browser.close();
}
aac run --domain www.example.com --env-all -- \
  node cdp-login.cjs https://www.example.com/login '#username' '#password' 'button[type=submit]'

ここでやっていることは以下の2つです。

  • ログイン後のURLはoriginとpathnameだけを出す(クエリにトークンが入っていることがあるため)
  • 成功しても失敗しても、最後にパスワード欄を空にする(MCPのスナップショットにパスワードが写らないようにするため)

最後に、Playwright MCPをこのChromiumにつなぎます。
Playwright MCPはPLAYWRIGHT_MCP_CDP_ENDPOINTという環境変数でも接続先を指定できるので、MCPサーバーの設定はいじらずに、環境変数を渡すだけで済みます。

.claude/settings.json
{
  "env": {
    "PLAYWRIGHT_MCP_CDP_ENDPOINT": "http://localhost:9222"
  }
}

僕が公開しているclaude-task-workerというプラグインでもPlaywright MCP(--headless --browser chromium)を入れているのですが、この起動引数のままでも、環境変数だけでログイン済みのタブを操作できました。

CDPのポートにつなげるプロセスは、ブラウザを完全に操作でき、ログイン済みのセッションも使えます。
localhostに限定していても同じホストの別プロセスからはつなげるので、共有ホストでは使わず、使い捨てのコンテナなどで実行してください。

TOTPやメールの認証コード

二段階認証にも対応させました。

TOTPの場合は、AAC_TOTPのシークレットからRFC 6238に従ってワンタイムコードを作り、スクリプトの中で入力します。Node.js標準のcryptoだけで実装できたので、ライブラリは追加していません。

メールやSMSで届く認証コードはAgent Accessでは受け取れないので、以下のようにしました。

  • Gmailなどのメール用MCPが使える場合は、送信元と時刻で絞ってメールを検索し、最新の1通からコードを取り出す
  • 使えない場合は、届いたコードをユーザーに教えてもらう

認証コードは数分で使えなくなる使い捨ての値なので、こちらはMCPのツールで入力してOKということにしています。

Skillも作りました

今回の内容をもとに、Playwright MCPでAgent Access SDKを使うためのSkillも作成しました。
よければ使ってみてください。

1Password for Agentsとの違い

最後に、1Password for Agentsとの違いを簡単にまとめておきます。

1Password for Agents Bitwarden Agent Access
使えるエージェント 対応しているエージェント(Claude、ChatGPT Workなど) aacのCLIやSDKを呼べるもの(クラウド環境も可)
認証情報の渡し方 ログインフォームに自動入力 子プロセスの環境変数に注入
承認 ユーザーのデバイスで承認 手元のターミナル(aac listen)で承認
承認の通知 1Passwordが承認を求めてくれる 仕組みはないので、自分で用意する必要がある
提供状況 一部のエージェントで提供開始、ほかは準備中 early preview

対応しているエージェントで使うなら、1Password for Agentsの方が手軽です。
一方でAgent AccessはCLIやSDKから使えるので、クラウド環境のエージェントからでも使えるのが強みだと思います。

ただ、Agent Accessは承認を手元のターミナルでしないといけないのが少し面倒です。
要求が来ても通知などは特に飛んでこないので、ターミナルを開いていないと気づけず、そのまま120秒でタイムアウトしてしまいます。
エージェントに作業を任せて放置する使い方をするなら、承認待ちの通知などは自分で仕組みを用意する必要があります。

最後に

今回は、BitwardenのAgent Access SDKを使って、クラウドのClaude Codeから手元の保管庫の認証情報を受け取ってみました。
early previewなので詰まるところも多かったですが、パスワードを見せずにエージェントに使わせることができました!

エージェントにいろいろな作業を任せるようになると、認証情報をどう安全に渡すかはどんどん大事になってくると思います。
Bitwardenを使っている方は、ぜひAgent Accessを試してみてください。

最後までお読みいただきありがとうございました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?