Skip to content

PII 가드레일·RAG 감사 로그·프라이빗 관측성 ​

RagService는 RLS가 검색 후보를 먼저 제한한 뒤, PII 가드레일을 적용한 query/context/response만 임베딩 API, local LLM, browser SSE, audit database에 전달한다. PII filter는 database authorization을 대체하지 않으며, 이미 허가된 context의 불필요한 개인정보 노출을 줄이는 별도 defense-in-depth 계층이다.

계층구현실패 시 동작
regex PIIemail, Korean RRN, account/card/phone-like numeric identifier를 [REDACTED_PII]로 치환항상 local 실행
local NERoptional KoBERT-compatible POST /v1/ner endpoint의 PERSON, EMAIL, RRN, ACCOUNT, PHONE, ADDRESS span을 병합endpoint 오류면 regex-only masking 유지
query ingressmasked query만 local embedding/RRF/LLM에 전달raw query는 audit에 SHA-256 hash로만 사용
safe contextRLS 반환 후 title과 chunk text를 masking해 SSE·LLM·audit에 재사용RLS 범위를 넓히지 않음
response egressSSE 종료 시 response를 한 번 더 masking해 immutable audit event에 기록model/runtime error는 기존 no-context response 사용
audit availabilityRAG_SERVICE_AUDIT_REQUIRED=true가 기본값audit insert 실패 시 503 rag_audit_unavailable으로 fail closed

tb_rag_audit_logs ​

감사 테이블은 operation, masked query, raw query SHA-256, RLS 통과 뒤의 masked context snapshot, masked response, PII label, request ID, timestamp를 저장한다. rag_app에는 SELECT, INSERT만 부여하고 UPDATE, DELETE, TRUNCATE를 revoke한다. 일반 사용자는 자신의 tenant·organization audit event만 insert할 수 있으며, select은 같은 tenant의 Directory-derived admin principal에게만 허용된다.

원본 query의 평문 저장은 PII masking과 충돌할 수 있으므로 기본 schema에는 포함하지 않는다. 규제상 원문 보존이 필요한 조직은 별도 key-management/HSM 정책과 retention·break-glass 승인 절차를 거친 encrypted evidence store를 설계해야 하며, 이 audit table의 masking 원칙을 약화하면 안 된다.

INSERT ... RETURNING을 쓰지 않는다. 감사 행을 SELECT할 수 있는 범위가 같은 tenant의 admin principal뿐이라, PostgreSQL은 RETURNING 절에도 SELECT 정책을 적용한다. INSERT ... RETURNING audit_id를 쓰면 관리자 외 모든 사용자의 검색·답변이 감사 insert 실패로 503 rag_audit_unavailable(fail closed)이 된다. 그래서 audit_id는 애플리케이션이 만들어 넣고(ragcenter/audit.py) 값은 RETURNING 없이 그대로 돌려준다. tests/test_pii_audit.py가 문장에 returning이 없음을 회귀 검증한다.

Optional local KoBERT NER contract ​

text
PRIVATE_NER_URL=http://ner-api.internal:8090
PRIVATE_NER_ALLOWED_HOSTS=ner-api.internal
RAG_SERVICE_AUDIT_REQUIRED=true

NER adapter는 internal/loopback/RFC1918/.local/.internal 또는 explicit allow-list endpoint만 허용한다. request/response contract는 다음과 같다.

json
POST /v1/ner
{"text":"홍길동의 이메일은 hong@example.com"}

{"entities":[{"start":0,"end":3,"label":"PERSON"}]}

NER가 unavailable이면 regex redaction을 중단하지 않는다. NER server가 raw PII를 처리하므로 local VPC/namespace, TLS/mTLS, service account, request log masking을 함께 적용해야 한다.

Phoenix private observability stack ​

infra/observability/docker-compose.phoenix.yml는 Phoenix와 전용 PostgreSQL을 observability-private internal network에 둔다. UI/OTLP ports는 기본적으로 host loopback에만 bind한다.

bash
set -a; source .env.offline; set +a
docker compose -f infra/observability/docker-compose.phoenix.yml up -d

원격 운영자 접근이 필요한 경우 Phoenix port를 public으로 열지 말고 PortalConsole OIDC 또는 사내 IdP를 적용한 reverse proxy를 앞에 둔다. Phoenix Docker self-hosting은 PostgreSQL 14 이상 연결을 지원하며 production에는 image version pin과 persistent volume이 필요하다. [1]

검증 ​

tests/test_pii_audit.py는 regex/NER span merge, original query hash-only audit parameters, masked context snapshot을 검증한다. tests/test_api_contract.py는 fixture에서 audit required를 명시적으로 비활성화해 JWT principal/RLS header forgery contract만 분리하여 검증한다. 실제 운영에서는 audit required를 활성화한 fresh schema integration test를 수행해야 한다.

References ​

[1] Phoenix self-hosted Docker deployment

[2] PostgreSQL Row Security Policies