브라우저에 API 키 노출? OpenAI, Claude, Gemini 키 2분 만에 확인하세요

앱이 브라우저에서 바로 OpenAI, Claude 또는 Gemini를 호출하면 누구나 DevTools에서 키를 복사할 수 있습니다. 2분이면 되는 점검, 수정 순서, 그리고 코딩 에이전트가 프록시 경로를 테스트하는 방법.

Ashley Innocent

Ashley Innocent

2 November 2026

브라우저에 API 키 노출? OpenAI, Claude, Gemini 키 2분 만에 확인하세요

Apidog 엔터프라이즈

온프레미스 배포

SSO & RBAC

SOC 2 준수

Apidog Enterprise 살펴보기

웹 앱이 브라우저에서 직접 OpenAI, Claude 또는 Gemini를 호출하면 API 키가 브라우저에 노출되며, DevTools를 여는 사람은 누구나 이를 복사할 수 있습니다. 2분이면 확인할 수 있습니다: Network 탭을 열고 AI 요청을 하나 보낸 다음, 제공자로 전송되는 Authorization, x-api-key 또는 x-goog-api-key 헤더를 찾아보세요. 거기서 키가 보인다면 오늘 바로 키를 교체하고 호출을 서버 라우트 뒤로 옮기세요.

아래에는 점검 방법, 수정 순서, 그리고 코딩 에이전트가 수정된 라우트를 테스트하는 방법이 있습니다.

브라우저에 노출된 API 키가 문제인 이유

브라우저에 노출된 API 키는 프런트엔드 코드나 네트워크 트래픽에 포함되어 전송되는 비밀 정보로, 모든 방문자가 읽을 수 있습니다. 브라우저가 키를 전송하면 사용자가 이를 복사해 귀하의 비용으로 제공자를 호출할 수 있습니다.

Google의 Gemini API 키 가이드는 “API 키를 웹 또는 모바일 앱에 직접 하드코딩하지 마세요”라고 말합니다. 왜냐하면 “클라이언트 측 코드에 컴파일된 키는 사용자가 추출할 수 있기” 때문이며, “실제 API 호출을 수행하려면 백엔드 프록시 서버를 실행하세요”라고 안내합니다. OpenAI의 API 키 안전 가이드도 같은 규칙을 제시합니다: 키를 클라이언트에 배포하는 대신 자체 백엔드를 통해 요청을 라우팅하세요.

openai-node README는 “비밀 API 자격 증명이 노출되는 것을 피하기 위해” 브라우저 지원이 기본적으로 비활성화되어 있으며, 이를 켜려면 dangerouslyAllowBrowser: true를 설정해야 한다고 말합니다. Anthropic의 TypeScript SDK도 같은 옵션 이름을 사용하며, 해당 API는 요청에 anthropic-dangerous-direct-browser-access: true 헤더가 있을 때만 브라우저(CORS) 호출을 허용합니다. 이는 출시 당시 Simon Willison이 문서화한 내용입니다.

이 패턴은 AI 앱 빌더와 코딩 에이전트로 빠르게 만든 앱에서 흔합니다. 가장 짧은 작동 경로는 React 컴포넌트에서 키를 VITE_ 또는 NEXT_PUBLIC_ 변수에 넣고 fetch하는 것입니다. 그러면 도난당한 키는 대량 추론을 위해 재판매됩니다. 도난당한 AI API 키와 LLMjacking에 관한 저희 글에서 다룬 것처럼요. 그리고 그 결과는 청구서에서 알게 됩니다.

API 키가 노출되었는지 확인하는 방법 (2분)

Three-step infographic of the 2-minute key exposure check: 1 Network tab with a POST request highlighted, 2 search the JavaScript bundle for sk- and AIza, 3 check env vars prefixed NEXT_PUBLIC_ or VITE_

이 점검은 localhost가 아니라 프로덕션에 대해 실행하세요.

  1. Network 탭을 확인하세요(45초). 사이트를 열고 F12(Mac에서는 Cmd+Option+I)를 누른 뒤 Network 탭을 선택하고 채팅 메시지 전송 같은 AI 기능 하나를 실행하세요. openai, anthropic 또는 googleapis로 필터링하세요. 요청이 api.openai.com, api.anthropic.com 또는 generativelanguage.googleapis.com으로 직접 가면, 해당 요청을 클릭해 Headers를 여세요. Authorization: Bearer, x-api-key, x-goog-api-key 또는 ?key= 쿼리 매개변수에 키가 있으면 노출된 것입니다.
  2. 번들을 검색하세요(45초). DevTools에서 Cmd+Option+F(Windows에서는 Ctrl+Shift+F)를 눌러 로드된 모든 스크립트를 검색하세요. sk-, sk-ant-, AIza 및 dangerouslyAllowBrowser를 검색하세요. 브라우저에 제공되는 .js 파일에서 하나라도 발견되면 아직 요청이 발생하지 않았더라도 유출입니다.
  3. 환경 변수 접두사를 확인하세요(30초). .env 파일과 호스팅 대시보드를 여세요. 빌드 도구는 설계상 일부 변수를 클라이언트 번들에 복사합니다: Vite의 VITE_*(Vite 환경 변수 문서 참조), Next.js의 NEXT_PUBLIC_*, Create React App의 REACT_APP_*, Expo의 EXPO_PUBLIC_*. 이러한 접두사 뒤에 있는 제공자 키는 이름이 무엇이든 공개됩니다.

