최근 1Password에서 AI 에이전트를 위한 연동 기능인 '1Password for Agents'가 출시되었습니다.
Claude나 ChatGPT 같은 에이전트가 로그인 화면에 도달하면, 1Password에 인증 정보를 요청하고 사용자가 자신의 기기에서 승인할 경우 비밀번호나 일회용 코드를 양식에 자동으로 입력해 주는 기능입니다.
에이전트는 비밀번호 자체를 받지 않도록 설계되었습니다.
에이전트에게 브라우저 조작을 맡기면 로그인 단계에서 매번 멈추는 경우가 많았는데, 이 기능은 매우 유용할 것 같다고 생각했습니다.
하지만 저는 개인적으로 Bitwarden을 사용하고 있습니다.
'Bitwarden에서도 같은 것이 불가능한가...?!'라고 궁금해서 찾아보니, Bitwarden에서 'Agent Access SDK'라는 것을 공개했습니다.
이 글에서는 다음 내용을 소개합니다.
- Agent Access SDK를 사용하여 웹상의 Claude Code(클라우드 환경)에서 로컬의 Bitwarden 인증 정보를 안전하게 받을 수 있는지 테스트해 보았습니다.
- 테스트한 내용을 바탕으로 Playwright MCP로 로그인하기 위한 Skill을 제작했습니다.
Agent Access는 작성 시점(2026년 10월) 기준으로 early preview 단계이며, API나 프로토콜은 향후 변경될 가능성이 있습니다. 이 글은 aac v0.11.0으로 테스트한 내용입니다.
공식에서도 실제 인증 정보를 그대로 사용하는 것은 권장하지 않으므로, 시도할 때는 주의가 필요합니다.
Agent Access는 비밀번호 관리자의 인증 정보를 전체 저장소(vault)를 전달하지 않고 원격 시스템에 전달하기 위한 프로토콜과 그 CLI 및 SDK입니다.
작동 방식은 다음과 같습니다.
- 로컬 PC에서
aac listen을 실행합니다 (Bitwarden CLI의 bw를 사용하여 저장소를 읽는 측).
- 에이전트 측에서는
aac connect나 aac run으로 인증 정보를 요청합니다.
- 양쪽은 중계 서버를 거쳐 엔드투엔드로 암호화된 터널(Noise Protocol)로 연결됩니다.
- 요청이 올 때마다 로컬 CLI에 승인 화면이 나타나고, 승인한 정보만 반환됩니다.
특히 유용한 것이 aac run이라는 서브 커맨드입니다. 이 명령어는 획득한 인증 정보를 표준 출력(stdout)으로 내보내지 않고 자식 프로세스의 환경 변수(environment variable)로 전달해 줍니다.
aac run --domain example.com --env DB_USER=username --env DB_PASSWORD=password -- psql
에이전트가 echo 등으로 내용을 노출하면, 그 순간 비밀번호가 대화의 컨텍스트(context)에 남아버립니다.
aac run을 사용하면 에이전트는 비밀번호를 보지 않고도 비밀번호를 사용하는 명령어만 실행할 수 있습니다.
1Password for Agents는 지원하는 에이전트들을 위한 연동이며, 웹상의 Claude Code와 같은 클라우드 환경의 코딩 에이전트는 지원 대상에 포함되어 있지 않습니다.
반면 Agent Access는 중계 서버를 통해 연결되기 때문에, 클라우드에서도 사용할 수 있지 않을까 하여 테스트해 보았습니다.
이번에는 다음과 같은 단계로 진행했습니다.
- 클라우드 환경에
aac을 설치합니다.
- 로컬에서
aac listen을 실행합니다.
- 페어링하여 인증 정보를 받습니다.
먼저 클라우드 세션에 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는 외부 통신이 프록시(proxy)를 거치도록 설정되어 있습니다.
aac은 WebSocket으로 중계 서버(wss://ap.lesspassword.dev)에 연결하기 때문에, 프록시를 통해 연결될지 약간 걱정했지만, 특별히 아무 설정 없이도 연결되었습니다.
다음으로 로컬 PC에서 aac listen을 실행합니다.
갑자기 실제 저장소(vault)에서 시도하는 것은 두려웠기 때문에, 먼저 샘플 인증 정보만 반환하는 --provider example로 테스트했습니다.
aac listen --provider example
실행하면 ABC-DEF-GHI와 같은 형태의 페어링 토큰이 표시됩니다. 클라우드 측에서 이 페어링 토큰을 사용하여 연결합니다.
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_가 붙은 환경 변수로 전달됩니다. 이후 실제 저장소(vault) ( --provider 없이 aac listen 실행)“으로 전환하여 시도한 결과, 다음과 같이 가져올 수 있었습니다.
username_len=27 password_len=14 totp=no
직접 승인하면 클라우드 측에 인증 정보가 전달되며, 비밀번호는 대화 기록이나 파일에 남지 않습니다. 클라우드의 Claude Code에서 로컬의 Bitwarden 인증 정보를 받아올 수 있었습니다!
여기까지 비교적 쉽게 설명했지만, 실제로는 꽤 막혔습니다.
실제 저장소로 전환하자 어떤 도메인을 요청해도 몇 초 만에 Request denied가 반환되었습니다. 로컬에 승인 화면도 나타나지 않습니다.
aac의 소스를 읽어보니, 로컬의 aac listen은 요청을 받으면 먼저 bw get item <도메인>으로 저장소를 검색하고, 발견되었을 때만 승인 화면을 띄우는 구조였습니다. 찾지 못하면 승인 화면 없이 자동으로 거부되는데, 원격 측에는 둘 다 동일한 Request denied로 반환됩니다.
원인은 두 가지가 있었습니다.
첫 번째는 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입니다.
[이미지 placeholder]
두 번째는 aac listen이 가지고 있는 저장소 세션 키가 오래되었던 것입니다. 이 상태의 bw는 "저장소가 잠겨있다(locked)"를 반환하는 것이 아니라 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 등이며, 패스키(passkey)는 처리할 수 없습니다.
참고로 totp에는 일회용 코드 자체가 아니라 코드를 생성하기 위한 시크릿이 들어 있습니다.
인증 정보를 받을 수 있게 되었으므로, 다음으로는 Playwright MCP로 브라우저를 조작할 때 로그인에 어떻게 사용할지 고민했습니다.
가장 직관적인 방법은 Playwright MCP의 browser_type 도구에서 비밀번호를 입력하는 것입니다. 하지만 이 방법으로는 도구 인수로 비밀번호가 대화 기록에 남게 됩니다.
어렵게 aac run으로 비밀번호 노출을 막았는데, 이렇게 되면 수포로 돌아갑니다.
그래서 다음과 같이 역할을 분리하기로 했습니다.
- 로그인만
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로 브라우저에 연결하고, 로그인 폼을 채우고 전송한 후 연결만 끊으면 됩니다. 탭은 브라우저에 남아 있습니다.
const browser = await chromium.connectOverCDP(CDP_ENDPOINT);
const context = browser.contexts()[0] ?? await browser.newContext();
const page = await context.newPage();
...
aac run --domain www.example.com --env-all -- \
node cdp-login.cjs https://www.example.com/login '#username' '#password' 'button[type=submit]'
여기서 수행하는 것은 다음 두 가지입니다.
- 로그인 후 URL은
origin과 pathname만 출력한다 (쿼리 파라미터에 토큰이 포함될 수 있기 때문). - 성공하든 실패하든, 마지막에 비밀번호 필드를 비운다 (MCP 스냅샷에 비밀번호가 기록되는 것을 방지하기 위함).
마지막으로, Playwright MCP를 이 Chromium에 연결합니다.
Playwright MCP는 PLAYWRIGHT_MCP_CDP_ENDPOINT라는 환경 변수를 통해서도 연결 대상을 지정할 수 있으므로, MCP 서버 설정을 건드릴 필요 없이 환경 변수만 전달하면 됩니다.
{
"env": {
"PLAYWRIGHT_MCP_CDP_ENDPOINT": "http://localhost:9222"
...
}
제가 공개한 claude-task-worker라는 플러그인에도 Playwright MCP(--headless --browser chromium)가 포함되어 있는데, 이 실행 인수를 그대로 사용하더라도 환경 변수만으로 로그인된 탭을 조작할 수 있었습니다.
CDP 포트에 연결하는 프로세스는 브라우저를 완벽하게 제어할 수 있으며, 로그인된 세션도 사용할 수 있습니다. localhost로 한정되어 있어도 같은 호스트의 다른 프로세스에서는 연결이 가능하므로, 공유되는 호스트에서는 사용하지 말고 일회용 컨테이너 등에서 실행해 주세요.
2단계 인증에도 대응시켰습니다.
TOTP의 경우, AAC_TOTP의 시크릿을 이용해 RFC 6238에 따라 일회용 코드를 생성하고 스크립트 내에서 입력합니다. Node.js 표준 라이브러리인 crypto만으로 구현할 수 있었기 때문에 라이브러리는 추가하지 않았습니다.
이메일이나 SMS로 도착하는 인증 코드는 Agent Access로는 받을 수 없으므로, 다음과 같이 처리했습니다.
- Gmail 등의 이메일용 MCP를 사용할 수 있는 경우, 발신자와 시간으로 검색하여 메일을 찾고, 가장 최신 메일에서 코드를 추출한다.
- 사용 가능한 경우가 아닌 경우, 도착한 코드를 사용자에게 알려달라고 요청한다.
인증 코드는 몇 분 안에 만료되는 일회용 값이므로, 여기서는 MCP 도구를 통해 입력하는 것으로 간주하겠습니다.
이번 내용을 바탕으로 Playwright MCP에서 Agent Access SDK를 사용할 수 있는 Skill도 제작했습니다. 사용해 보시면 좋겠습니다.
마지막으로 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를 시도해 보시길 바랍니다.
끝까지 읽어주셔서 감사합니다.