호라이즌앱스 로고
← 기술 노트

개인정보 유출 없이 AI 쓰기, 고객 이름 대신 '이 고객'으로 보냈습니다

임균영조회 —

마스킹 함수는 분명히 있었습니다. 파일도 타입도 다 갖춘 채였습니다.

리뷰 결과를 받고 그 파일을 다시 열었습니다. 함수 안쪽에는 이 한 줄이 남아 있었습니다.

const maskName = (s) => s;

받은 문자열을 손대지 않고 그대로 돌려주는 함수입니다. 게다가 언어모델로 가는 어느 경로도 이 파일을 import하지 않았습니다.

보험대리점(GA) 지사의 고객관리 시스템에 AI 비서를 붙이던 날의 일입니다. 저는 고객 정보가 가려지고 있다고 믿고 있었습니다.

믿음과 코드 사이에 빠져 있던 것을 하나씩 짚겠습니다. 고객 데이터가 있는 업무에 AI를 붙이려는 분이라면 맨 끝 절차는 복사해 쓰셔도 됩니다.

아무도 부르지 않던 마스킹 함수

대화와 분석 경로가 마스킹 관문을 비켜 가던(import 0곳) 구조가 모든 경로가 한 입구를 지나는 구조로 바뀝니다. 언어모델 쪽에는 '이 고객'으로 가린 조각만 닿습니다.

발견은 코드 리뷰에서 나왔습니다. 에이전트 다섯이 설계사 UX, 백엔드와 데이터, 보안과 개인정보, 제품 관점으로 나눠 코드를 정독했습니다.

가장 급한 등급(P0)으로 두 건이 올라왔습니다.

발견실제 상태
마스킹 관문어느 언어모델 경로도 import하지 않음, 내부는 항등 함수
대화창의 고객 조회감사 도구를 거치지 않아 열람 기록 0건

두 번째도 가볍지 않았습니다. 대화창에서 특정 고객을 물으면 시스템이 그 고객의 계약을 읽어 옵니다.

그런데 그 읽기가 누가 언제 무엇을 봤는지 남기는 장부를 건너뛰고 있었습니다.

화면에서는 아무 문제도 보이지 않았습니다. 답변도 정상으로 나왔습니다.

분석 기능은 처음부터 비식별 필드만 골라 보내는 구조입니다. 그래서 화면만 봐서는 관문이 죽어 있다는 걸 알 수 없었습니다.

AI에는 '이 고객'만

원칙 자체는 처음부터 단순했습니다. 고객 분석 기능은 계약 구조, 보장 공백, 임박한 일정만 언어모델에 보냅니다.

고객 이름과 주민번호, 연락처, 상담 본문은 보내지 않습니다. 프롬프트 안에서 고객은 줄곧 '이 고객'입니다.

언어모델에 보내는 것보내지 않는 것
계약 구조(종목·보험사·상태)고객 이름
보장 공백주민번호
임박한 일정연락처
실무지식·약관 근거상담 본문

이 정도로 분석이 되느냐고 물으실 수 있습니다. 한 고객으로 돌려 보니 실손 공백을 가장 먼저 짚었습니다.

보험 나이가 바뀌는 날까지 D-203이라는 일정도 잡아냈습니다. 설계사가 다음에 할 일을 고르는 데 이름은 필요하지 않았습니다.

계약과 고객을 나눈 단위는 이음 데모의 계약 카드에서 보입니다. 무엇을 보내고 무엇을 남길지 정할 때 그 단위가 기준이 됩니다.

AI 쪽 권한도 좁혔습니다. 언어모델 실행 환경에는 도구를 하나도 주지 않았습니다.

AI가 내놓는 것은 읽기 전용 브리핑과 제안입니다. 고객에게 나가는 자동 발송은 없고 쓰기는 사람이 승인한 뒤에만 일어납니다.

도구를 부를 때 쓰는 설계사 신원은 도구 인자에서 뺐습니다. 대신 로그인 세션에서 넣습니다.