빌드 출력을 터미널에서 스캔하려면:

# from your project root after a production build
grep -rnoE "sk-(proj-|ant-)?[A-Za-z0-9_-]{20,}|AIza[0-9A-Za-z_-]{35}" dist/ build/ .next/static/ 2>/dev/null
grep -rn "dangerouslyAllowBrowser" dist/ build/ .next/static/ 2>/dev/null

출력이 비어 있으면 통과입니다. Google은 2026년 5월 28일 이후 생성된 새로운 AI Studio 키는 “auth 키”이며 형식이 AIza와 다를 수 있다고 말하므로, 번들에서 generativelanguage.googleapis.com도 검색하세요. 브라우저가 자체 도메인(예: /api/chat)과 통신하고 제공자 호스트 이름이나 키가 어디에도 나타나지 않을 때만 통과입니다.

키가 노출되었을 때 해야 할 일: 수정 순서

먼저 키를 교체하면 오늘의 구멍이 막히고, 아키텍처 변경은 이를 영구히 막습니다.

단계 수행할 작업 이 순서인 이유 시간
1. 키 교체 제공자 콘솔에서 새 키를 만들고 유출된 키를 폐기하세요 이전 키는 이미 복사되었을 수 있으며, 폐기만이 사용을 막는 유일한 방법입니다 5분
2. 호출을 서버 측으로 이동 키를 보관하는 서버 라우트 또는 엣지 함수에 제공자 호출을 넣으세요 새 키가 같은 방식으로 유출되는 것을 막습니다 30~60분
3. 인증 및 사용자별 속도 제한 추가 라우트에 세션을 요구하고 사용자당 요청 수를 제한하세요 이것이 없으면 프록시가 누구나 쓰는 무료 AI 엔드포인트가 됩니다 30분
4. 제공자 지출 한도 설정 각 제공자의 결제 설정에서 예산 또는 지출 한도를 설정하세요 다른 것이 유출될 경우 최악의 청구서를 제한합니다 10분
5. CI에서 스캔 출력 번들에 키 패턴이 나타나면 빌드를 실패시키세요 다음 에이전트가 작성한 기능이 수정을 되돌리는 것을 방지합니다 15분

이전 키를 폐기하기 전에 새 키를 서버 측에 배포하세요. 그렇지 않으면 AI 기능이 중단됩니다. API 키 교체 모범 사례에 관한 저희 가이드에서는 무중단 교체를 다룹니다. 4단계에서 OpenAI의 키 안전 지침은 강제 적용되는 지출 한도를 권장합니다. 경고만으로는 트래픽을 막지 못하기 때문입니다. Google Cloud 결제 예산은 경고를 보내지만 그 자체로 사용을 중단시키지는 않습니다.

OpenAI, Claude 및 Gemini 호출을 서버 라우트 뒤로 옮기는 방법

Diagram comparing an unsafe setup, where the browser holds the API key and calls the AI provider directly while DevTools copies the key, with a safe setup where the browser calls your server route, which holds the key and applies auth, rate limits and a spend cap before calling the provider

백엔드 프록시 라우트는 사용자의 프롬프트를 받고, 서버에서 API 키를 추가하고, 제공자를 호출한 뒤 답변만 반환하는 작은 서버 엔드포인트입니다. 브라우저는 키를 절대 보지 못합니다. 다음은 OpenAI를 위한 최소한의 Next.js 라우트입니다. Claude와 Gemini도 같은 형태를 따릅니다:

// app/api/chat/route.ts (runs on the server only)
import OpenAI from "openai";
import { getSession } from "@/lib/auth";        // your auth helper
import { checkRateLimit } from "@/lib/limits";  // your per-user limiter

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // no NEXT_PUBLIC_ prefix

export async function POST(req: Request) {
  const session = await getSession(req);
  if (!session) return new Response("Unauthorized", { status: 401 });

  if (!(await checkRateLimit(session.userId, 20))) {   // 20 requests per minute
    return new Response("Too many requests", { status: 429 });
  }

  const { message } = await req.json();
  if (typeof message !== "string" || message.length > 4000) {
    return new Response("Bad request", { status: 400 });
  }

  const response = await client.responses.create({
    model: process.env.OPENAI_MODEL!,   // pin the model server-side
    input: message,
  });

  return Response.json({ reply: response.output_text }); // return the answer, nothing else
}

