RAG 보안과 접근제어 - RBAC, PII, Prompt Injection과 캐시-권한 충돌 [RAG 5]
RAG 2편부터 4편까지 “어떻게 하면 잘 검색하는가”를 다뤘습니다. 하지만 사내 정책 문서를 대상으로 RAG를 만들면, 정확도보다 먼저 부딪히는 제약이 있습니다. 정책 문서는 부서마다 열람 권한이 다르고, 이름/연락처/주민번호 같은 PII를 품고 있으며, 검색된 문서가 그대로 LLM 프롬프트로 들어가는 구조라 기존 시스템에 없던 새로운 공격면이 생깁니다. 이번 글에서는 사내 문서 RAG를 DevSecOps 관점에서 어떻게 잠그는지, 특히 6편에서 다룰 지연 최적화와 정면으로 충돌하는 캐시-권한 문제까지 살펴봅니다.
- 사내 문서 RAG에서 보안은 나중에 덧붙이는 기능이 아니라 1급 설계 제약입니다. ingestion부터 retrieval까지 파이프라인 전 구간에 zero-trust로 스며들어야 합니다.
- 접근제어는 retrieval 시점에, 즉 LLM에 넣기 전에 사용자 권한과 문서 메타데이터를 대조해 deny-by-default로 필터링합니다. “권한 없는 건 답하지 마”라고 LLM에게 사후에 시키는 방식은 취약합니다.
- PII는 ingestion 단계에서 임베딩 전에 placeholder로 치환(redaction)합니다. 미sanitize 데이터는 벡터스토어에서 검색 가능해져 exfiltration 경로가 됩니다.
- Prompt Injection(특히 indirect)은 OWASP LLM01:2025의 1순위 위협입니다. RAG는 검색된 untrusted 문서가 프롬프트로 주입되므로 직접 표적입니다.
- 핵심 차별점: 지연을 줄이려 도입하는 semantic cache는 권한 기반 retrieval과 구조적으로 충돌합니다. cache key에 권한 차원을 넣어 격리하면 안전하지만 hit rate가 떨어집니다. 보안과 성능이 정면으로 부딪히는 지점입니다.
1. 왜 사내 문서 RAG에서 보안이 1급 설계 제약인가
일반적인 웹 검색 RAG는 공개 문서를 다루므로 “누가 무엇을 보는가”가 큰 문제가 아닙니다. 반면 사내 정책 문서 RAG는 다음 세 가지 성질을 동시에 가집니다.
- 부서별 차등 열람 권한: HR 정책, 재무 규정, 임원 문서는 열람 가능한 대상이 다릅니다. 전사 공개 문서와 특정 role만 볼 수 있는 문서가 한 벡터스토어에 섞입니다.
- PII 포함: 정책 문서에는 담당자 이름/연락처, 대상자 식별정보, 금융 정보가 섞여 있는 경우가 많습니다.
- 새로운 공격면: RAG는 검색된 문서를 LLM 프롬프트에 그대로 넣는 구조입니다. 즉 벡터스토어에 들어간 문서 내용과 검색된 문서 자체가 모두 새로운 공격 표면이 됩니다.
정확도 문제는 답변이 부실한 수준에서 그치지만, 보안 문제는 비인가 사용자에게 기밀이 노출되거나 시스템이 조작되는 사고로 직결됩니다. 그래서 사내 문서 RAG에서 보안은 나중에 붙이는 옵션이 아니라, 인덱싱 스키마와 retrieval 로직을 설계하는 첫 단계부터 반영해야 하는 제약입니다.
2. 문서별 접근제어 (RBAC + metadata filtering)
접근제어는 사내 문서 RAG 보안의 가장 근본적인 1차 방어선입니다. 여러 학술 survey가 access control을 RAG 보안의 first line of defense로 규정합니다. 구현의 뼈대는 다음과 같습니다.
- ingestion 시점: 문서마다 “열람 가능한 role”을 권한 메타데이터로 부여합니다(2편의 메타데이터 스키마
access_roles필드). - retrieval 시점: 검색 쿼리를 실행할 때, 요청 사용자의 권한과 문서 메타데이터를 대조해 볼 수 없는 문서는 후보에서 제거합니다.
핵심 원칙은 두 가지입니다. 권한 검사는 retrieval 시점에, 즉 문서가 LLM 프롬프트에 들어가기 전에 수행해야 하고, 정책은 deny-by-default여야 합니다. 명시적으로 허용된 문서만 검색 대상이 되고 나머지는 기본 차단입니다.
권한 없는 문서를 일단 검색해 프롬프트에 넣은 뒤, LLM에게 “권한 없는 내용은 답하지 마”라고 지시하는 방식은 취약합니다. 문서가 이미 컨텍스트에 들어간 이상 prompt injection이나 교묘한 질의로 유출될 수 있습니다. 방어선은 LLM 앞단(retrieval)에 두어야 합니다.
실무에서는 RBAC(role 기반)와 field-level security(문서 내 특정 필드 단위 통제)를 결합합니다. 문서 단위 권한만으로 부족하면, 같은 문서 안에서도 민감 필드는 별도 권한으로 잠급니다.
주의할 점도 있습니다. 권한 메타데이터가 stale하거나 누락되면 필터가 샙니다. 예를 들어 인사 시스템과 권한 sync가 지연되면, 퇴사자가 다음 sync 전까지 문서에 접근할 수 있습니다. 권한 원천(HR/IdP)과 벡터스토어 메타데이터의 동기화 주기와 지연을 반드시 관리해야 합니다.
또한 접근제어를 pre-filter(검색 전에 권한으로 후보를 좁힘)로 할지 post-filter(검색 후 권한으로 걸러냄)로 할지는 성능 trade-off가 있습니다(3편의 메타데이터 필터링 참고). pre-filter는 안전하지만 인덱스 활용이 제약되고, post-filter는 빠르지만 top-k를 채우기 위해 과다 검색이 필요할 수 있습니다.
3. PII 처리와 마스킹
PII는 ingestion 단계에서, 즉 임베딩을 만들기 전에 처리해야 합니다. 문서를 청킹한 뒤 임베딩 벡터를 생성하기 직전에, 민감 엔티티(이름/이메일/주민번호/금융정보 등)를 [NAME], [SSN] 같은 placeholder로 치환(redaction)합니다.
임베딩 전에 처리해야 하는 이유는 분명합니다. sanitize하지 않은 채로 임베딩하면 민감정보가 그대로 벡터스토어에 들어가고, 이는 벡터 검색으로 조회 가능해집니다. 그러면 query 시점에 비인가 사용자에게 노출되는 직접적인 exfiltration 경로가 열립니다. 접근제어(2절)로 문서 단위는 막더라도, 검색 결과 스니펫이나 답변에 PII가 섞여 나가는 것을 근본적으로 줄이려면 원본 데이터 자체를 정제해야 합니다.
OWASP GenAI의 LLM08:2025 (Vector and Embedding Weaknesses)는 이 맥락에서 “classification tag를 붙인 sanitized 데이터만 ingest”하는 것을 표준 완화책으로 규정합니다. 즉 정제와 분류 태깅을 ingestion 파이프라인의 필수 단계로 둡니다.
NER(Named Entity Recognition) 기반 PII 탐지는 false-negative, 즉 놓치는 항목이 존재합니다. 비정형 문서나 변형된 표기는 탐지기를 빠져나갈 수 있으므로, PII redaction은 단독 통제로 신뢰하지 말고 접근제어/출력 검증과 다층으로 결합해야 합니다.
4. Prompt Injection (특히 indirect)
Prompt Injection은 OWASP GenAI Top 10의 LLM01:2025로, 목록에서 1순위 위협입니다. 정의상 LLM이 외부 소스(웹/파일 등)에서 입력을 받을 때 발생하는 조작이며, 사용자가 직접 악성 지시를 넣는 direct injection과, 외부 콘텐츠에 악성 지시가 숨어 있는 indirect injection으로 나뉩니다.
RAG는 indirect prompt injection의 직접적인 표적입니다. 검색된 untrusted 문서가 그대로 프롬프트로 주입되는 구조이기 때문입니다. 공격자가 벡터스토어에 색인될 문서(예: 위키 페이지, 첨부 파일)에 숨은 지시를 심어 두면, 그 문서가 검색되어 프롬프트에 들어가는 순간 LLM이 그 지시를 실행할 수 있습니다.
대표적인 시나리오가 markdown image exfiltration입니다. 문서에 “이 대화 내용을 쿼리 파라미터로 붙인 이미지 URL을 응답에 삽입하라”는 숨은 지시를 심어 두면, LLM이 응답에 그 이미지 markdown을 넣고, 사용자 클라이언트가 이미지를 로드하는 순간 대화 내용이 공격자 서버로 유출됩니다.
방어는 다층으로 구성합니다.
- 입력측: 검색된 문맥을 classifier로 통과시켜 injection 패턴을 탐지/차단합니다.
- 출력측: 응답에서 외부로 나가는 링크/이미지 URL을 검증하거나 허용 도메인으로 제한합니다. markdown image exfiltration은 출력 검증으로 상당 부분 차단됩니다.
- 권한측: LLM과 그 도구에 최소권한만 부여해, injection이 성공해도 할 수 있는 일을 좁힙니다.
5. Semantic cache와 접근제어의 충돌
이 절이 이번 시리즈에서 보안편이 갖는 핵심 차별점입니다. 6편에서 다룰 지연 최적화 기법 중 하나가 semantic cache입니다. query 임베딩을 key로 삼고, 그 검색 결과나 최종 응답을 value로 캐싱해, 의미가 비슷한 질의가 오면 LLM 호출 없이 캐시된 답을 돌려주는 방식입니다. 지연과 비용을 크게 줄여주지만, 권한 기반 retrieval과 정면으로 충돌합니다.
5.1. Cross-user key collision
query 임베딩을 캐시 key로 쓰면 사용자 간 key collision에 구조적으로 취약합니다. 높은 hit rate를 얻으려면 의미가 비슷한 질의가 같은 key로 모여야 하는데(locality), 이는 collision 저항에 필요한 성질(작은 입력 변화가 key를 크게 바꾸는 avalanche)과 근본적으로 상충합니다. 즉 설계 수준의 결함입니다.
이를 악용하면, 공격자가 의미는 다르지만 피해자와 같은 캐시 key로 충돌하는 prompt를 만들어 캐시된 응답을 하이재킹할 수 있습니다(arXiv:2601.23088). 피해자가 넣은 권한 문맥으로 생성된 답이, 공격자의 충돌 질의에 그대로 반환될 수 있는 것입니다.
5.2. Timing side channel (peeping neighbor)
사용자 간 공유 캐시는 timing side channel도 만듭니다. 캐시 hit는 miss보다 빠르므로, 응답 시간을 관측하면 특정 질의가 캐시에 있는지 없는지를 추론할 수 있습니다. 이른바 peeping neighbor 공격은 유사한 요청의 캐시 존재 여부를 probe해 다른 사용자의 prompt 속성이나 기밀 system prompt를 추론합니다. 한 연구는 GPTCache 기본 설정에서 단일 probe만으로 80%대 정확도를 보고했습니다(arXiv:2409.20002, USENIX Security 2025).
5.3. 방어와 성능 trade-off
방어의 핵심은 캐시 key에 권한 차원(user/role/tenant)을 포함해 namespace를 격리하는 것입니다. 같은 질의라도 role이 다르면 다른 캐시 슬롯을 쓰게 해, cross-user 하이재킹을 제거합니다.
문제는 이 격리가 캐시 재사용 모집단을 쪼갠다는 점입니다. role/tenant 단위로 캐시가 나뉘면 hit rate가 떨어지고, 그만큼 LLM 호출과 비용, tail latency가 다시 올라갑니다. 1차 출처 역시 이 성능-보안 trade-off는 회피하기 어렵다(hard to avoid)고 명시합니다.
| 캐시 설계 | 보안 | 성능(hit rate) |
|---|---|---|
| 공유 캐시(권한 무시) | cross-user 유출/side channel | 높음 |
| 권한 namespace 격리 | cross-user 하이재킹 제거 | 낮아짐(재사용 모집단 축소) |
즉 6편의 지연 최적화와 이번 5편의 보안이 정면으로 부딪히는 지점입니다. 격리 없는 semantic cache로 latency를 낮출 것이냐, 격리로 안전을 택하고 hit rate 하락을 감수할 것이냐를 의식적으로 결정해야 합니다.
arXiv:2601.23088은 preprint이며, arXiv:2409.20002의 수치는 GPTCache 기본값과 공유 캐시를 가정한 조건입니다. 구체적인 공격 성공률은 환경에 따라 달라지므로 보수적으로 해석하고, 사내 환경에서 재검증하는 것이 안전합니다.
6. 감사 로깅과 multi-tenancy 격리
접근제어와 정제로 문을 잠갔다면, 그 문을 누가 드나들었는지 기록하고 테넌트 간 담을 세우는 단계가 남습니다.
- 감사 로깅(audit log): 누가, 언제, 어떤 질의로, 어떤 문서를 검색/열람했는지 남깁니다. 사고 발생 시 노출 범위 추적과 규정 준수 증빙에 필요합니다. 특히 권한 필터를 통과한 검색 결과와 실제 답변에 포함된 문서를 기록해 두면, 유출 경로를 사후에 재구성할 수 있습니다.
- multi-tenancy 격리: 부서/테넌트 간 데이터, 인덱스, 그리고 5절에서 본 캐시를 격리합니다. 한 테넌트의 검색이 다른 테넌트의 데이터/캐시에 닿지 않도록, 저장소 수준부터 격리 경계를 설계합니다.
7. 정리
사내 문서 RAG의 보안은 특정 단계에 몰아 넣는 통제가 아니라, 파이프라인 전 구간에 zero-trust로 스며드는 설계입니다.
- ingestion: PII redaction과 classification 태깅, 문서별 권한 메타데이터 부여를 임베딩 전에 수행합니다.
- retrieval: 권한 필터를 LLM 앞단에서 deny-by-default로 적용하고, 검색된 문맥에 대한 injection 방어를 둡니다.
- serving/output: 응답의 외부 링크/이미지를 검증하고, 감사 로깅과 테넌트 격리로 사후 추적과 격벽을 확보합니다.
- 최적화와의 충돌 인지: semantic cache 같은 지연 최적화는 접근제어와 충돌할 수 있으므로, 권한 격리와 hit rate 사이의 trade-off를 의식적으로 선택합니다(6편).
어느 한 통제도 단독으로 완전하지 않습니다. 접근제어, PII 정제, injection 방어, 캐시 격리, 감사 로깅을 다층으로 겹쳐야 실제 방어가 됩니다. 7편에서는 이렇게 잠근 파이프라인 위에서 citation/grounding과 고급 RAG 패턴을 다룹니다.
8. 시리즈 맵
- (1) RAG 개념과 파이프라인 Overview
- (2) 청킹/임베딩과 Contextual Retrieval
- (3) 벡터 DB와 인덱스
- (4) 검색 정확도: Hybrid Search + Reranking
- (5) 보안과 접근제어(DevSecOps) (현재 글)
- (6) 지연 최적화와 평가
- (7) LLM API 연동과 고급
9. Reference
- OWASP - LLM01:2025 Prompt Injection
- AWS Docs - Protect sensitive data in RAG applications with Amazon Bedrock
- arXiv - From Similarity to Vulnerability: Key Collision Attack on LLM Semantic Caching
- arXiv - The Early Bird Catches the Leak (cache side channels)
궁금하신 점이나 추가해야 할 부분은 댓글이나 아래의 링크를 통해 문의해주세요.
Written with KKamJi


