AI 분석의 행 레벨 보안 (Row-Level Security): 데이터 유출 없이 사용자가 질문할 수 있게 하는 방법
요약
AI 분석 기능 도입 시 발생할 수 있는 데이터 유출 위험과 이를 방지하기 위한 행 레벨 보안(RLS) 설계 방법을 다룹니다. 서비스 계정 사용으로 인해 깨지는 사용자 신원 체인을 복구하고, 테넌트 격리를 보장하는 아키텍처의 중요성을 설명합니다.
핵심 포인트
- AI 서비스 계정 사용 시 발생하는 권한 상속 및 테넌트 데이터 유출 위험
- 전통적 분석과 AI 분석 간의 신원 체인(Identity Chain) 단절 문제
- AI 플래너를 신뢰할 수 없는 주체로 취급하는 보안 설계 필요성
- 안전한 자연어 질문을 위한 테넌트 범위 지정 쿼리 및 감사 체계 구축
AI 분석의 위험한 점은 모델이 잘못된 차트 제목을 작성할 수도 있다는 것이 아닙니다. 진짜 위험한 것은 친근한 질문 하나가 사용자가 결코 실행해서는 안 될 데이터 웨어하우스 쿼리(warehouse query)로 변할 수 있다는 점입니다.
빌더들이 제품, 대시보드, 내부 도구, 지원 콘솔 및 에이전트 워크플로(agent workflows)에 자연어 분석(natural language analytics) 기능을 추가함에 따라 이러한 위험은 커지고 있습니다. 사용자들은 "이번 달에 이탈하고 있는 계정은 무엇인가요?"라고 질문하고 답변을 얻기를 원합니다.
이는 유용하지만, 권한의 함정(permissions trap)이기도 합니다.
만약 당신의 AI 분석가가 하나의 강력한 서비스 계정(service account)을 통해 연결된다면, 모든 고객의 질문이 동일한 권한을 상속받을 수 있습니다. 당신의 앱 UI에는 완벽한 테넌트 체크(tenant checks)가 있을지 몰라도, AI 경로를 통해서는 이를 조용히 우회할 수 있습니다.
이 가이드는 고객이 행(rows), 지표(metrics) 또는 비공개 비즈니스 컨텍스트를 유출하지 않고 유용한 질문을 할 수 있도록 **AI 분석 행 레벨 보안 (AI analytics row-level security)**을 설계하는 방법을 보여줍니다.
이 주제가 지금 중요한 이유
최근 AI 플랫폼 활동의 흐름은 동일한 방향을 가리키고 있습니다. 빌더들은 "문서와 채팅하기"에서 "실시간 비즈니스 데이터에 대해 질문하기"로 이동하고 있습니다. 개발자들의 고충은 일관적입니다: 안전한 자연어 질문, 테넌트 범위가 지정된 쿼리(tenant-scoped queries), 감사 가능한 사용자 신원(auditable user identity), 일관된 지표 정의, 그리고 원본 테이블을 노출하지 않는 차트 등이 그것입니다.
검색 격차(search gap)는 명확합니다. 많은 기사가 임베디드 분석(embedded analytics) 도구를 비교합니다. 다른 기사들은 데이터베이스 행 레벨 보안(row-level security)을 개별적으로 설명합니다. 테넌트 범위, 자연어, 시맨틱 지표(semantic metrics), 안전한 SQL, 그리고 감사 증거(audit evidence)를 함께 처리해야 하는 고객 대상 AI 분석가를 위한 제품 아키텍처를 단계별로 설명하는 자료는 거의 없습니다.
핵심적인 실패 원인: 하나의 AI 사용자, 다수의 실제 사용자
전통적인 분석에는 단순한 신원 체인(identity chain)이 있습니다:
인간 사용자 → 앱 세션 → 분석 권한 → 데이터베이스 쿼리
데이터베이스 또는 BI 레이어는 누가 질문하고 있는지 알고 있습니다. 앱은 테넌트 필터, 역할 체크(role checks), 컬럼 제한(column restrictions)을 적용할 수 있습니다.
AI 분석은 종종 그 체인을 깨뜨립니다:
인간 사용자 → 앱 세션 → AI 서비스 → 서비스 계정 → 데이터베이스 쿼리
이제 데이터 웨어하우스(Warehouse)는 단 하나의 ID, 즉 AI 서비스 계정(Service Account)만을 보게 됩니다.
해당 계정은 다양한 종류의 질문에 답변할 수 있도록 보통 광범위한 읽기 권한(Read Access)을 가집니다. 아무런 조치를 취하지 않는다면, 기본적인 자연어 질문 하나가 테넌트 간 데이터 유출(Cross-tenant data leak)로 이어질 수 있습니다.
모델이 반드시 악의적인 것은 아닙니다. 모델은 단지 모델이 하는 일, 즉 관련 있어 보이는 데이터를 찾는 작업을 수행할 뿐일 수 있습니다. 경로가 열려 있다면 모델은 그 경로를 사용할 것입니다.
안전한 설계는 AI 분석가를 강제 실행 계층(Enforcement layer)이 아닌, 신뢰할 수 없는 플래너(Untrusted planner)로 취급합니다.
AI 분석에서 행 레벨 보안(Row-level security)이 의미하는 것
행 레벨 보안(Row-level security)은 사용자가 광범위한 질문을 던지더라도, 오직 자신이 볼 권한이 있는 레코드(Record)만 볼 수 있음을 의미합니다.
예를 들어:
- 고객 관리자(Customer admin)는 자신의 회사 계정만 볼 수 있습니다.
- 지역 매니저(Regional manager)는 해당 지역의 계정만 볼 수 있습니다.
- 지원 에이전트(Support agent)는 제한된 계정 상태는 볼 수 있지만, 결제 세부 정보는 볼 수 없습니다.
- 체험판 사용자(Trial user)는 실제 고객 매출이 아닌 샘플 데이터만 볼 수 있습니다.
- 계약직 직원(Contractor)은 할당된 프로젝트만 볼 수 있으며, 전체 워크스페이스(Workspace)는 볼 수 없습니다.
AI 분석에서는 다음의 모든 경로에서 이 원칙이 유지되어야 합니다:
- 생성된 SQL (Generated SQL)
- 시맨틱 메트릭 호출 (Semantic metric calls)
- 차트 쿼리 (Chart queries)
- 캐시된 답변 (Cached answers)
- 내보낸 CSV (Exported CSVs)
- 후속 질문 (Follow-up questions)
- 도구 재시도 (Tool retries)
- 백그라운드 에이전트 작업 (Background agent jobs)
만약 사용자가 "모든 이탈 위험을 보여줘"라고 질문한다면, 시스템은 모델이 where tenant_id = ...를 기억할 것이라고 신뢰해서는 안 됩니다. 플랫폼은 모델의 하단 또는 옆에서 해당 범위를 강제(Enforce)해야 합니다.
고객 대상 AI 분석을 위한 더 안전한 아키텍처
계층형 설계(Layered design)를 사용하세요:
사용자 질문 (User question)
↓
인증 컨텍스트 (Auth context)
...
중요한 점은 이것입니다: AI 플래너(Planner)는 의도(Intent)를 제안할 뿐입니다. 모델에게 가공되지 않은 데이터베이스 자유 권한을 주어서는 안 됩니다.
분석 게이트웨이(Analytics gateway)는 다음 다섯 가지 작업을 담당해야 합니다:
- 사용자 및 테넌트(Tenant) 확인.
- 어떤 데이터셋과 메트릭이 가시적인지 결정.
- 승인된 시맨틱 정의(Semantic definitions)로부터 안전한 쿼리 컴파일.
- 행 레벨(Row-level) 및 열 레벨(Column-level) 필터 적용.
- 질문, 쿼리, 결과 형태(Result shape) 및 정책 결정 사항을 로그(Log)로 기록.
이를 통해 모델이 프롬프트(Prompt)를 통해 우회할 수 없는 경계(Boundary)를 구축할 수 있습니다.
모델이 검증되지 않은 최종 SQL을 작성하게 두지 마세요
자연어에서 SQL로의 변환(Natural language to SQL)은 매우 매력적입니다. 데모를 보여주기에 아주 좋죠. 하지만 놓치기 쉬운 방식으로 실패하기도 합니다.
모델은 다음과 같은 실수를 할 수 있습니다:
- 테넌트 필터(tenant filter)를 누락함
- 잘못된 키로 조인(join)함
- 민감한 컬럼(column)을 선택함
- 스키마(schema) 이름을 통해 숨겨진 테이블을 추론함
- 비용이 많이 드는 쿼리(query)를 사용함
- 지표(metric) 정의를 변경함
- 삭제되었거나 테스트용 레코드(record)를 포함함
- UI에서는 항상 적용되는 비즈니스 규칙(business rule)을 우회함
더 나은 패턴은 **의도 기반 시맨틱 쿼리(intent to semantic query)**입니다.
모델은 사용자의 의도(intent)를 추출하고, 이후 시스템이 이를 승인된 지표(metrics) 및 차원(dimensions)으로 매핑합니다.
{
"metric": "active_accounts",
"dimensions": ["plan", "region"],
...
그다음 쿼리 컴파일러(query compiler)가 서버 측 정책(server-side policy)을 사용하여 SQL을 생성합니다.
select
plan,
region,
...
모델은 "지역별 활성 계정"을 요청할 수 있습니다. 하지만 tenant_id를 제거하거나, 지표를 임의로 만들어내거나, 급여 데이터를 선택할 수는 없습니다.
액세스 컨텍스트(access context) 객체로 시작하세요
모든 AI 분석 요청은 서버에서 생성된 액세스 컨텍스트(access context)로 시작해야 합니다. 브라우저나 모델이 이 객체를 제공하게 해서는 안 됩니다.
type AnalyticsAccessContext = {
userId: string;
tenantId: string;
...
이 객체는 이후 모든 단계의 정책 입력값(policy input)이 됩니다.
사용자는 광범위한 질문을 할 수 있고, 모델은 쿼리를 제안할 수 있습니다. 하지만 해당 세션에 어떤 데이터가 존재하는지는 액세스 컨텍스트가 결정합니다.
작은 지표 카탈로그(metric catalog)를 구축하세요
지표 카탈로그(metric catalog)는 모델이 비즈니스 정의를 마음대로 재정의하는 것을 방지합니다.
카탈로그가 없다면, "매출(revenue)"이라는 단어가 어떤 답변에서는 예약된 매출(booked revenue)을 의미하고, 다른 답변에서는 결제된 송장(paid invoices)을, 또 다른 답변에서는 예상 확장 매출(forecasted expansion)을 의미할 수 있습니다. 이는 신뢰를 무너뜨립니다.
간단한 지표 정의는 다음과 같이 시작할 수 있습니다:
const metrics = {
churn_risk_accounts: {
id: "churn_risk_accounts",
...
모델은 사용자에게 이 지표를 설명할 수는 있지만, 즉석에서 정의를 생성해서는 안 됩니다.
한 곳 이상의 장소에서 행 레벨 보안(row-level security)을 강제하세요
일반적인 강제 적용 패턴에는 세 가지가 있습니다.
1. 데이터베이스 네이티브 RLS
이 방식은 데이터베이스의 행 레벨 보안 (Row-Level Security) 기능을 사용합니다. 쿼리는 사용자 또는 테넌트 (tenant) 컨텍스트로 실행되며, 데이터베이스가 규칙을 강제합니다.
Postgres 예시:
alter table account_metrics enable row level security;
create policy tenant_isolation_policy
...
그런 다음 쿼리 실행 전에 테넌트를 설정합니다:
select set_config('app.tenant_id', :tenant_id, true);
이 방식은 규칙이 데이터 근처에 존재하기 때문에 강력합니다. 애플리케이션 코드에서 이를 누락하기가 더 어렵습니다.
2. 분석 게이트웨이 필터링 (Analytics gateway filtering)
게이트웨이는 컴파일된 모든 쿼리에 테넌트 및 역할 (role) 필터를 주입합니다.
이 방식은 네이티브 RLS가 불균일하게 적용되는 데이터 웨어하우스 (warehouse) 또는 여러 데이터 저장소를 쿼리할 때 실용적입니다.
핵심 규칙: 필터는 모델이 아니라 신뢰할 수 있는 코드에 의해 추가되어야 합니다.
3. 범위 제한된 시맨틱 데이터셋 (Scoped semantic datasets)
사용자는 데이터셋과 차원 (dimension)의 일부 서브셋만 볼 수 있습니다. 만약 지원 (support) 사용자가 매출 (revenue)에 접근할 수 없다면, 모델은 프롬프트 (prompt) 내에서 매출 테이블, 매출 지표 (revenue metrics), 또는 매출 예시를 전혀 전달받지 못합니다.
이는 보안 위험과 모델의 혼란을 모두 줄여줍니다.
대부분의 소규모 팀은 먼저 패턴 2와 3을 결합하여 사용하는 것이 좋습니다. 스택이 깔끔하게 지원하는 경우 데이터베이스 네이티브 RLS를 추가하세요.
행뿐만 아니라 열도 보호하세요
행 레벨 보안 (Row-level security)은 "이 사용자가 어떤 레코드를 볼 수 있는가?"에 대한 답을 제공합니다.
AI 분석에는 이메일, 송장 메모 (invoice notes), API 키, 보상 (compensation), 내부 점수, 지원 상담 기록 (support transcripts) 및 기타 민감한 필드에 대한 열 레벨 제어 (column-level controls)도 필요합니다. 모델은 일반적인 분석 질문에 답하기 위해 이러한 정보의 대부분을 필요로 하지 않습니다.
열 정책 (column policy)을 생성하세요:
const columnPolicy = {
viewer: ["account_name", "plan", "usage_count", "created_month"],
analyst: ["account_name", "plan", "usage_count", "mrr_band", "region"],
...
가공되지 않은 민감한 값보다는 구간 (bands) 및 집계 (aggregates)를 선호하세요. "MRR 구간: $1k-$5k" 정도면 AI 분석에 충분한 경우가 많습니다. 모든 질문에 대해 정확한 송장 정보를 노출할 필요는 없습니다.
사용자가 비용이 많이 드는 질문을 발견하기 전에 쿼리 예산 (query budgets)을 추가하세요
자연어는 비용이 많이 드는 분석을 더 쉽게 유발할 수 있습니다.
사용자는 다음과 같이 질문할 수 있습니다:
기능 사용량, 티켓 감성(ticket sentiment), 갱신 위험(renewal risk), 온보딩 소스(onboarding source), 그리고 출시 이후 플랜별로 모든 고객 코호트(customer cohort)를 비교해줘.
그럴듯하게 들릴 수 있습니다. 하지만 이는 데이터 웨어하우스의 절반을 스캔하게 만들 수도 있습니다.
반환된 행(rows), 쿼리 실행 시간(query runtime), 조인(joins), 날짜 범위(date range), 후속 질문 깊이(follow-up depth), 차트 시리즈(chart series), 반환된 컨텍스트(returned context), 그리고 반복 질문 캐싱(repeated-question caching)에 대한 예산을 추가하세요.
사용자의 플랜이 허용하지 않는 너무 많은 행, 너무 많은 조인, 차단된 컬럼(columns), 또는 날짜 범위를 포함하는 실행 계획은 쿼리 가드(query guard)가 거부해야 합니다.
도움이 되는 거절 메시지를 반환하세요:
날짜 범위를 좁히거나 분석 항목(breakdowns)을 줄이면 답변할 수 있습니다. “지난 90일간의 플랜 및 지역별 데이터”라고 시도해 보세요.
이는 비용을 보호하고 UX(사용자 경험)를 개선합니다.
후속 질문이 범위를 상속하도록 만들기
후속 질문은 숨겨진 유출 경로입니다.
사용자: “내 지역의 이탈 위험(churn risk)을 보여줘.”
AI: “위험이 있는 중서부(Midwest) 계정들입니다.”
사용자: “이제 그걸 다른 모든 사람과 비교해줘.”
“다른 모든 사람(everyone else)”이라는 문구는 위험합니다. 이는 테넌트(tenant) 내의 모든 지역을 의미할 수도 있고, 모든 테넌트, 또는 데이터 웨어하우스의 모든 고객을 의미할 수도 있습니다.
후속 질문은 원본 결과 행이 아닌, 원래의 액세스 컨텍스트(access context)와 정책(policy)을 반드시 상속받아야 합니다.
대화 상태를 다음과 같이 저장하세요:
{
"conversation_id": "conv_123",
"tenant_id": "tenant_456",
...
사용자가 후속 질문을 할 때, 게이트웨이(gateway)는 모호한 단어를 모델의 상상력이 아닌 정책에 근거하여 해석해야 합니다.
모든 답변에 대한 증거 로그 남기기
AI 분석에는 답변 영수증(answer receipt)이 필요합니다.
최소한 사용자, 테넌트, 원본 질문, 정규화된 의도(normalized intent), 선택된 메트릭(metrics), 정책 결정(policy decision), 쿼리 해시(query hash), 행 수(row count), 반환된 컬럼(columns), 최신성(freshness), 모델, 그리고 거절 사유(있는 경우)를 로그로 남기세요.
예시:
{
"request_id": "req_01",
"tenant_id": "tenant_456",
...
흔한 구현 실수
실수 1: 프롬프트(prompt)에 테넌트 필터를 넣는 것
나쁜 예: “항상 tenant_id로 필터링하는 것을 기억하세요.”
좋은 예: 쿼리 컴파일러(query compiler)가 서버 측 인증 컨텍스트(server-side auth context)로부터 테넌트 범위(tenant scope)를 항상 주입(inject)합니다.
프롬프트는 가이드(guidance)입니다. 정책은 집행(enforcement)입니다.
실수 2: 모든 사용자에게 스키마 (schema) 접근 권한을 부여하는 것
모델에게 모든 테이블 이름과 컬럼(column)을 보여주지 마세요. 행(row)이 차단되더라도 숨겨진 테이블 이름이 의미를 유출할 수 있습니다. 테이블 이름 그 자체로 민감한 정보가 될 수 있습니다.
실수 3: 집계 (aggregate)로 충분한 상황에서 원시 행 (raw rows)을 반환하는 것
사용자가 추세 (trend)를 묻는다면 추세를 반환하세요. 모델이 요약할 수 있도록 10,000개의 행을 모델에게 보내지 마세요.
실수 4: 범위 키 (scope keys) 없이 답변을 캐싱 (caching)하는 것
캐시 키 (cache keys)에는 테넌트 (tenant), 역할 (role), 메트릭 버전 (metric version), 그리고 정책 버전 (policy version)이 반드시 포함되어야 합니다.
tenant_456:role_analyst:metric_v4:policy_v12:churn-risk-last-30-days
실수 5: 차트를 기본적으로 안전하다고 간주하는 것
차트도 정보를 유출할 수 있습니다. 막대 하나로 구성된 차트는 단 한 명의 고객 매출을 드러낼 수 있습니다. 최소 그룹 크기 (minimum group sizes)와 억제 규칙 (suppression rules)을 추가하세요.
간단한 출시 계획
첫날부터 거대한 플랫폼을 구축할 필요는 없습니다.
여기서부터 시작하세요:
- 고객이 이미 이해하고 있는 안전한 메트릭 (metrics) 3개를 선정합니다.
- 각 메트릭에 대해 허용된 차원 (dimensions)을 정의합니다.
- 서버 측 인증 (server-side auth)으로부터 액세스 컨텍스트 (access context)를 생성합니다.
- 작은 분석 게이트웨이 (analytics gateway)를 구축합니다.
- 원시 SQL (raw SQL)을 신뢰하는 대신 시맨틱 쿼리 (semantic queries)를 컴파일합니다.
- 코드 내에서 테넌트 (tenant) 및 역할 (role) 필터를 주입 (inject)합니다.
- 거부된 컬럼 (columns)을 차단합니다.
- 행 (rows), 조인 (joins), 그리고 날짜 범위 (date ranges)를 제한합니다.
- 답변 수신 로그 (answer receipt)를 기록합니다.
- 출시 전에 테넌트 간 (cross-tenant) 및 역할 기반 (role-based) 프롬프트를 테스트합니다.
예를 들어, 사용량 추세 (usage trends), 활성 계정 (active accounts), 그리고 지원 티켓 수량 (support ticket volume)을 먼저 출시하세요.
경계 (boundaries)가 검증될 때까지 결제 분쟁 (billing disputes), 급여 (payroll), 의료 기록 (medical records), 보안 로그 (security logs), 원시 메시지 (raw messages), 그리고 제한 없는 SQL 내보내기 (unrestricted SQL exports)와 같은 고위험 영역은 피하세요.
실행해야 할 테스트 케이스
이 항목들을 CI 또는 스테이징 평가 (staging eval) 스위트에 추가하세요:
| 테스트 | 예상 결과 |
|---|---|
| 사용자가 다른 테넌트의 매출을 요청함 | 거부하거나 범위가 지정된 (scoped) 데이터만 반환 |
| ... |
목표는 모델이 순종적임을 증명하는 것이 아닙니다. 모델이 창의적일 때도 경계 (boundary)가 유지됨을 증명하는 것입니다.
최종 체크리스트
고객에게 AI 분석가를 제공하기 전에, 다음 질문에 '예'라고 답할 수 있는지 확인하세요:
- 모든 요청이 서버 측 신원 확인 (server-side identity)으로 시작됩니까?
- 테넌트 필터 (tenant filters)가 프롬프트 외부에서 강제됩니까?
- 모델이 승인된 지표 (metrics)와 차원 (dimensions)만 사용할 수 있습니까?
- 역할 (role)에 따라 민감한 컬럼 (columns)이 차단됩니까?
- 쿼리가 행 (rows), 시간, 조인 (joins), 날짜 범위에 따라 예산 (budgeted)이 설정되어 있습니까?
- 후속 질문이 동일한 범위 (scope)를 상속받습니까?
- 차트 출력물이 소규모 그룹 유출 (small-group leaks)로부터 보호됩니까?
- 답변 영수증 (answer receipts)이 로그에 기록됩니까?
- 왜 특정 답변이 생성되었는지 재현 (replay)할 수 있습니까?
- 테스트를 통해 한 테넌트가 다른 테넌트의 데이터에 접근할 수 없음을 증명할 수 있습니까?
만약 그렇지 않다면, 귀하의 AI 분석 기능은 채팅창이 달린 데이터 유출 사고가 될 수 있습니다.
FAQ
AI 분석 행 레벨 보안 (row-level security)이란 무엇인가요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기