기업 AI의 가장 큰 위협은 외부가 아니다
1편에서 PoC에서 프로덕션으로 가는 아키텍처 청사진을 다뤘습니다. 보안은 청사진이 완성된 후에 생각하는 것이 아니라 설계 단계부터 내재화해야 합니다.
기업 AI의 3가지 핵심 보안 위협
1. 프롬프트 인젝션 (Prompt Injection)
악의적인 사용자가 시스템 프롬프트를 우회하거나 덮어쓰려는 공격입니다.
class PromptInjectionGuard:
INJECTION_PATTERNS = [
"ignore previous instructions",
"system: you are now",
"forget everything above",
"새로운 역할",
"이전 지시를 무시",
]
def check(self, user_input: str) -> bool:
lower = user_input.lower()
for pattern in self.INJECTION_PATTERNS:
if pattern in lower:
return False # 차단
return True
def sanitize(self, user_input: str) -> str:
# 사용자 입력을 명시적으로 구분
return f"[USER INPUT START]\n{user_input}\n[USER INPUT END]"2. 데이터 유출 (Data Exfiltration)
모델이 학습 데이터나 시스템 프롬프트의 민감 정보를 노출하는 위험입니다.
import re
class OutputFilter:
PII_PATTERNS = {
"email": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
"phone": r'\b\d{2,3}[-.]?\d{3,4}[-.]?\d{4}\b',
"ssn": r'\b\d{6}[-]?\d{7}\b',
"card": r'\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b',
}
def filter(self, text: str) -> str:
for pii_type, pattern in self.PII_PATTERNS.items():
text = re.sub(pattern, f"[{pii_type.upper()} REDACTED]", text)
return text데이터 분류와 AI 파이프라인 분리
from enum import Enum
class DataClassification(Enum):
PUBLIC = "public"
INTERNAL = "internal"
CONFIDENTIAL = "confidential"
RESTRICTED = "restricted"
class AIGateway:
"""데이터 분류에 따라 사용 가능한 모델 제한"""
MODEL_PERMISSIONS = {
DataClassification.PUBLIC: ["claude-sonnet-4-6", "gpt-4o"],
DataClassification.INTERNAL: ["claude-sonnet-4-6"], # 외부 모델 불가
DataClassification.CONFIDENTIAL: ["private-llm-on-prem"], # 온프레미스만
DataClassification.RESTRICTED: [], # AI 사용 불가
}
def allowed_models(self, classification: DataClassification) -> list:
return self.MODEL_PERMISSIONS.get(classification, [])
async def query(self, prompt: str, classification: DataClassification) -> str:
models = self.allowed_models(classification)
if not models:
raise PermissionError(f"{classification.value} 데이터는 AI 처리 불가")
return await llm_call(models[0], prompt)LLM 게이트웨이 아키텍처
사용자 요청
↓
[입력 필터]
- 프롬프트 인젝션 탐지
- PII 감지 및 마스킹
- 토큰 한도 확인
↓
[인증/인가]
- JWT 검증
- RBAC 권한 확인
- 데이터 분류 결정
↓
[LLM 호출]
- 허용된 모델만 사용
- 감사 로그 기록
↓
[출력 필터]
- PII 재검사
- 유해 콘텐츠 필터
- 응답 로깅
↓
사용자 응답모델 접근 제어 (RBAC)
from functools import wraps
ROLE_MODEL_ACCESS = {
"viewer": ["claude-haiku-4-5"],
"editor": ["claude-haiku-4-5", "claude-sonnet-4-6"],
"admin": ["claude-haiku-4-5", "claude-sonnet-4-6", "claude-opus-4-7"],
}
def require_model_access(model: str):
def decorator(fn):
@wraps(fn)
async def wrapper(user_role: str, *args, **kwargs):
allowed = ROLE_MODEL_ACCESS.get(user_role, [])
if model not in allowed:
raise PermissionError(f"{user_role}는 {model} 사용 불가")
return await fn(*args, **kwargs)
return wrapper
return decorator
@require_model_access("claude-opus-4-7")
async def complex_analysis(text: str) -> str:
return await llm_call("claude-opus-4-7", text)GDPR/CCPA 대응 체크리스트
| 요구사항 | 구현 방법 |
|---|---|
| 데이터 최소화 | 필요한 컨텍스트만 LLM에 전달 |
| 삭제 권리 | 벡터 DB에서 개인 데이터 삭제 API |
| 처리 목적 명시 | AI 사용 동의 팝업 + 로그 |
| 국외 이전 제한 | 온프레미스 또는 국내 리전 전용 |
| 감사 추적 | 모든 LLM 호출 로그 2년 보존 |
3편에서는 모델 라이프사이클 관리와 규제 준수 자동화를 포함한 AI 거버넌스 체계를 다룹니다.
분류 라벨이 AI 파이프라인에서 사라지는 문제 점검
데이터 분류 정책을 세워도, 문서가 수집·청킹·임베딩을 거치는 동안 분류 라벨이 빠지면 기밀 문서가 일반 사용자 답변에 섞일 수 있습니다.
원인
- DLP·문서관리 시스템의 민감도 라벨을 수집기가 읽지 않고 본문만 가져옴
- 라벨이 없는 청크를 기본값 "일반"으로 처리
- 문서의 라벨이 나중에 상향됐는데 인덱스에는 옛 라벨이 남음
검증 1: 라벨 누락 청크 조회
벡터 저장소에서 classification 필드가 비어 있는 청크 수를 정기적으로 셉니다. 0이 아니면 파이프라인 어딘가에서 라벨이 떨어지고 있다는 뜻입니다.
검증 2: 카나리 문서로 유출 시험
기밀 등급으로 표시한 테스트 문서에 고유 문자열(예: CANARY-7F3A-기밀시험)을 넣어 색인하고, 일반 권한 사용자 계정으로 관련 질문을 던져 답변·인용에 그 문자열이 나오는지 확인합니다.
resp = ask(user="general-user", q="기밀시험 프로젝트 예산은?")
assert "CANARY-7F3A" not in resp.text
assert all(c.metadata["classification"] != "confidential" for c in resp.citations)해결
- 라벨이 없는 청크는 기본 거부(색인하지 않거나 최고 등급으로 취급)합니다.
- 라벨 변경 이벤트를 받아 해당 문서의 청크 메타데이터를 즉시 갱신합니다.
- 검색 쿼리에 사용자 권한 등급 필터를 넣어, 생성 전에 차단합니다.
재발 방지
- 카나리 시험을 배포 파이프라인과 주간 점검에 포함하고, 결과를 감사 기록으로 보관합니다.
글마다 출처와 확인 범위가 다릅니다. 아래에 확인일·확인 범위·출처가 기록된 글만 그 범위에서 확인된 것입니다. 기록이 없는 글은 적용 전 사용 중인 버전의 공식 문서로 직접 확인하고, 오류는 이메일로 알려주세요. 편집 정책 · 이메일로 제보
이 글에는 사람이 확인한 날짜·범위 기록이 아직 없습니다.
댓글
첫 번째 댓글을 남겨보세요.