본문으로 건너뛰기

Zero Token Architecture: AI 에이전트에게 진짜 API 키를 보여줄 필요가 없는 이유

· 약 4분
nilbox
The secure desktop runtime for AI agents

핫테이크 하나 던져볼게요: AI 에이전트 보안 가이드는 대부분 잘못된 문제를 풀고 있습니다.

우리는 런타임 샌드박스를 수 시간에 걸쳐 구성합니다. 파일시스템을 잠그고, 패키지를 하나하나 감사합니다. 에이전트를 Docker로 감싸고, Docker를 VM으로 감싸고, VM을 정책으로 감쌉니다.

그리고는 에이전트에게 평문 API 키를 건네주고 "이제 안전하다"고 말합니다.

토큰을 보호하려 하지 마세요. 처음부터 넘겨주지 않으면 됩니다.

TL;DR

  • AI 에이전트가 볼 수 있는 토큰은 언제든 유출될 수 있습니다. Prompt Injection과 임의 패키지 실행이 공존하는 한.
  • 토큰을 건넨 뒤 보호하는 대신, 값이 이름과 동일한 가짜 토큰을 에이전트에게 전달하세요.
  • 에이전트가 API를 호출하는 순간 경계에서 요청을 가로채 진짜 토큰으로 교체합니다.
  • 가짜 토큰이 유출되더라도 공격자가 얻는 건 의미 없는 문자열뿐. 진짜 토큰은 신뢰 프로세스 밖으로 나가지 않습니다.

"토큰을 보호한다"는 접근의 한계

AI 에이전트 환경은 보통 이렇게 생겼습니다:

OPEN_API_TOKEN=sk-proj-1a2b3c4d5e...

진짜 토큰이 그대로 노출되어 있습니다. 에이전트는 이걸 읽어 Authorization: Bearer 헤더에 담아 호출합니다. 문제없어 보이죠 — 다음 중 하나가 일어나기 전까지는.

  • Prompt Injection 공격이 에이전트를 설득해 다음 응답에 echo $OPEN_API_TOKEN을 실행하게 만듭니다.
  • 에이전트가 설치한 악성 npm/pip 패키지process.env를 읽어 어딘가의 서버로 전송합니다.
  • 에이전트가 로그 파일에 요청 헤더를 기록하고, 그게 외부로 새나갑니다.
  • 툴 호출 결과에 모델이 "도움이 된다고 판단해서" 토큰을 포함시킵니다.

샌드박스, 권한 프롬프트, 이그레스 필터링, 감사 로그 — 우리가 손에 닿는 모든 완화 조치는 이미 잘못된 전제 이후에 작동합니다. 잘못된 전제란: 신뢰할 수 없는 프로세스 안에 시크릿이 존재한다는 것.

임의의 모델 생성 코드를 실행하는 프로세스 안에서 값을 완벽하게 격리하는 건 불가능합니다. 그러니 그 시도를 멈추세요.

패러다임의 전환

다른 질문을 던져보겠습니다:

처음부터 에이전트에게 진짜 토큰을 주지 않으면 어떻게 될까요?

불가능하게 들립니다. API 호출에는 토큰이 필요하니까요. 하지만 에이전트에게 필요한 건 진짜 토큰이 아닙니다 — 호출이 성공하기만 하면 됩니다. 요청이 나가는 순간 다른 무언가가 진짜 토큰으로 교체해준다면, 에이전트 입장에서는 아무것도 달라진 게 없습니다.

그 "다른 무언가"가 바로 에이전트와 LLM 사이에 위치한 작은 프록시입니다. **경계(Boundary)**라고 부르겠습니다.

기존 방식

# 에이전트 환경 변수
OPEN_API_TOKEN=sk-proj-1a2b3c4d5e...

진짜 토큰이 에이전트 안에 있습니다. 에이전트가 뚫리면 토큰도 뚫립니다.

Zero Token 방식

# 에이전트 환경 변수
OPEN_API_TOKEN=OPEN_API_TOKEN

오타가 아닙니다. 변수의 값이 변수의 이름과 동일합니다. 에이전트는 이 값을 읽어 Authorization: Bearer OPEN_API_TOKEN을 만들고 요청을 보냅니다. 뭔가 이상하다는 걸 전혀 모릅니다.

