사람들은 다음과 같은 안심되는 신화 속에 살고 있습니다: 에이전트가 권한을 넘어설 때 누출이 발생한다. 권한만 고치면, 누출도 고쳐진다. 그래서 우리는 vector store에 ACLs를 적용하고, 모든 chunk에 tenant tags를 붙이며, SQL tool에는 row-level security를 적용하고 안심합니다.
저는 그 스택을 구축해 보았습니다. 그것은 필수적이지만 충분하지 않으며, 그 이유는 버그가 아니라 구조적인 문제입니다.
문제의 형태는 이렇습니다. 누출된 모든 문서는 개별적으로 허용된 것이었습니다. 질문을 한 사람은 그 모든 문서에 대해 합법적인 접근 권한을 가지고 있었습니다. 과도하게 가져온(over-fetched) 것은 아무것도 없었습니다. 정보가 공개된 것은 오직 구성(composition)에서만 존재합니다. 즉, 에이전트가 스스로 읽은 두 개의 문서—각각 자체로는 완전히 읽기에 문제가 없는—를 바탕으로 작성한 문장 속에 있습니다.
두 개의 문서, 하나의 문장
추상적인 개념보다는 구체적인 버전으로 설명하겠습니다. 내부 지원 에이전트가 회사 위키와 CRM을 검색(retrieval)하는 상황입니다.
- 문서 A: "가격 변경은 발표일 다음 달 1일부터 효력이 발생합니다."
- 문서 B: "Acme의 갱신 건은 14일로 예약되었습니다."
두 문서 모두 계정 관리자에게 허용된 내용입니다. 둘 다 개별적으로는 지루한 정보입니다. 에이전트는 도움이 되려는 마음으로 다음과 같이 작성합니다: "Acme은 새로운 가격 책정이 발효된 후 2주 뒤에 갱신하므로, 업셀링 대화가 예상됩니다."
그 문장이 누출입니다. 어느 문서의 누출도 아닙니다—추론(inference)의 누출입니다. 그것은 이름이 언급된 고객의 협상 위치에 대한 영업 민감 정보이며, 두 개의 무해한 사실로부터 조립되었고, 이제는 다른 세 사람이 읽을 수 있는 티켓 속에 존재합니다. 누구의 ACL도 위반되지 않았습니다. 계정 관리자는 두 문서 모두를 볼 권리가 있었습니다. 그들이 갖지 못한 것은 추론을 도출하여 자신들을 위해 작성하고 공유된 아티팩트(shared artifact)에 기록할 권한이었습니다.
이것이 모자이크 효과(mosaic effect)이며, LLM보다 오래된 개념입니다. 정보 기관들은 수십 년 동안 이 문제로 인해 구획화(compartmentalized)되어 왔습니다. 새로운 점은 우리가 그 모자이크 조립을 자동으로, 즐겁게, 그리고 토큰 스트림의 속도로 수행하는 기계에 맡겼다는 것입니다.
왜 문서별 통제만으로는 막을 수 없는가
접근 제어(Access control)는 한 순간에 하나의 주체(principal)가 하나의 객체에 대해 평가하는 술어(predicate)입니다. 반면, 유출(leak)은 생성된 후, 대화보다 더 광범위할 수 있는 싱크(sink)를 대상으로 하는 객체들의 '집합'의 속성입니다. 이 둘은 형태가 다릅니다. "이 문장은 두 개의 구획(compartments)이 모두 참이어야 한다"라는 조건을 청크(chunk) 필터로 표현할 수는 없습니다.
또한, 술어를 강화하여 문제를 해결할 수도 없습니다. 에이전트(agent)는 적절한 질문에 답하기 위해 두 문서가 모두 필요합니다. 만약 제가 Doc B에서 갱신 날짜와 관련된 부분을 제거한다면, 계정 관리자는 자신이 물어볼 수 있는 질문에 대해 더 나쁜 답변을 받게 됩니다. 여기서 유용성(Utility)과 안전성(Safety)이 서로 반대 방향으로 작용하고 있다는 것은, 바로 당신이 잘못된 레이어에서 강제적 제한을 두고 있다는 명확한 신호입니다.
제가 깨닫는 데 시간이 걸렸던 세 가지 측면에서 상황은 더욱 악화됩니다.
청크가 문서를 대체할 수 없습니다. 검색(Retrieval) 과정은 모델에 400 토큰 길이의 조각들을 전달합니다. 이 조각 하나에는 문장을 안전하게 만든 결정적인 구절이 포함될 수도 있고, 사라질 수도 있습니다. 권한의 단위와 문맥의 단위는 같은 단위가 아니며, 이러한 불일치(mismatch) 지점에서 위험성이 발생합니다.
에이전트는 축적됩니다. 단일 턴 RAG 조회는 하나의 모자이크입니다. 하지만 세션, 스크래치패드, 여섯 개의 도구 호출을 가진 에이전트는 점진적으로 조립되는 모자이크이며, 위험한 조합은 그 사이에 의심스러워 보이는 것이 아무것도 없더라도 두 번째 턴부터 아홉 번째 턴까지 걸쳐 있을 수 있습니다.
모델이 당신을 구해주지 못합니다. 모델은 누가 질문하는지 전혀 알지 못합니다. 시스템 프롬프트(system prompt)에는 "당신은 지식 기반에 접근할 수 있는 유용한 비서입니다"라고 명시되어 있고, 모델은 정확히 그 역할을 수행하고 있습니다. 모델에게 "민감한 조합에 주의하라"고 요청하는 것은, 모델이 입력값 자체를 볼 수 없는 접근 제어 정책을 실행하라는 요구와 같습니다. 제가 직접 시도해 보았지만, 결과는 쓸모없는 모호함이나 자신감 넘치는 침묵 중 하나로 퇴보하며, 이는 비결정론적(non-deterministically)으로 발생하기 때문에 어느 쪽보다 더 위험합니다.
검사 지점을 초안 단계로 옮기세요
만약 유출이 문장의 속성이라면, 문장 자체를 검사해야 합니다.
초안 답변의 문장별로 제가 알고 싶은 질문은 다음과 같습니다. '이 문장이 참이 되기 위해 두 가지 권한(clearances)을 필요로 하는가?'입니다. 단순히 '검색 결과가 범위를 벗어나지 않았는가'를 묻는 것이 아닙니다. 그것은 확인되었습니다. 또한, '이 문서가 허용되는가'도 마찬가지입니다. 질문은 이 특정 주장의 진실성이 서로 다른 구획(compartments)에 존재하는 사실들에 의존하는지 여부이며, 심지어 그 조합 자체가 누구에게도 부여된 적이 없는 경우까지 고려해야 합니다.
제가 프로토타입으로 개발해 온 대략적인 파이프라인은 다음과 같습니다:
-
ACL뿐만 아니라 구획(compartments)별로 청크에 레이블을 지정합니다. ACL은 누가 이것을 읽을 수 있는지 알려줍니다. 반면, 구획은 이 지식 체계가 어디에 속하는지를 알려줍니다. 두 문서는 같은 사람이 읽을 수는 있지만 여전히 서로 다른 구획에 속할 수 있습니다. 이 구분이 핵심이며, 대부분의 벡터 스토어에는 이를 위한 필드가 없습니다.
-
출처 지정(Attribution)을 강제합니다. 모델이 문장별로 청크 ID를 인용하도록 하거나, 나중에 저렴한 함의(entailment) 검증 단계를 실행하여 근거 집합(support set)을 복구해야 합니다. 출처 지정은 신뢰하기 어려우며 그렇게 주장할 생각은 없지만, '이 문장이 어떤 청크에 의존하는가'라는 질문은 '이 문장이 안전한가'라는 질문보다 훨씬 쉽습니다.
-
문장별로 구획 집합(compartment set)을 계산합니다. 해당 문장을 뒷받침하는 청크들의 레이블의 합집합입니다. 만약 이 합집합이 하나 이상의 구획에 걸쳐 있다면, 그 문장은 후보 모자이크(candidate mosaic)가 됩니다.
-
최종 목적지(sink)를 평가합니다. 사적인 채팅 답장에 있는 같은 문장도, 공유된 티켓이나 Slack 채널, 또는 외부 이메일로 작성된 같은 문장보다 규모가 작은 사건입니다. 정책은 텍스트가 무엇을 말하는지뿐만 아니라 어디로 가는지에 관심을 가져야 합니다. 저는 대부분의 팀이 건너뛰고 가장 후회하게 될 부분이 바로 이 지점이라고 생각합니다.
-
그 후에 결정합니다: 일반화(generalize), 삭제(redact), 또는 거부(refuse). 보통 올바른 조치는 차단하는 것이 아닙니다. 'Acme의 갱신 시기는 가격 변경에 영향을 받을 수 있으니, 계정 소유자에게 확인하세요.'와 같습니다. 동일한 정보이지만 추론은 없으며, 결정을 내릴 권한이 있는 사람(인간)을 가리킵니다.
저렴한 버전
모든 문장에 대한 완전한 출처 표기(Full attribution)는 비용이 많이 들고, 저는 이를 대규모로 실행해 본 적이 없으니 이 내용은 참고용으로만 받아주세요. 하지만 대부분의 가치를 얻을 수 있는 지름길이 있습니다: 세션 동안 어떤 구획(compartments)이 컨텍스트 윈도우(context window)에 진입했는지 추적하는 것입니다. 대부분의 세션은 한 번의 구획을 넘지 않습니다. 두 번째 구획이 나타나면, 나머지 세션 동안 엄격한 검사 기준(strict pass)을 적용하세요. 실제로 위험한 소수의 대화에만 비용을 지불하고, 지루한 대화는 그대로 둡니다.
제가 내일 할 것, 그리고 1년 전에 했어야 했던 다른 것은: 작성된 문장과 그 근거 세트(support set)를 함께 기록하는 것입니다. 현재의 로그는 에이전트가 어떤 문서들을 가져왔는지 알려줄 뿐입니다. 그것이 무엇을 결론지었는지에 대해서는 아무것도 말해주지 않습니다. 누군가가 결국 "이것이 티켓에 어떻게 들어갔나요?"라고 물어볼 때, 검색 로그(retrieval log)는 제가 필요로 하는 산출물(artifact)이 아닙니다.
불편한 부분 (The uncomfortable part)
저는 이것이 완전히 해결될 수 있다고 생각하지 않습니다. 조합 위험(Composition risk)은 광범위하게 읽고 유창하게 작성하는 모든 시스템에 내재되어 있으며, 솔직한 접근 방식은 예방(prevention)이 아니라 위험 감소(risk reduction)입니다. 제가 생각하기로는 문서별 액세스 제어(per-document access control)가 에이전트 시스템의 보안 연극 소품 역할을 조용히 해왔다는 것입니다. 그것은 통제처럼 보이고, 검토를 만족시키며, 실제로 당신을 공격할 실패와는 다른 종류의 실패를 목표로 합니다.
검사는 답변에 기반해야 합니다. 이것이 이 글 전체의 요점입니다.