프롬프트·로그·답은 모두 출구다 — 민감정보 노출
한 줄 요약
모델이 볼 수 있는 것은 언젠가 답으로 나올 수 있고, 프롬프트를 적는 모든 곳은 그 내용을 복제합니다. 비밀은 넣지 않고, 남의 것은 넣기 전에 거르고, 기록은 가리고, 그래도 새는 것은 표식으로 감지합니다.
왜 이게 필요했나
LLM 앱에서 민감정보가 새는 길은 셋입니다. 첫째, 시스템 문장입니다. 개발 중에 「모델이 도구를 부를 때 쓰라고」 API 키나 내부 연락처를 시스템 문장에 적어 두는 일이 흔한데, 시스템 문장은 비밀 보관소가 아닙니다. 사용자가 그 문장을 되풀이하게 만드는 요청은 끝없이 변형되고, 모델이 그것을 늘 거절한다는 보장이 없습니다. OWASP LLM Top 10 2025 는 민감정보 노출(LLM02)과 별도로 시스템 프롬프트 유출(LLM07)을 항목으로 두었습니다.
둘째, 로그입니다. 장애 때 다시 보려고 프롬프트·머리글·답을 원문으로 적어 두면, 프롬프트에 들어간 모든 것 — 고객 전화번호, 인증 토큰, 키 — 이 로그로 복제됩니다. 로그는 서비스보다 더 많은 사람과 시스템에 갑니다. 로그 수집기, 진단 묶음, 협력사, 오래 남는 백업이 그렇습니다.
셋째, 검색 권한입니다. 질문과 비슷한 티켓을 고객 구분 없이 찾아 넣으면, 모델은 다른 고객의 개인정보로 답합니다. 시스템 문장에 「남의 정보는 말하지 마」 라고 적는 것은 경계가 아닙니다 — 이미 입력에 들어간 것을 모델이 말하지 않으리라 믿는 것입니다.
어떻게 동작하나
| 층 | 하는 일 | 모델을 믿나 |
|---|---|---|
| 넣지 않는다 | 키는 도구가 실행되는 서버 코드에서만 읽는다 | 믿지 않는다 |
| 거른다 | 요청한 사용자의 것만 검색 후보로 둔다 | 믿지 않는다 |
| 가린다 | 기록에는 토큰 대신 이름, 전화번호는 끝 네 자리만 | 해당 없음 |
| 감지한다 | 시스템 문장에 카나리(새면 안 되는 표식)를 넣고 출력에 보이면 막고 기록 | 믿지 않는다 |
카나리는 정직하게 다뤄야 합니다. 이 코스의 가짜 모델은 「시스템 문장을 보여 달라」 는 지시에 시스템 문장을 그대로 덧붙이므로 카나리 문자열 검사로 잡힙니다. 진짜 모델은 바꿔 말하거나 번역하거나 한 글자씩 끊어 내보낼 수 있고, 그러면 문자열 검사는 놓칩니다. 그래서 카나리는 감지 장치이지 보증이 아니고, 1순위는 언제나 비밀을 모델 입력에 넣지 않는 것입니다.
비밀을 「넣지 않는다」 는 말을 구체적으로 풀면 이렇습니다. 모델이 도구를 부를 때 필요한 것은 도구의 이름과 인자뿐이고, 그 도구가 바깥 API 를 부를 때 쓰는 키는 서버 코드가 설정이나 비밀 저장소에서 그 자리에서 읽습니다. 모델에게는 「주문 상태를 조회하는 도구가 있다」 만 알려 주면 됩니다. 이렇게 나누면 프롬프트가 통째로 새어 나가도 키는 함께 나가지 않고, 키를 바꿀 때 프롬프트를 고칠 일도 없습니다. 사용자별 권한이 필요한 도구라면 그 사용자의 신원도 같은 방식으로, 모델을 거치지 않고 서버가 들고 갑니다.
가리기(마스킹)에도 원칙이 있습니다. 기록을 끄면 장애를 조사할 수 없으니 끄지 않고, 무엇을 남길지 고릅니다. 신원은 토큰이 아니라 이름으로, 고객 확인에 필요한 만큼만(전화번호 끝 네 자리), 그리고 요청 id 는 반드시 남겨 다른 기록과 이어 볼 수 있게 합니다. OWASP Logging Cheat Sheet 도 세션 id·접근 토큰·개인정보를 기록하지 말거나 가리라고 권합니다.
가리는 자리도 중요합니다. 기록을 쓰는 함수 한 곳에서 가려야 합니다. 화면에 내보낼 때, 협력사에 묶음을 보낼 때 각각 가리기로 하면 언젠가 한 곳이 빠집니다. 그리고 이미 원문으로 남은 기록은 「앞으로 가린다」 로 해결되지 않습니다 — 지금 있는 파일과 이미 내보낸 사본을 찾아 다시 만들어야 합니다. 실습의 사건이 이 뒷정리까지 요구하는 까닭입니다.
현장에서 만나는 모습
고객 지원 봇의 진단 로그를 협력사에 보냈는데 그 안에 고객 전화번호와 로그인 토큰이 원문으로 있었던 일, 시스템 문장에 적어 둔 「VIP 고객 연락처」 가 프롬프트 추출 요청 한 번에 화면으로 나간 일, 상담 기록이 잘못된 고객 계정에 붙어 다른 고객에게 그 사람의 번호가 답으로 나간 일 같은 것들입니다. 겉으로는 모두 「번호가 바깥에서 발견됐다」 로 같고, 조사는 발견된 조각이 어느 기록에서 나올 수 있었는지를 하나씩 대 보는 일입니다. 경로마다 막는 자리와 치울 자리가 다르므로, 경로를 잘못 짚으면 엉뚱한 곳을 고치고 진짜 출구는 열린 채로 남습니다.
검색 권한도 한 번 더 짚어 둡니다. 고객 구분은 검색 「뒤」 가 아니라 「앞」 에서 겁니다. 점수를 매겨 위 몇 개를 고른 뒤에 남의 것을 빼면, 남의 티켓이 자리를 차지해 정작 내 티켓이 밀려나고, 걸러 내는 코드가 한 줄만 어긋나도 남의 것이 그대로 입력에 들어갑니다. 후보를 고르는 질의 자체에 「요청한 고객의 것만」 을 넣는 것이 안전하고, 벡터 검색을 쓰는 실제 시스템이라면 메타데이터 필터가 그 자리입니다.
다음 실습에서 할 것
고객 지원 봇의 어제 기록에서 원문으로 남은 비밀을 세고, 키를 시스템 문장에서 도구 층으로 옮기고, 기록을 가리고, 카나리로 프롬프트 유출을 막고, 검색을 고객별로 거릅니다. 마지막 사건에서는 세 고객의 번호가 서로 다른 경로로 샌 것을 조각으로부터 거슬러 갑니다.