클라이언트 측 JavaScript에 노출된 API 키
브라우저로 전달되는 것 — 인라인 스크립트, 번들된 JS, source map — 은 무엇이든 누구나 읽을 수 있습니다. 프런트엔드 코드에 포함된 시크릿 키(OpenAI, Stripe 시크릿, AWS, 데이터베이스 URL)는 사실상 공개된 것입니다.
무엇인가요
빌드 도구는 컴파일 시점에 환경 변수를 인라인합니다. 클라이언트 코드에서 시크릿을 참조하면, 모든 방문자가 다운로드하는 JavaScript 번들에 그 값이 들어가게 됩니다.
중요한 구분이 있습니다: publishable/anon 키(Stripe pk_live, Firebase web config)는 공개되도록 설계되었지만, 시크릿 키(sk_live, service-role 키, 제공업체 API 키)는 그렇지 않으며 절대 브라우저에 도달해서는 안 됩니다.
왜 중요한가요
유출된 제공업체 키는 누구나 여러분의 계정으로 사용량을 쌓게 합니다 — LLM 요금, SMS 요금, 클라우드 비용 — 또는 키가 허용하는 데이터에 접근하게 합니다.
공격자는 키 패턴을 찾으려 대규모로 JavaScript 번들을 수집합니다. 커밋된 시크릿은 보통 배포된 지 몇 분 안에 악용됩니다.
고치는 방법
서드파티 API를 브라우저가 아니라 서버 route나 백엔드에서 호출하세요. 브라우저는 여러분의 endpoint와 통신하고, endpoint가 키를 보유합니다.
// app/api/summarize/route.ts (server-only)
export async function POST(req: Request) {
const { text } = await req.json();
const r = await fetch("https://api.openai.com/v1/responses", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
body: JSON.stringify({ model: "gpt-4o-mini", input: text }),
});
return Response.json(await r.json());
}Next.js에서는 NEXT_PUBLIC_ 접두사가 붙은 변수만 브라우저에 노출됩니다. 그 접두사 뒤에 시크릿을 절대 두지 말고, 공개해도 안전한 값에만 사용하세요.
시크릿이 브라우저로 전달됐다면 이를 폐기하고 재발급하세요. 다음 빌드에서 제거해도 이미 수집된 사본은 무효화되지 않습니다.
자주 묻는 질문
프런트엔드의 Stripe publishable 키(pk_live)가 문제인가요?
아니요 — publishable 키는 공개되도록 만들어졌습니다. 위험한 것은 서버 측에 있어야 하는 시크릿 키(sk_live)입니다.
키를 그냥 난독화하면 안 되나요?
안 됩니다. 난독화는 손쉽게 되돌릴 수 있습니다. 유일한 해결책은 시크릿을 클라이언트에서 완전히 배제하는 것입니다.