
고객 문의를 분석하는 대화에서, 원문 열람 권한이 없는 사람이 보는 답변에는 전화번호가 [전화번호-1]로 가려져 있습니다(「답변에서 지운 전화번호도 모델은 이미 읽었다, 도구 결과에서 가린다」). 그런데 같은 대화에서 에이전트가 만든 엑셀 보고서에 원래 번호가 실리거나, 에이전트가 이슈 댓글에 그 번호를 적어 올리면 답변을 지킨 검사는 아무것도 막지 못한 셈입니다. 채팅 답변만 검사하는 규칙은 에이전트가 파일을 만들고 외부 서비스에 글을 쓰기 시작하면 이렇게 빈틈이 생깁니다. 이 빈틈은 오류를 내지 않아서, 누군가 그 파일이나 댓글을 열어 보기 전까지 드러나지 않습니다.
이 글은 데이터가 밖으로 나가는 경로를 채팅 답변, 생성 파일, 외부 서비스에 쓰는 도구 셋으로 정하고, 세 경로가 같은 검사 코드를 지나게 만든 설계를 설명합니다. 검사 코드는 개인정보를 받는 쪽의 권한에 맞게 가리고, 외부로 나갈 내용은 사람이 보고 승인하게 하며, 검사한 일을 감사 기록에 남깁니다. 판정은 LLM을 거치지 않는 규칙이라 같은 내용에는 매번 같은 결과가 나옵니다.
데이터가 밖으로 나가는 경로는 셋이다
여기서 경로는 데이터가 OwlAI 밖의 사람이나 시스템에 닿는 자리를 말합니다. 셋으로 나뉩니다.
| 경로 | 무엇이 나가나 |
|---|---|
| 채팅 답변 | 사용자가 화면에서 읽는 답과 그 안의 표·차트 |
| 생성 파일 | 모델이 만들어 사용자가 내려받거나 남에게 넘기는 엑셀·워드·파워포인트·한글 문서, CSV 내보내기, 다이어그램 |
| 외부 서비스에 쓰는 도구 | 메일 보내기, 이슈 등록과 댓글, 메신저 게시, 커넥터(회사가 OwlAI에 연결한 MCP 서버 같은 외부 서비스)의 도구, 바깥 주소로 파일을 올리는 요청 |
플레이북(정해 둔 조회와 보고를 일정에 맞춰 대신 돌리는 자동 작업)에는 따로 나가는 경로가 없습니다. 플레이북 결과는 실행 기록과 앱 알림으로 실행한 본인에게 갑니다. 결과를 다른 사람에게 보내려면 메일 커넥터를 쓰므로, 그 메일은 셋째 경로로 나갑니다.
경로마다 검사를 따로 두면 새 경로가 빠진다
흔한 방법은 경로마다 검사를 붙이는 것입니다. 메일 보안 제품이 보내는 메일의 본문과 첨부를 검사하고, 문서를 만드는 기능이 자기 파일을 검사하는 식입니다. 붙이기 쉽고 경로마다 맞춤 규칙을 둘 수 있다는 장점이 있습니다.
에이전트에서는 이 방법이 빨리 낡습니다. 도구가 늘면 경로도 늘어납니다. 커넥터 하나를 연결하면 그 서버가 내놓는 도구가 한꺼번에 경로가 됩니다. 새 도구에 검사를 빠뜨려도 그 도구는 정상으로 돌고 오류도 나지 않습니다. 규칙도 경로마다 조금씩 어긋납니다. 답변에서는 가린 번호가 CSV에서는 원래 값으로 나가는 일이 그렇게 생깁니다.
그래서 경로를 정하는 자리와 검사를 거는 자리를 하나로 묶었습니다. 내장 도구는 도구 선언에 「밖으로 나가는 도구」 표시를 붙이고, 이 표시가 붙은 도구의 인자와 결과물은 실행 전에 같은 검사 코드를 지납니다. 테스트는 밖으로 나가는 도구 목록과 검사를 지나는 도구 목록이 정확히 같은지 확인합니다. 한쪽이 다른 쪽에 들어 있는지만 보면, 새 도구를 목록에 넣고 검사를 빠뜨린 경우를 잡지 못합니다.
커넥터의 도구는 고객사마다 붙이는 서버가 달라서, 그 도구가 무엇을 하는지 제품 코드는 미리 알 수 없습니다. 그래서 커넥터 도구로 넘기는 인자는 모두 검사 코드를 지나고, 그 가운데 어느 도구에 승인을 걸지는 관리자가 도구마다 정합니다(「권한은 코드로 막고, 판단은 모델에 맡긴다」).
개인정보는 나가는 순간 받는 쪽의 권한으로 정한다
모델은 처음부터 원문 대신 [전화번호-1] 같은 토큰을 보고 글을 씁니다(「답변에서 지운 전화번호도 모델은 이미 읽었다, 도구 결과에서 가린다」). 그러니 경로에서 할 일은 토큰을 원문으로 되돌릴지 그대로 둘지 정하는 것입니다. 기준은 그 경로를 쓰는 사용자의 원문 열람 권한입니다.
- 채팅 답변은 보는 사람의 권한을 따릅니다. 답은 토큰이 든 채로 저장되고, 권한이 있는 사람만 화면에서 원문을 펼칩니다. 대화를 공유받은 사람도 자기 권한대로 봅니다.
- 생성 파일은 파일을 만들게 한 사용자의 권한을 따릅니다. 권한이 있으면 원문으로 채운 파일이, 없으면 토큰이 든 파일이 만들어집니다.
- 외부 서비스에 쓰는 도구는 그 도구를 부른 사용자의 권한을 따릅니다. 플레이북이 메일 커넥터로 보내는 메일은 플레이북을 실행한 사용자의 권한으로 정합니다.
이 판정을 경로에서 하는 이유는 같은 대화라도 누가 어느 경로로 받느냐에 따라 답이 달라지기 때문입니다. 권한 있는 사람이 화면에서 읽을 때는 원문이, 공유받은 사람이 권한 없이 읽을 때는 토큰이 보여야 합니다. 어느 쪽인지는 내용이 경로를 지나는 순간에야 정해집니다.
검사 코드는 모델이 쓴 글에서 전화번호·이메일·계좌번호처럼 형식이 정해진 값을 한 번 더 찾습니다. 입력에서 가리지 못한 값이 모델 글에 섞여 있으면, 권한이 없는 쪽으로 나갈 때 여기서 가립니다.
경로에서 바꾸는 것은 개인정보뿐입니다. 조회한 이름, 코드, 숫자, 날짜, 상태 값은 답과 파일에 그대로 실립니다. 누가 어떤 값을 볼 수 있는지는 조회할 때 권한이 이미 정했습니다(「감사 기록에는 대화 대신 AI가 실행한 SQL과 도구 호출을 남긴다」).
외부로 나가는 내용은 사람이 보고 승인한다
메일, 이슈 댓글, 메신저 게시는 한 번 나가면 거둘 수 없습니다. 그래서 셋째 경로에는 사람이 나갈 내용을 보고 결정하는 단계를 둡니다. 바깥 주소로 파일을 올리는 도구는 매번 승인을 받고, 커넥터 도구는 관리자가 승인을 걸어 둔 것만 승인을 받습니다.
승인 창은 개인정보 검사를 마친 뒤의 인자를 보여 줍니다. 사용자가 승인 창에서 보는 메일 본문과 실제로 나가는 메일 본문이 같습니다. 모델이 인자를 파일 경로로 넘긴 호출(「스크립트 안에서 부른 도구도 호출마다 권한과 승인을 확인한다」)도 파일 내용을 펼친 뒤에 보여 주므로, 승인하는 사람이 경로 문자열만 보고 누르는 일이 없습니다.
테이블 이름은 고치지 않고, 지시와 로그로 다룬다
테이블·컬럼 이름도 답에 드러나지 않았으면 하는 정보입니다. 업무 사용자는 TB_SALES_MON.BR_CD 대신 「월별 지점 매출」을 읽어야 합니다. 하지만 검사 코드는 이 이름을 고치지 않습니다. 이유는 둘입니다.
첫째, 답에 나올 수 있는 이름은 그 사용자가 조회할 권한이 있는 DB의 것뿐입니다. 이름의 출처가 그 사용자의 스키마 조회 결과와 분석 카탈로그이기 때문입니다. 개인정보처럼 권한 밖으로 새는 정보가 아닙니다.
둘째, 내용을 바꾸는 판정은 어느 고객 스키마에서도 틀리지 않아야 합니다. order, result, version처럼 평범한 영어 단어로 지은 컬럼 이름은 문장 속 낱말과 구별할 수 없고, 잘못 가르면 멀쩡한 문장이 바뀝니다.
그래서 이름을 쓰지 않는 규칙은 시스템 프롬프트가 세 경로 모두에 같은 문장으로 지시합니다. 답변뿐 아니라 파일 본문, 파일 이름, 표 제목과 머리글, 외부로 보내는 메시지에도 같은 규칙이 걸립니다. 검사 코드는 경로를 지나는 글을 이 대화에서 스키마 조회가 돌려준 이름, 분석 카탈로그에 있는 이름과 대조해, 정확히 일치한 이름을 로그로 남깁니다. 사용자가 직접 쓴 이름은 대조에서 뺍니다. 로그가 쌓이면 어느 경로에서 지시가 잘 지켜지지 않는지 보이고, 그 자리의 프롬프트 문구를 고칩니다.
검사한 일은 감사 기록에 남는다
검사 코드가 한 일은 감사 기록에 남습니다. 한 건마다 남는 항목은 다음 넷입니다.
- 검사가 일어난 경로
- 그 실행을 부른 사용자
- 한 일의 종류(개인정보를 가림, 원문으로 되돌림, 승인 요청)와 건수
- 승인이라면 누가 언제 결정했는지
나간 내용의 원문과 개인정보 값은 감사 기록에 남지 않습니다. 관리자는 이 기록으로 어느 경로에서 무엇이 자주 걸리는지 보고 컬럼 분류와 도구 정책을 고칩니다.
설계에서 고른 것
경로마다 따로 검사하는 대신 한 곳에서 검사하는 쪽을 골랐습니다. 경로마다 맞춤 규칙을 둘 수 있는 장점은 내려놓았습니다. 대신 검사 코드는 문서, CSV, 도구 인자 같은 여러 모양의 내용을 읽어야 합니다. 문서는 글자가 든 자리마다 읽고, 도구 인자는 펼친 뒤에 읽습니다. 그 대가로 규칙을 고칠 곳도 테스트할 곳도 한 곳이고, 새 도구가 검사를 빠뜨리면 목록 비교 테스트가 실패합니다.
LLM에게 나가는 내용을 다시 쓰게 하는 대신 규칙을 골랐습니다. LLM은 문맥을 보고 형식이 없는 값까지 고쳐 쓸 수 있습니다. 하지만 수십 쪽짜리 문서나 수천 행의 CSV를 모델 출력으로 다시 통과시키면 출력 상한을 넘는 뒷부분이 잘리고, 다시 쓴 표에서 숫자 하나가 바뀌어도 알아챌 방법이 없으며, 답이 다 나온 뒤에 기다림이 붙습니다. 규칙은 같은 내용에 매번 같은 결과를 내고, 무엇을 가렸는지 정확히 기록합니다.
커넥터 서버가 도구 선언에 붙이는 「읽기 전용」 표시를 믿는 대신, 커넥터 도구의 인자를 모두 검사하는 쪽을 골랐습니다. 그 표시는 서버가 붙여도 되고 안 붙여도 되는 선택 항목이라, 서버마다 믿을 수 있는 정도가 다릅니다. 읽기만 하는 도구의 인자까지 검사하는 비용은 문자열 대조 한 번입니다.
테이블 이름은 고치는 대신 지시와 로그로 다룹니다. 그 대가로 시스템 프롬프트를 지키지 않은 이름은 그 턴에 그대로 나갑니다. 로그가 그 자리를 가리키면 프롬프트 문구를 고칩니다.
에이전트가 파일을 만들거나 외부 서비스에 쓰는 시스템이라면, 경로마다 검사를 다듬기 전에 밖으로 나가는 도구 목록과 검사를 지나는 도구 목록이 같은지 확인하는 테스트부터 두는 편이 낫습니다. 검사가 빠진 경로는 오류를 내지 않습니다.
어떤 문제를 해결하나요?
보안과 감사의 다른 글

엔지니어링 노트 · 보안과 감사
외부 모델 대화에는 내부 모델 전용 DB의 이름도 싣지 않는다
고객사 망 안에 띄운 모델과 외부 API 모델을 한 설치에 함께 두면, 쓸 모델을 정해 두는 것만으로는 사내 데이터가 밖으로 나가는 일을 막지 못합니다.

엔지니어링 노트 · 보안과 감사
도구 결과에 나온 주소도 모델이 먼저 썼다면 열지 않는다
웹 페이지를 읽거나 주소에서 파일을 받는 도구는 대화에 먼저 나온 주소만 엽니다(「권한은 코드로 막고, 판단은 모델에 맡긴다」).

엔지니어링 노트 · 보안과 감사
답변에서 지운 전화번호도 모델은 이미 읽었다, 도구 결과에서 가린다
「지난주에 환불을 세 번 넘게 요청한 고객을 찾아 문의 내용을 정리해 줘」라는 질문은 숫자 하나로 끝나지 않습니다.

