AI 보조 코딩의 마지막 단계는 가입 양식이다
요약
AI 에이전트가 개발 과정에서 겪는 마지막 병목 현상인 외부 서비스 가입 및 API 키 설정 문제를 다룹니다. 작성자는 봇 차단 기술(Turnstile 등)을 우회하여 에이전트가 스스로 환경 설정을 완료할 수 있도록 만드는 엔지니어링적 도전 과제를 설명합니다.
핵심 포인트
- AI 에이전트는 코드 작성은 빠르지만 외부 서비스 가입/설정 단계에서 수동 개입이 필요함
- SaaS의 강력한 봇 차단 기술(Cloudflare Turnstile 등)이 에이전트 자동화의 주요 장애물임
- IP, GPU, CDP 흔적 등 다양한 가설을 실험하며 문제의 근본 원인을 찾는 과정 제시
- 가설과 실험 결과를 기록하는 체계적인 접근 방식(STATE.md)의 중요성 강조
AI는 저의 개인 개발 속도를 2~3배 더 빠르게 만들어 주었습니다. 이제 저는 상용구 코드 (boilerplate)가 아니라 제품 결정에 시간을 씁니다. 에이전트 (agent)가 통합 (integration), 마이그레이션 (migration), 테스트 (test)를 작성하기 때문입니다. 하지만 루프의 한 부분만큼은 에이전트가 계속 저에게 다시 떠넘겼고, 그것은 매번 저의 흐름 (flow)을 끊어놓았습니다.
여러분도 그 순간을 아실 겁니다. 이메일, 푸시 알림, 또는 데이터베이스를 요청할 때 말이죠. 에이전트는 30초 만에 통합 코드를 작성하고는 멈춰버립니다:
.env 파일에 RESEND_API_KEY를 추가하세요.
그래서 저는 에디터에서 Alt-Tab으로 화면을 전환하여, 대시보드를 찾고, 가입하고, 온보딩 (onboarding) 과정을 거치고, 이메일을 인증하고, API 키를 생성하고, 복사해서 .env에 붙여넣습니다. 그러면 이제 커밋 (commit)하지 않도록 주의해야 하는 라이브 비밀 키 (live secret)가 평문 (plaintext)으로 놓이게 되는데, 에이전트는 이를 잊어버리고 다음 세션에서 또 저에게 요구합니다. 모든 서비스, 모든 프로젝트마다 말이죠. 이것은 AI 보조 코딩에서 마지막으로 남은 수동 병목 현상 (manual bottleneck)이며, 한동안은 에이전트가 저를 대신해 뚫을 수 없는 유일한 벽이었습니다.
그래서 저는 그 드라이버 (driver)를 만들었습니다. 그것은 두 가지 진정으로 어려운 엔지니어링 문제로 변했고, 그중 두 번째 문제가 제가 실제로 관심을 두고 있는 문제입니다.
문제 1: 문을 통과하는 것
현대의 SaaS는 봇 (bot)이 가입 양식을 채우는 것을 원하지 않습니다. Cloudflare Turnstile, Stytch, Clerk, DataDome — 가입 페이지는 이제 웹에서 가장 공격적으로 봇 차단 (bot-gated)이 이루어지는 영역 중 하나입니다. 이것은 몇 주간의 미궁 (rabbit hole)이 되었고, 제가 공유할 수 있는 가장 유용한 사실은 제가 반복적으로 얼마나 틀렸었는가 하는 점입니다.
가입이 차단될 때마다 저는 확신에 찬 이론을 세웠습니다:
"IP 평판 (IP reputation) 문제일 거야." 데이터센터 IP는 당연히 플래그 (flagged)가 지정될 테니까요. 하지만 이는 틀렸습니다: 새로운 주거용 IP (residential IP)도 동일하게 실패했습니다.
"지문 (fingerprint) 또는 GPU 부재 문제일 거야." 헤드리스 크로미움 (Headless Chromium)은 실제 GPU가 없으며, 모든 곳에 이를 알립니다. 하지만 이 또한 틀렸습니다: 실제 GPU와 실제 디스플레이가 있는 실제 노트북도 동일하게 실패했습니다.
"그것은 CDP 수준의 자동화가 드러내는 것입니다." navigator.webdriver, Runtime.enable, mainWorld isolation. 이것은 실제였지만 충분하지 않았습니다. 저는 stealth 플러그인들이 해결하지 못하는 CDP 흔적(tells)을 차단하는 Playwright의 포크(fork)인 patchright로 옮겼고, 상황은 나아졌지만 여전히 Turnstile을 통과하지는 못했습니다.
저는 계속해서 "환경의 문제다"라는 결론을 도출했지만, 실험을 통해 계속해서 틀렸음이 증명되었습니다. 결국 저를 구원한 규율은 모든 허위 가설을 STATE.md에 기록하는 것이었습니다. 가설을 세우고, 그것을 반증할 실험을 명명하고, 실행하고, 결과를 기록하는 방식입니다. 그것은 마치 공동묘지처럼 읽히지만, 저로 하여금 똑같은 세 가지 오답을 반복해서 맹목적으로 따르는(cargo-culting) 것을 막아주었습니다.
제가 마침내 통제된 매트릭스(controlled matrix)를 실행했을 때 밝혀진 Turnstile 장벽의 실제 원인은 거의 바보 같을 정도였습니다: 바로 Playwright의 launchPersistentContext였습니다. IP도, GPU도, 핑거프린트(fingerprint)도 아니었습니다. Playwright가 지속적인 브라우저 프로필(persistent browser profile)을 실행하고 연결하는 방식 자체가 탐지 가능한 신호였습니다. 해결책은 Playwright가 브라우저를 실행하게 두는 대신, 일반 Chrome 프로세스를 직접 실행하고 CDP를 통해 연결(connectOverCDP)하는 것이었습니다. 동일한 IP, 동일한 기기, 동일한 핑거프린트 — 토큰이 발급되었습니다.
나머지 안티 봇(anti-bot) 계층은 덜 놀랍지만 제 역할을 다하고 있습니다: 보이지 않는/점수가 매겨지는 챌린지를 위한 행동 시뮬레이션(베지에 곡선(bezier-curve) 마우스 경로, 생각하는 듯한 휴지기를 포함한 가변적인 타이핑 속도, 로드 후 체류 시간), 보이는 체크박스 챌린지를 위한 클릭 후 대기, 그리고 — 헤드리스(headless) Chromium은 보는 즉시 차단되기 때문에 — 사용자는 볼 수 없지만 렌더링할 실제 표면이 존재하도록 온디맨드 가상 디스플레이(Xvfb)를 대상으로 헤드(headed) 모드로 실행하는 것 등이 있습니다.
이 중 어느 것도 "장벽"이 아닙니다. 제가 장벽이라고 불렀던 모든 차단은 특정하고 수정 가능한 흔적(tell)으로 밝혀졌습니다. "차단은 지형의 문제가 아니라 진단의 문제다"라는 사고방식이 업무의 대부분을 차지합니다.
문제 2: 열쇠를 얻은 후 이를 유지하는 법
이 부분이 제가 실제로 이 도구를 만든 이유입니다. 열쇠를 얻는 것은 수단일 뿐이며, 흥미로운 질문은 그 열쇠가 어디로 가느냐 하는 것입니다.
기본적인 답변인 .env 파일은 진심으로 나쁜 방식이며, 이 글을 읽는 모든 사람이 이를 경험해 보았을 것입니다. .env 파일은 GitHub에 커밋됩니다. 분실되기도 합니다. 세 개의 서비스에 붙여넣기만 되고 그 중 어느 곳에서도 교체(rotation)되지 않습니다. 그리고 AI 코딩 시대에는 새로운 최악의 시나리오가 존재합니다. 바로 API 키가 에이전트(agent)의 컨텍스트 윈도우(context window)에 들어가게 되는 것인데, 이는 비밀 정보가 가장 통제되지 않는 장소입니다.
따라서 설계 원칙은 다음과 같습니다: 원본 비밀 정보(raw secret)는 에이전트에게 절대 다시 전달되지 않으며, 사용자의 저장소(repo)에도 절대 저장되지 않습니다. 구체적으로는 —
볼트(vault)는 쓰기 전용(write-only)입니다. 드라이버가 대시보드에서 키를 추출하면, 이는 즉시 암호화된 저장소로 들어갑니다. 에이전트는 이를 다시 읽어낼 수 없습니다. 의도적으로 "평문(plaintext)을 가져오라"는 API는 존재하지 않습니다. 만약 .env를 위한 값이 필요하다면, 사용자가 직접 웹 볼트에서 읽어야 합니다.
코드가 키를 필요로 할 때, 값 자체를 가져오는 것이 아니라 프록시(proxy)를 통해 호출을 수행합니다. 요청 시 ${SECRET}라고 작성하면, 프록시가 이그레스 경계(egress boundary)의 서버 측에서 실제 키를 주입하고 업스트림(upstream) 응답만을 반환합니다. 비밀 정보는 제공자(provider)로 전송되는 과정에서만 통신망을 통과할 뿐, 사용자에게는 절대 전달되지 않습니다.
배포된 앱(또는 CLI 에이전트 루프)의 경우, 범위가 지정되고(scoped), 속도 제한이 걸려 있으며(rate-limited), 즉시 취소 가능한(instantly-revocable) 토큰인 'egress grant'를 발행합니다. 앱은 실제 키가 아닌 해당 토큰을 보유한 제공자(provider)를 호출합니다. 따라서 Vault(금고)는 더 이상 평문(plaintext)이 담긴 폴더가 아니라 제어 평면(control plane)이 됩니다. 키를 한 번 회전(rotate)하면 모든 grant가 이를 반영하며, 무언가 유출되면 grant를 취소하기만 하면 됩니다. 그러면 다음 호출은 차단(fail closed)되며, 키를 다시 회전하기 위해 허둥댈 필요가 없습니다.
제가 가장 조용히 자부심을 느끼는 부분은 멀티 콘솔 설정(multi-console setup)입니다. 예를 들어, GCP 콘솔에서 클라이언트를 생성한 후 그 시크릿(secret)을 다른 콘솔에 붙여넣는 Google OAuth 설정 같은 작업 말입니다. 드라이버는 콘솔 A에서 시크릿을 캡처하여 세션 내에 봉인(seals it in-session, 값 자체가 아닌 핸들 형태)한 뒤, 콘솔 B에 입력합니다. 이 과정에서 시크릿은 에이전트의 컨텍스트나 채팅 기록 어디에도 결코 나타나지 않습니다. 이러한 '세션 내 봉인된 전송(sealed-in-session transfer)' 덕분에,
일회용 매직 링크(Single-use magic links)는 시간과의 싸움입니다. 링크가 도착하고 클릭되는 사이에 만료될 수 있으며, 이는 부분적으로 제공업체의 의미론(semantics)에 따른 것이라 제가 완전히 가려버릴 수 있는 문제가 아닙니다.
데이터센터 IP(Datacenter-IP) 세션 무효화는 지속적으로 발생하는 운영상의 현실입니다.
이것은 베타 버전이며, 베타 기간 동안은 무료입니다.
그 형태 (The shape of it)
안티 봇(anti-bot) 디버깅은 제가 최근에 했던 일 중 가장 유익한 작업이었으며, '쓰기 전용 금고(write-only-vault) + 주입 프록시(injecting-proxy) + 세션 내 봉인된 전송(sealed-in-session)' 모델은 에이전트(agent) 환경에서 비밀 정보를 다루기에 적합한 형태라고 느껴집니다.
Trusty Squire는 바로 그 드라이버입니다. 여러분의 코딩 에이전트가 구동하는 오픈 소스 MCP 서버입니다. 이 서버는 프로젝트에 필요한 서비스들에 가입하고, 모든 키를 에이전트가 다시 읽을 수 없는 금고(vault)에 잠가버립니다. Claude Code, Cursor, Codex에 연결할 수 있습니다. 여기에서 시작하거나, GitHub(https://GitHub.com/trusty-squire/trusty-squire)에서 코드와 — 거짓된 가설들의 무덤인 — STATE.md를 읽어보세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기