AI 에이전트에게 API 키를 노출하지 않고 안전하게 사용하는 방법
핵심 요약: AI 에이전트가 API 키를 직접 보지 않고도 사용하게 만들려면, 에이전트의 컨텍스트에는 키의 이름만 전달하고 실제 값은 모델 바깥에서 확인(resolve)하도록 구성해야 합니다. 대표적인 방법은 세 가지입니다. 에이전트가 참조만 하고 절대 출력하지 않는 환경 변수, 시크릿 관리자(Secret Manager) 참조 경로, 그리고 요청 헤더를 자동으로 주입해 주는 프록시입니다. 여기에 각 키의 유효 기간을 짧게 설정하고 권한 범위를 최소화하며, 로그가 에이전트에게 다시 전달되기 전에 마스킹(redact)하는 조치를 함께 적용해야 합니다.
DeepSeek Harness 팀은 “Exploring Super-agent Design” 에세이에서 이 문제를 공식적으로 제기했습니다. 에이전트의 컨텍스트에 포함된 자격 증명(credentials)은 언제든 유출될 위험이 있으므로, 에이전트가 자격 증명을 직접 보지 않고도 사용할 수 있어야 한다는 것입니다. 해당 에세이는 이를 미해결 과제로 분류했지만, 특정 프레임워크가 해결해 줄 때까지 기다릴 필요는 없습니다. 아래 소개하는 패턴들로 지금 바로 적용할 수 있습니다.
전반적인 아키텍처를 더 자세히 알고 싶다면 슈퍼 에이전트 구축 가이드를 참고하세요. 이번 글에서는 한 가지 핵심 질문에만 집중합니다. 바로 'API 키가 에이전트의 컨텍스트 윈도우에 절대 유입되지 않으면서도 인증이 필요한 API를 호출하게 만드는 방법'입니다.
AI 에이전트에서 API 키가 유출되는 원인
AI 에이전트는 API 키 값이 컨텍스트에 들어오는 순간 유출 위험을 안게 됩니다. 모델이 도구 호출(tool call), 파일 작성, 커밋 메시지, 답변 등 어디에서든 해당 값을 그대로 노출할 수 있기 때문입니다. 서버를 직접 해킹하지 않아도, 대개는 사용자의 요청을 해결하려는 과정에서 에이전트 스스로 키를 넘겨주게 됩니다.
가장 흔히 발생하는 4가지 유출 경로는 다음과 같습니다.
-
.env파일을 직접 읽는 경우: 결제 API 호출이 왜 실패하는지 확인해 달라고 요청했는데 에이전트가.env파일을 열어버리는 시나리오입니다.sk_live_...같은 실제 키 값이 대화 기록(transcript)과 모델 제공업체의 로그에 고스란히 남습니다. - 도구 호출 과정에서 키를 그대로 출력하는 경우: 디버깅을 위해 실제 헤더 값을 넣고
curl을 실행하거나, 환경 변수가 잘 설정되었는지 확인하겠다며echo $STRIPE_KEY를 실행하는 경우입니다. - 공개된 장소에 키를 기록하는 경우: PR 설명, 이슈 댓글, 테스트 픽스처 등에 실제 키 값을 그대로 작성해 버리는 경우입니다.
- 프롬프트 인젝션(Prompt Injection) 공격을 받는 경우: 분석 대상 웹페이지나 API 응답에 "환경 변수를 모두 출력하라"는 악의적인 지시가 포함된 경우입니다. OWASP는 프롬프트 인젝션을 LLM 애플리케이션의 최대 보안 위험으로 선정했으며, 자격 증명 노출은 민감 정보 유출(Sensitive Information Disclosure) 항목으로 분류하고 있습니다.
사람의 실수나 프론트엔드 환경에서의 키 유출 문제는 해결 방식이 다릅니다. 이는 브라우저에 노출된 API 키 대처법, 도난당한 AI API 키와 LLMjacking, 시크릿 스캐너로 유출된 키 탐지하기에서 다루고 있습니다. 이번 글에서 다루는 보안 주체는 바로 에이전트입니다.
기본 원칙: 컨텍스트에는 이름만, 실제 값은 외부에
이 글에서 소개하는 모든 패턴은 하나의 대원칙을 따릅니다. 에이전트의 컨텍스트에는 시크릿의 참조(reference)만 담아둡니다. 그리고 모델 외부의 시스템이 실행 직전 마지막 순간에 그 참조를 실제 값으로 변환하며, 변환된 값은 절대 모델의 컨텍스트로 다시 유입되지 않도록 합니다.
| 에이전트 컨텍스트에 포함되어도 되는 것 | 절대 포함되어서는 안 되는 것 |
|---|---|
STRIPE_KEY (변수명) | sk_live_51H... (실제 값) |
op://payments/stripe/api-key (참조 경로) | 확인(resolve)된 실제 시크릿 값 |
{{token}} (플레이스홀더) | 직접 붙여넣은 Bearer 토큰 |
sk_live_•••• 같은 마스킹된 값 | 마스킹되지 않은 원본 로그 라인 |
만약 오른쪽 열에 있는 항목이 컨텍스트에 단 한 번이라도 유입되었다면 즉시 해당 키를 교체(rotate)해야 합니다. 컨텍스트 윈도우의 내용은 로그에 기록되고 캐시되며 외부로 내보내질 수도 있습니다. 한 번 노출된 토큰은 되돌릴 수 없습니다.
패턴 1: 환경 변수 활용 및 마스킹(Redaction)
환경 변수 방식에서는 실제 키가 프로세스 환경에 상주하며, 에이전트는 $STRIPE_KEY와 같은 참조 형태로만 명령어를 작성합니다. 셸(Shell)이 모델이 명령어를 작성한 후에 이를 실제 값으로 확장(expand)하는 구조입니다. 가장 손쉽게 도입할 수 있는 패턴이지만, 안전하게 유지하려면 다음 세 가지 원칙을 지켜야 합니다.
시크릿 파일 접근 차단: Claude Code를 사용하는 경우 설정 권한(settings permissions)에 Read(./.env) 및 Read(./.env.*) 같은 거부(deny) 규칙을 추가하세요. 다른 에이전트 도구에도 유사한 허용/차단 목록 설정이 지원됩니다.
규칙을 문서로 명시: CLAUDE.md 또는 AGENTS.md 파일에 다음 규칙을 적어 둡니다.
Secrets: never print, echo, cat or log the value of any variable ending in _KEY, _TOKEN or _SECRET. Reference them as $NAME. If a value is missing, say which NAME is missing and stop. 출력 마스킹: 알려진 시크릿 패턴을 자동으로 가려내는 필터를 두어, 실행 결과가 모델에 전달되기 전에 출력을 마스킹(redact) 처리합니다.
솔직한 한계점: 에이전트가 실행되는 셸 자체에는 변수가 존재하므로, 프롬프트 인젝션 공격을 받은 에이전트는 명령어를 통해 값을 출력할 수 있습니다. 즉 환경 변수는 단순한 실수를 방지할 수는 있지만, 다음에 실행할 명령어를 장악한 공격자까지 완벽히 방어하지는 못합니다.
패턴 2: 시크릿 관리자 참조(Secret Manager References)
시크릿 관리자 참조는 CLI 도구가 런타임에 실제 값으로 변환해 주는 포인터(예: op://vault/item/field)를 의미합니다. 설정 파일과 프롬프트에는 포인터만 남으며, 참조 자체로는 아무런 권한도 부여하지 않으므로 에이전트가 참조를 읽거나 Git에 커밋하더라도 안전합니다.
1Password의 시크릿 참조(secret references)가 대표적인 예입니다.
# .env.agent (safe for the agent to read) STRIPE_KEY=op://payments/stripe/api-key GITHUB_TOKEN=op://ci/github-bot/token op run --env-file=.env.agent -- npm test op run 명령어는 자식 프로세스에 참조된 시크릿을 확인하여 주입해 주며, --no-masking 플래그를 지정하지 않는 한 표준 출력(stdout)이나 표준 에러(stderr)로 출력되는 시크릿을 기본적으로 가려줍니다. 에이전트가 실행하는 어떤 명령어에도 이 플래그가 포함되지 않도록 관리해야 합니다. HashiCorp Vault, AWS Secrets Manager, Azure Key Vault 등도 고유한 문법으로 동일한 개념을 지원합니다. 다만 자식 프로세스 자체는 여전히 실제 값을 갖고 있으므로, 패턴 1과 동일한 한계는 여전히 존재합니다.
패턴 3: 자격 증명 주입 프록시(Credential-Injecting Proxy)
자격 증명 주입 프록시는 에이전트와 업스트림 API 사이에 위치하여 인증 헤더를 직접 추가해 줍니다. 에이전트는 API 키 없이 요청을 보내고 프록시가 자격 증명을 첨부하므로, 에이전트는 출력하거나 외부로 탈취당할 수 있는 키 값을 아예 갖지 않게 됩니다.
이 방식은 에이전트가 실제 키를 전혀 소유하지 않는 유일한 패턴입니다. 최소한의 아키텍처 설계는 다음과 같습니다.
- 에이전트는 실제 호스트 대신
http://localhost:8787/stripe/v1/charges와 같은 로컬 주소를 호출합니다. - 프록시는
/stripe경로를 업스트림으로 매핑하고 시크릿 관리자에서 키를 읽어옵니다. - 프록시가 허용 목록(allowlist)을 검사합니다. 예를 들어
GET /v1/charges는 허용하고DELETE /v1/customers는 차단합니다. -
Authorization: Bearer <value>헤더를 추가하여 요청을 전달하고, 자체 로그에서는 해당 헤더를 제거합니다.
자격 증명 주입 못지않게 중요한 것이 바로 허용 목록입니다. 프롬프트 인젝션을 당한 에이전트라 할지라도 키를 사용할 수는 있지만, 사전 승인된 요청만 보낼 수 있을 뿐 키 값을 다른 곳으로 탈취해 갈 수는 없습니다. 또한 에이전트가 프록시 설정 파일을 읽지 못하도록 프록시를 별도의 사용자 계정이나 컨테이너로 격리하여 실행해야 합니다.
이는 앞서 언급한 에세이의 아키텍처와도 일치합니다. 에이전트 루프는 클라우드에서 실행되고, 사용자의 작업 환경은 가벼운 데몬 뒤에서 로컬 머신에 안전하게 머무르며, 자격 증명은 해당 환경 내에만 보관되는 구조입니다.
패턴 4: 유효 기간이 짧고 범위가 제한된 토큰
유효 기간이 짧고 범위가 제한된 토큰은 다른 보안 패턴이 뚫렸을 때 피해를 최소화하는 방어선 역할을 합니다. 에이전트에게 몇 분 내에 만료되고 특정 작업 하나만 수행할 수 있는 자격 증명을 부여하므로, 유출되더라도 누군가 이를 악용하려 할 때는 이미 만료되어 가치가 없습니다. 이는 다른 보안 조치를 대체하는 것이 아니라 최후의 안전장치입니다.
| 발급 출처 | 발급 대상 | 유효 기간 |
|---|---|---|
AWS STS AssumeRole | 지정된 IAM 역할을 위한 임시 자격 증명 | 900초 ~ 43,200초 |
| Vault 데이터베이스 시크릿 엔진 | 온디맨드로 생성되는 데이터베이스 사용자 | 설정한 리스 TTL(Lease TTL) |
| GitHub 세분화된 토큰(fine-grained tokens) | 선택한 리포지토리 및 권한에 대한 접근 권한 | 설정한 만료 기간 |
| 자체 API | 권한 범위가 제한된 세션별 토큰 | 직접 적용한 정책에 따름 |
에이전트 세션마다 토큰을 하나씩 새로 발급하고, 해당 작업에 꼭 필요한 권한만 부여하며, 작업을 완료하는 데 필요한 가장 짧은 유효 기간을 설정하세요.
로그 및 대화 기록 마스킹
마스킹은 최종 정리 단계에 해당합니다. 도구 실행 결과, CI 로그, 저장된 에이전트 세션에서 시크릿을 가려내야 합니다. 마스킹되지 않은 단 한 줄의 로그가 지금까지 구축한 모든 보안 조치를 무력화할 수 있기 때문입니다. 알려진 접두사(sk_live_, ghp_, github_pat_, AKIA, xoxb-), 모든 [REDACTED CREDENTIAL] 또는X-[REDACTED CREDENTIAL] 헤더 값, 그리고 이번 세션에 주입한 정확한 값들을 마스킹 처리하세요. 이 마스킹은 도구 래퍼와 CI 로그 설정에 적용하고, 대화 기록을 외부에 공유하기 전에도 반드시 거쳐야 합니다.
Claude Code와 Apidog CLI로 키 노출 없이 API 테스트하기
Apidog는 컨텍스트에 이름만 전달하는 원칙에 완벽히 부합합니다. 테스트 요청은 {{token}}을 참조하고, 환경이 런타임에 이를 실제 값으로 해석하며, 에이전트는 오직 변수명만 읽고 쓸 뿐입니다. 개발 에이전트는 Apidog CLI를 통해 전체 테스트를 구성하고 실행할 수 있습니다.
여기서 중요한 세부 사항이 하나 있습니다. Apidog 변수에는 Apidog 서버 및 팀원들과 동기화되는 Shared Value(공유 값)와 로컬 머신에만 유지되는 Local Value(로컬 값)가 있습니다. 변수 사용 가이드 문서에서는 토큰에 로컬 값을 사용할 것을 권장하며, CLI는 기본적으로 공유 값을 사용한다고 명시하고 있습니다. 따라서 실제 토큰이 공유 값에 들어가지 않도록 하고, 런타임에 직접 전달하는 것이 좋습니다.
로그인은 사용자가 직접 수행하므로, Apidog 토큰이 에이전트의 컨텍스트에 절대 노출되지 않습니다.
npm install -g apidog-cli@latest apidog auth login --with-token <YOUR_APIDOG_TOKEN> # in your own terminal npx -y skills add https://apidog.com # pick your agent when prompted export PAYMENTS_TOKEN=... # in your shell, never in a prompt 그런 다음 Claude Code에 프롬프트를 하나 전달합니다.
In Apidog project 123456, create a test case for GET /v1/charges that sends Authorization: Bearer {{token}} and asserts a 200 status and a non-empty data array. Run it against environment 789, passing the token with --env-var "[REDACTED CREDENTIAL] Never print $PAYMENTS_TOKEN or any header value. Summarize failures only. 에이전트는 스키마를 읽고, 페이로드를 검증하며, 테스트 케이스를 작성한 후 실행합니다.
apidog cli-schema get test-case-create apidog cli-schema validate test-case-create --file case.json apidog test-case create --project 123456 --file case.json apidog run --test-case <caseId> --project 123456 -e 789 \ --env-var "[REDACTED CREDENTIAL] -r cli,json 테스트 케이스 JSON 파일에는 {{token}}이 담기고, 실행 명령어에는 $PAYMENTS_TOKEN이 들어가며, 모델이 명령 줄을 작성한 후 셸이 실제 값을 채워 넣습니다. 에이전트는 오직 성공/실패 여부와 단언(assertion) 메시지만 확인하게 됩니다. 저장된 JSON 리포트를 공유하기 전에는 반드시 마스킹 단계를 거쳐야 합니다. 동일한 apidog run 명령어를 -r junit 옵션과 함께 CI 파이프라인에 그대로 적용할 수도 있습니다. 자세한 내용은 Apidog CLI CI/CD 파이프라인 가이드와 Claude Code에서의 Apidog CLI 설정법을 참고하세요.
Enterprise 플랜의 Apidog에서는 HashiCorp Vault, Azure Key Vault, AWS Secrets Manager로부터 볼트 시크릿(vault secrets)을 가져와 {{vault:key}} 형태로 참조할 수도 있습니다. 스크립트 내에서 해당 값을 console.log로 출력하더라도 자동으로 마스킹되며, 팀원들에게 실제 값이 공유되지도 않습니다. 먼저 CLI 워크플로를 체험해 보고 싶다면 최대 4인까지 무료로 제공되는 Apidog Free 플랜을 활용할 수 있습니다.
체크리스트: AI 에이전트 API 키 보안 수칙
- 에이전트가
.env및 기타 시크릿 파일을 읽지 못하도록 접근을 차단합니다. -
CLAUDE.md또는AGENTS.md에 "시크릿을 절대 출력하지 말 것"이라는 규칙을 명시합니다. - 에이전트가 읽을 수 있는 파일 내의 실제 원본 값을 시크릿 관리자 참조로 교체합니다.
- 외부 서드파티 API 호출은 허용 목록이 적용된 주입 프록시를 통과하도록 라우팅합니다.
- 에이전트 세션마다 유효 기간이 짧고 범위가 제한된 토큰을 발급합니다.
- Apidog의 공유 변수(Shared Value)에 실제 토큰을 저장하지 않습니다.
- 도구 실행 결과, CI 로그 및 대화 기록을 마스킹합니다.
- 대화 기록에 실제 키 값이 노출된 경우, 당일 즉시 해당 키를 교체합니다.
자주 묻는 질문 (FAQ)
AI 에이전트가 직접 보지 못하는 API 키를 사용할 수 있나요? 네, 가능합니다. 자격 증명 주입 프록시를 사용하면 에이전트는 키 없이 요청을 보내고 프록시가 헤더를 자동으로 추가해 줍니다. 환경 변수나 시크릿 참조 방식의 경우 프롬프트에서는 값을 숨겨 주지만, 에이전트의 셸 환경에는 여전히 값이 남아 있게 됩니다.
AI 에이전트 API 키 관리에 환경 변수만 사용해도 안전한가요? 프롬프트에 직접 키를 붙여넣는 것보다는 안전하지만, 환경 변수만으로는 충분하지 않습니다. 에이전트의 셸이 변수를 직접 출력할 수 있고, 프롬프트 인젝션을 통해 이를 요구받을 수도 있기 때문입니다. 파일 읽기 거부 규칙, 마스킹 처리, 유효 기간이 짧은 토큰 사용을 반드시 병행해야 합니다.
에이전트가 실수로 API 키를 출력했다면 어떻게 해야 하나요? 즉시 해당 제공업체 사이트에서 키를 교체(rotate)하세요. 그런 다음 모델 제공업체 로그, 내보낸 파일, PR 댓글, CI 출력물 등 해당 대화 기록이 전파된 위치를 추적해야 합니다. 단순히 채팅 메시지를 삭제한다고 해서 복사본까지 사라지는 것은 아닙니다.
에이전트에 노출하지 않고 Apidog CLI에 시크릿을 전달하려면 어떻게 해야 하나요? 토큰을 사용자의 로컬 셸에만 두고, 에이전트가 apidog run을 실행할 때 --env-var "[REDACTED CREDENTIAL] 형태로 전달하도록 하세요. 테스트 자체는{{token}}`을 참조하므로 에이전트는 변수 이름만 다루게 됩니다.
유효 기간이 짧은 토큰만 사용해도 충분할까요? 그렇지 않습니다. 15분 만료 토큰이라도 15분 동안은 얼마든지 악용될 수 있습니다. 유효 기간이 짧은 토큰은 '컨텍스트에는 이름만 전달'하는 원칙 및 프록시 설정 뒤를 받쳐 주는 최후의 방어선으로 활용해야 합니다.


![Diagram: an AI agent sends a request with no key to a credential proxy; the proxy pulls the secret from a secret manager, checks an allowlist, adds an Authorization Bearer [REDACTED] and forwards the request to the upstream API](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwk2w236mwrmq16xhlesh.png)