안전하게 만드는 요소는 단순히 숨기는 것이 아닙니다:

Claude와 Gemini의 경우 ANTHROPIC_API_KEY 또는 GEMINI_API_KEY와 함께 해당 SDK를 사용하세요. Google의 웹사이트 및 IP 제한은 도움이 되지만, curl을 가진 사람은 누구나 임의의 Referer 헤더를 보낼 수 있으므로 이를 두 번째 방어 계층으로 취급하세요. 브라우저 호출이 정당한 유일한 경우는 각 사용자가 자신의 키를 붙여 넣는 bring-your-own-key 도구입니다.

코딩 에이전트가 Apidog CLI로 프록시 라우트를 테스트하게 하기

키를 옮기는 것은 절반의 작업입니다. 나머지 절반은 에이전트가 다음 기능을 작성한 후에도 키가 옮겨진 상태로 유지됨을 증명하는 것입니다. 라우트를 만든 에이전트가 Apidog CLI로 테스트하도록 하세요. 한 번 설정하세요:

npm install -g apidog-cli@latest
apidog login --with-token <YOUR_TOKEN>
npx -y skills add https://apidog.com   # pick your agent when prompted

그런 다음 Claude Code, Codex, Cursor 또는 사용 중인 에이전트에 다음 프롬프트를 주세요:

Create Apidog test cases in project <id> for POST /api/chat on our staging
environment that assert:
1. a request without a session cookie returns 401,
2. a valid request returns 200 and the body does not contain "sk-", "sk-ant-",
   "AIza" or "api_key",
3. the 21st request inside 60 seconds from the same user returns 429.
Run them and summarize any failures.

에이전트는 스키마를 읽고, 페이로드를 검증하고, 케이스를 작성하고 실행합니다:

apidog test-case category --project <id>
apidog cli-schema get test-case-create
apidog cli-schema validate test-case-create --file case.json
apidog test-case create --project <id> --file case.json
apidog run --test-case <caseId> --project <id> -e <envId> -r cli,json

두 번째 검증이 가장 중요합니다. 제공자 오류 객체나 디버그 필드가 키를 클라이언트로 다시 에코하기 시작하는 순간 실패하기 때문입니다. 동일한 apidog run ... -r junit 단계를 파이프라인에 넣어 모든 배포가 이를 다시 확인하게 하세요. Apidog CLI CI/CD 가이드에서 설정을 보여주고, Claude Code에서의 Apidog CLI는 에이전트 측을 다룹니다. Apidog의 무료 플랜은 최대 4명의 사용자를 지원하므로 소규모 팀은 유료 좌석 없이 이를 실행할 수 있습니다.

같은 문제의 저장소 측면, 즉 브라우저로 전송되는 대신 Git에 커밋된 키에 대해서는 시크릿 스캐너를 사용한 API 키 유출 확인을 참고하세요. 에이전트가 고객 데이터를 처리하는 도구를 호출한다면, AI 에이전트의 데이터 유출 테스트가 다음 계층을 다룹니다.

키를 계속 노출되게 만드는 흔한 실수

버튼

자주 묻는 질문

브라우저에서 내 OpenAI API 키가 노출되었는지 어떻게 알 수 있나요? DevTools Network 탭을 연 상태에서 AI 기능을 실행하세요. Authorization: Bearer sk-... 헤더와 함께 api.openai.com으로 요청이 가면 노출된 것입니다. 또한 로드된 스크립트에서 sk-와 dangerouslyAllowBrowser를 검색하세요.

API 키를 NEXT_PUBLIC_ 또는 VITE_ 환경 변수에 넣어도 안전한가요? 아닙니다. 해당 접두사는 값을 클라이언트 번들에 복사하기 위해 존재합니다. 그 아래에 저장된 제공자 키는 모든 방문자에게 전송됩니다. 접두사 없는 서버 전용 변수를 사용하고 서버 라우트 내부에서 읽으세요.

Gemini API 키를 내 웹사이트로 제한하면 브라우저에서 안전해지나요? 도움이 되지만, 브라우저 외부의 스크립트는 임의의 Referer 헤더를 보낼 수 있습니다. Google의 자체 가이드도 클라이언트 앱에는 백엔드 프록시를 권장합니다.

키가 노출된 것을 발견했습니다. 먼저 무엇을 해야 하나요? 새 키를 만들고 서버 측에 배포한 다음 이전 키를 폐기하세요. 그다음 호출을 사용자별 속도 제한이 있는 인증된 라우트 뒤로 옮기고, 제공자 지출 한도를 설정하세요.

코딩 에이전트가 이 문제를 수정하고 테스트할 수 있나요? 예. 제공자 호출을 서버 라우트로 옮기도록 요청한 다음, Apidog CLI를 사용해 세션 없이 401, 응답에 키 패턴 없음, 속도 제한 초과 시 429를 검증하는 테스트를 만들고 실행하세요.

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요