프롬프트로 남의 담당 고객을 꺼내 달라고 하면 어떻게 될까요? 신원은 세션에서만 들어가니 남의 고객을 지목할 인자가 없습니다.

타입으로 막았는데 왜 뚫렸나

마스킹은 타입 수준에서 강제해 두었습니다. 가린 값에만 Masked라는 표식이 붙고, 표식 없는 원문을 프롬프트에 넣으면 타입 검사에서 실패합니다.

이 관문은 언어모델을 붙이기 전에 미리 만들어 두었습니다. 문제는 그다음에 생겼습니다.

타입 검사는 그 타입을 쓰는 코드만 검사합니다. 분석 기능이 프롬프트를 문자열로 직접 조립하면 Masked를 한 번도 거치지 않아도 컴파일은 통과합니다.

수정은 입구를 하나로 만드는 쪽으로 했습니다. 대화와 분석 모두 프롬프트는 buildPrompt(Masked[]) 한 함수로만 만듭니다.

빌드 단계에는 규칙을 하나 더했습니다. 언어모델을 호출하는 파일이 이 함수를 쓰지 않으면 빌드가 멈춥니다.

이 검사기는 코드를 텍스트로 훑습니다. 같은 검사기가 다른 규칙에서 주석에 적힌 'persons'라는 낱말을 고객 테이블 접근으로 잘못 잡은 적도 있습니다.

텍스트 검사는 과하게 잡을 수 있습니다. 예외는 사유를 코드에 적어야 통과합니다.

감사 기록이 먼저, 실패하면 멈춤

이름과 연락처, 상담 메모가 먼저 가려집니다. 계약 구조 조각은 감사 장부에 열람 기록이 남을 때까지 기다렸다가 AI로 출발합니다.

감사 누락은 순서를 바꿔서 고쳤습니다. 고객 구조를 프롬프트에 붙이기 전에 감사 기록부터 남깁니다.

기록이 실패하면 고객 구조를 붙이지 않습니다. 실패했을 때 닫힌 쪽으로 떨어지게 하는 이 방식을 fail-closed라고 부릅니다.

고친 뒤 시험 계정으로 대화창에서 한 고객의 보장 공백을 물었습니다. 답변은 계약 구조를 근거로 나왔습니다.

수정 전 0건이던 AI 경유 열람 기록이 이번 시험 조회에서는 감사 장부에 남는 것을 확인했습니다. 대화 기록 테이블에도 2행이 남았습니다.

이후 정본화 단계에서는 대화창의 고객 조회도 다른 도구들과 같은 실행 함수를 거치게 바꿨습니다. 고객 정보를 읽는 도구로 따로 등록했으니 감사 기록도 그 함수 안에서 생깁니다.

사람이 직접 친 이름은?

남은 구멍은 사람 쪽에서 나옵니다. 설계사는 대화창에 고객 이름을 그냥 칩니다.

그래서 담당 고객 이름으로 사전을 만들었습니다. 프롬프트가 나가기 직전에 대화 속 이름을 이 사전으로 한꺼번에 가립니다.

사전에는 3자 이상 이름만 넣었습니다. 그만큼 2자 이름은 사전 밖에 남는다는 점도 알고 쓰셔야 합니다.

사전을 못 불러오면 여기서도 고객 구조를 붙이지 않습니다. 이름을 가릴 수 없는 상황이면 고객 데이터를 빼는 쪽이 기본값입니다.

시스템 밖에서도 같은 일이 벌어집니다. 직원이 범용 AI 채팅에 고객 명단을 그대로 붙여 넣는 경우입니다.

그쪽은 코드로 막기 어려워서 규칙부터 정해야 합니다. AI 개인정보보호 교육은 무엇을 넣어도 되는지 분류표와 마스킹 규칙을 함께 만드는 과정입니다.

같은 AI 비서는 답이 1~4분씩 늦는 문제도 안고 있었습니다. 범인은 고객 검색 쿼리였습니다. 추적 과정은 사내 AI 챗봇 응답이 느릴 때에 따로 남겼습니다.

