본문 바로가기
문서비밀 키가 사용자한테 그대로 노출돼요

비밀 키가 사용자한테 그대로 노출돼요

API 키·관리자 키·비밀번호가 브라우저로 가는 코드나 NEXT_PUBLIC_ 환경변수에 들어 있어서, 사이트 방문자 누구나 꺼내 쓸 수 있어요.

한눈에
위험도
치명적(Critical)
CWE
CWE-798 · 하드코딩된 자격증명
OWASP
A05:2021
vg-supabase-service-role-client · vg-public-env-secret · vg-llm-api-key-hardcoded

이게 무슨 말이냐 (쉽게)

웹앱은 브라우저에서 도는 코드(프론트)서버에서만 도는 코드(백)가 있어요. 비밀 키는 서버에만 있어야 합니다. 그런데 다음 셋 중 하나면 키가 사용자에게 새요.

헷갈리지 마세요: anon key와 STRIPE_PUBLISHABLE_KEY는 원래 공개되는 키라 NEXT_PUBLIC_이어도 괜찮아요. 문제는 비밀이어야 할 키가 공개로 새는 경우입니다.

실제로 어떻게 터지나 (시나리오)

당신이 OpenAI로 챗봇을 붙였어요. 빠르게 만들려고 NEXT_PUBLIC_OPENAI_API_KEY에 키를 넣었죠. 사이트를 열면 그 키가 자바스크립트 번들 안에 평문으로 들어갑니다. 누군가 브라우저 소스 검색으로 sk- 만 쳐도 키가 나와요. 그 키로 당신 계정에 마음껏 API를 호출합니다.

최악의 경우

나한테 해당되나? (체크)

→ 하나라도 체크되면 즉시 아래대로 고치고, 키를 새로 발급(회수) 하세요. 이미 노출된 키는 고친다고 안전해지지 않아요.

어떻게 고치나 (복붙)

① 접두어를 떼고 서버 전용으로

- NEXT_PUBLIC_OPENAI_API_KEY=sk-proj-xxxx   # 브라우저로 샘
+ OPENAI_API_KEY=sk-proj-xxxx               # 서버에서만 읽힘

② 호출은 서버에서 (Next.js Route Handler 예시)

// app/api/chat/route.ts  ← 서버에서만 실행됨
import OpenAI from "openai";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // 접두어 없음
export async function POST(req: Request) {
  // 여기서 호출하고 결과만 프론트로 돌려줌
}

③ service_role은 절대 프론트에 두지 않기 — 관리자 작업은 서버 라우트에서만.

// 서버 전용 파일에서만
const admin = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);

④ 이미 노출된 키는 회수·재발급 — OpenAI·Supabase·Stripe 대시보드에서 기존 키 폐기 → 새 키 발급. 그리고 .env는 .gitignore에 넣어 깃에 안 올라가게.

고친 뒤 확인

  1. 01

    번들에서 검색해 보기

    빌드 후 브라우저에서 사이트 열기 → 소스 검색 sk- , service_role → 안 나와야 정상.

  2. 02

    깃 기록 훑기

    git grep -nE "sk-(ant-)?[A-Za-z0-9_-]{20,}" → 결과가 없어야 합니다.

  3. 03

    도난 여부 확인

    노출됐던 키는 대시보드에서 마지막 사용 시각을 확인. 낯선 IP·시간이면 이미 도난 가능 → 즉시 폐기.

규제 연결

자격증명 노출은 ISMS-P 2.7(암호화·비밀관리)·2.5(인증·권한), PIPA 제29조 안전조치의무에 직접 걸립니다. 리포트는 키를 실제로 회수했는지까지 한국어로 확인해 드립니다. 조항 매핑은 신뢰 센터의 참고 대조표에 있고, 리포트에 자동으로 붙지는 않습니다.

출처/더 읽기: OWASP A05:2021(Security Misconfiguration), CWE-798, Next.js 환경변수 공식 문서, Supabase 키 종류 문서. 이 카드는 우리가 직접 작성했습니다.

문서 목록으로