경계는 나가는 요청을 가로채, 플레이스홀더를 인식하고, 에이전트 바깥에 암호화되어 있는 진짜 토큰으로 교체한 뒤 업스트림으로 전달합니다.

┌───────────┐ OPEN_API_TOKEN ┌──────────┐ sk-proj-real ┌──────┐
│ Agent │ ───────────────▶ │ Boundary │ ──────────────▶ │ LLM │
└───────────┘ └──────────┘ └──────┘
▲ │
│ response │
└──────────────────────────────────────────────────────────────┘

에이전트 입장: 완전히 정상적인 요청, 완전히 정상적인 응답. 공격자 입장: 훔칠 게 없습니다.

해커 시나리오

최악의 상황이 벌어졌다고 가정해보겠습니다. Prompt Injection이든 악성 패키지든 — 공격자가 에이전트 환경 전체를 탈취했습니다.

기존 방식:

OPEN_API_TOKEN=sk-proj-1a2b3c4d5e...

게임 오버. 과금 사고, 토큰 교체 소동, 새벽 3시의 알림.

Zero Token 방식:

OPEN_API_TOKEN=OPEN_API_TOKEN

공격자가 얻은 건 문자열 하나입니다. 이걸로 LLM을 호출할 수 없고, 계정에 요금을 청구할 수 없고, 어떤 벤더의 토큰인지조차 알 수 없습니다.

유출은 일어났습니다. 다만 유출된 값을 쓸모없게 만들었을 뿐입니다.

OTP나 Macaroon과 같은 논리입니다: 시크릿은 언젠가 탈출한다고 가정하고, 탈출하더라도 공격자에게도 당신에게도 아무 비용이 없도록 설계하는 것.

지금 이 순간 왜 중요한가

세 가지 흐름이 충돌하고 있습니다:

  1. 에이전트가 신뢰할 수 없는 코드를 실행합니다. 툴 사용, 코드 인터프리터, "스킬 설치" 플로우 덕분에 에이전트 프로세스는 일상적으로 임의 입력을 실행합니다.
  2. Prompt Injection은 해결되지 않았습니다. 더 나은 시스템 프롬프트로 해결될 문제가 아닙니다. 에이전트 프로세스는 항상 적대적이라고 가정하세요.
  3. 토큰은 돈입니다. 유출된 OpenAI/Anthropic 키는 단순한 자격증명 침해가 아니라 청구서입니다.

모든 AI 에이전트 스택이 환경 변수에 진짜 토큰을 담아 배포합니다. 12-factor 앱 방식이 그러하니까요. 하지만 에이전트는 12-factor 앱이 아닙니다. 샌드박스는 "조심하겠다고 약속한 언어 모델"인 채로 임의 모델 출력을 실행합니다.

더 나은 샌드박스가 해법이 아닙니다. 샌드박스에 시크릿을 넣지 않는 것이 해법입니다.

직접 구현하려면

에이전트 하네스를 직접 만드는 경우:

  • 에이전트와 업스트림 API 사이에 로컬 HTTP 프록시를 배치합니다.
  • 에이전트에게는 플레이스홀더 토큰(KEY=KEY 형식)을 줍니다.
  • 진짜 시크릿은 에이전트 프로세스 외부에 저장합니다 — OS 키체인, 별도 데몬 등.
  • 프록시에서 플레이스홀더를 감지하면 진짜 Bearer 토큰으로 교체 후 전달합니다.
  • 플레이스홀더를 통하지 않은 요청은 거부합니다 — 에이전트가 임의 URL을 호출하려는 시도도 차단됩니다.

직접 구현하고 싶지 않다면, nilbox가 이 아이디어를 뼈대로 만든 오픈소스 AI 에이전트 데스크탑 런타임입니다. 프록시, VM 격리, 암호화된 토큰 저장소를 묶어 제공하므로, 설치한 에이전트가 원하더라도 키를 볼 수 없습니다. 자세한 내용은 Zero Token Architecture 문서에서 확인하세요.

마치며

AI 에이전트 보안 논의는 항상 "에이전트에게 건넨 토큰을 어떻게 보호할까?"로 프레이밍됩니다. 그게 잘못된 질문입니다.

올바른 질문은: 애초에 왜 토큰을 줬나요?

에이전트가 갖고 있지 않으면, 유출할 수 없습니다. 나머지는 모두 후속 문제입니다.