날짜를 계좌로 삼킨 정규식

번호를 가리는 정규식에서도 사고가 있었습니다. 계좌번호 패턴이 2024-03-15 같은 YYYY-MM-DD 날짜까지 계좌로 보고 지웠습니다.

계약일과 갱신일이 사라지면 보장 분석은 기준을 잃게 됩니다. 설계사가 대화창에서 한 고객의 첫 계약일을 물었는데 답이 나오지 않은 일도 실제로 있었습니다.

단계한 일
원인 1고객 구조에 계약일 자체가 빠져 있음
응급 우회계약일을 추가하며 점 표기로 넣어 정규식을 피함
근본 수정정규식이 YYYY-MM-DD 모양을 날짜로 보존

우회만 두지 않은 이유는 간단합니다. 점 표기는 계약일 한 칸만 지켜 주고 다른 날짜는 여전히 지워지기 때문입니다.

마스킹 시험을 가려야 할 값으로만 하면 이런 사고는 안 보입니다. 가리면 안 되는 날짜와 금액을 같은 문장에 섞어 넣고 결과를 보세요.

공개 지식부터 연 순서

언어모델에 가장 먼저 연 것은 누구나 볼 수 있는 지식이었습니다. 고객 데이터는 맨 마지막에 열었습니다.

첫 대상은 보험 뉴스 423건과 실무지식 2,582건, 약관 11,236개 조항입니다. 밖으로 나가도 문제가 없는 데이터로 검색과 답변 품질부터 다졌습니다.

근거가 없는 질문에 어떻게 답하는지도 이 단계에서 봤습니다. 근거에 없는 약 이름을 묻자 지어내지 않겠다고 답하고 실무 안내와 정보 시점 주의를 붙였습니다.

뉴스와 계약을 짝짓는 기능은 언어모델 없이 상품군 키워드로 만들었습니다.

고객 데이터가 쌓인 업무에 AI를 붙이고 싶다면 이 순서를 권합니다. 공개 지식으로 답하는 비서를 먼저 두고 고객 구조는 가린 채로 나중에 잇는 방식입니다.

사내 시스템에 AI를 붙이는 설계부터 같이 보고 싶다면 AI 자동화 상담에 지금 쓰는 데이터 구조를 적어 주세요. 필드마다 보낼지 남길지를 표로 나누는 일부터 시작합니다.

점검 절차

고객 데이터에 AI를 붙이기 전에, 그리고 붙인 뒤에 한 번 더 이 순서로 확인하세요.

  1. 보낼 필드를 허용 목록으로 적으세요. 이름, 주민번호, 연락처, 상담 본문은 목록에 넣지 않습니다. 금지 목록으로 적으면 새 컬럼이 생길 때마다 구멍이 납니다.
  2. 마스킹 함수가 실제로 불리는지 보세요. 시험 고객 이름 하나를 넣고 언어모델로 나가기 직전의 프롬프트를 출력해 확인합니다.
  3. 언어모델을 부르는 파일이 마스킹 입구를 거치지 않으면 빌드가 멈추게 하세요.
  4. 감사 기록을 못 남기면 고객 데이터도 붙이지 않게 하세요. 기록 실패를 경고만 하고 넘어가면 장부에 사각이 생깁니다.
  5. 대화창에 사람이 직접 친 이름이 가려지는지 보세요. 이름 사전을 쓴다면 사전을 못 불러올 때 어떻게 동작하는지도 정해 둡니다.
  6. 마스킹이 날짜와 금액을 망가뜨리지 않는지 보세요. 계약일과 보험료가 든 문장을 넣고 결과에 둘 다 살아 있는지 확인합니다.
  7. 공개 지식부터 열고 고객 데이터는 마지막에 여세요. 뉴스나 약관으로 답변 품질을 먼저 맞추면 고객 데이터를 여는 범위도 좁게 잡을 수 있습니다.

이어서 읽기