우리가 로컬이라고 생각했던 AI 폴백(fallback), 데이터 흐름을 추적하다
요약
AI의 '로컬 처리'라는 개념에 의문을 제기하며, 실제 데이터 흐름 추적의 중요성을 강조합니다. 단순히 Ollama 사용이나 개인 모드 같은 제공자 라벨만으로는 프라이버시를 보장할 수 없으며, 데이터가 실제로 어디로 전송되는지(데이터 플로우)를 추적하는 것이 핵심입니다.
핵심 포인트
- 프라이버시는 제공자의 이름이 아닌 실제 데이터 흐름을 따라야 한다.
- AI 아키텍처 설계 시 '어떤 모델'보다 '신뢰 경계 초과 여부'가 우선 질문되어야 한다.
- 명시적인 프라이버시 정책 계층을 도입하여 모든 프롬프트를 동등하게 취급하는 것이 중요해졌다.
We Thought Our AI Fallback Was Local. Then We Traced the Data Flow.
AI에 회의적인 프로그래머와의 대화가 Zorgax를 명시적인 신뢰 경계(explicit trust boundaries), 로컬 처리, 실패 시 폐쇄 정책(fail-closed policies), 최소화(minimization), 그리고 감사 가능한 AI 이그레스(auditable AI egress)를 중심으로 재설계하도록 이끌었습니다.
며칠 전, 저는 AI 프라이버시에 대한 생각을 바꾼 대화를 나눴습니다.
AI 연구원과의 대화도 아니었고,
프라이버시 컨설턴트와의 대화도 아니었습니다.
회사 내부에서 일하는 프로그래머인 Nicola의 아버지와의 대화였습니다.
우리는 AI에 대해 이야기했고, 회사들이 직원들에게 AI 사용을 허용할지 여부를 어떻게 결정하는지에 대해서도 이야기했습니다.
그의 우려는 실질적이었습니다.
그가 근무하는 직장에서는 직원들이 개인 정보, 내부 회사 정보 또는 외부 시스템으로 단순히 전송되어서는 안 되는 기타 데이터와 함께 작업할 수 있기 때문에 어떤 AI 도구를 승인할지 여부를 결정하는 데 변호사들이 관여합니다.
그리고 근본적인 질문은 매우 간단했습니다:
AI가 실제로 무엇을 가져가며, 그 데이터는 어디로 가는가?
그 질문이 저에게 계속 남아 있었습니다.
개발자들이 아키텍처 라벨(architecture labels)로 프라이버시 질문에 답하기는 쉽기 때문입니다:
"로컬이에요."
"Ollama를 사용해요."
"사용자 데이터로 학습하지 않아요."
"개인 모드가 있어요."
하지만 이 진술들 중 어느 것도, 사용자가 '보내기(Send)' 버튼을 누른 후 특정 정보가 어떻게 되는지 알려주지는 못합니다.
그래서 저는 MyZubster 내부에서 구축하고 있는 AI 레이어인 Zorgax로 돌아가 실제 데이터 흐름을 추적하기 시작했습니다.
그때 우리는 불편한 것을 발견했습니다.
우리는 'Ollama'라는 것을 가지고 있었습니다.
개념적으로 저희는 다음과 같은 것을 가지고 있었습니다:
사용자 입력
↓
웹 검색
↓
모델 라우팅
├── OpenAI
│
└── "ollama"
↓
공개 AI 게이트웨이
라벨과 네트워크 경계가 서로 다른 이야기를 하고 있었습니다. 문제는 Ollama 자체가 아니었습니다. 저희는 이미 코드베이스의 다른 곳에서 다음과 같은 루프백 엔드포인트(loopback endpoint)를 사용하여 실제 로컬 Ollama 통합을 가지고 있었습니다:
127.0.0.1:11434
문제는 제공자 라벨이 프라이버시 보증의 대리인(proxy)이 되어버렸다는 것입니다. 그것들은 같은 것이 아닙니다. 경로가 ollama라고 불리지만 결국 프롬프트를 공개 게이트웨이에 전송한다면, 그것은 로컬 처리가 아닙니다. 이것이 리디자인의 첫 번째 규칙이 되었습니다: 프라이버시 속성은 제공자의 이름이 아닌 실제 데이터 흐름을 따라야 합니다.
질문이 바뀌다
처음에는 아키텍처적 질문이 사실상 다음과 같았습니다:
어떤 AI 모델이 이 요청을 처리해야 하는가?
그 대화와 코드 리뷰 이후, 저희는 이를 다음과 같이 변경했습니다:
이 데이터가 신뢰 경계(trust boundary)를 넘어 전송되는 것이 허용되는가?
모델 선택은 나중에 이루어집니다. 이 구분이 중요합니다. 직원이 다음을 포함하는 무언가를 붙여넣는다고 상상해 보세요:
고객: [email protected]
내부 프로젝트: Aurora
API 토큰: ...
전통적인 AI 라우터는 다음과 같이 질문할 것입니다:
어떤 모델이 이것에 답변해야 하는가?
프라이버시를 고려하는 라우터는 그보다 먼저 무언가를 물어봐야 합니다:
이 콘텐츠가 기기를 벗어나도 되는가?
답변이 '예'일 때만 외부 모델 선택이 관련성을 갖게 됩니다.
프라이버시가 정책 결정이 되다
저희는 Zorgax를 위해 명시적인 프라이버시 정책 계층을 도입했습니다. 모든 프롬프트를 동등하게 취급하는 대신, 데이터는 이제 네 가지 범주로 분류될 수 있습니다:
PUBLIC (공개)
INTERNAL (내부)
CONFIDENTIAL (기밀)
PII (개인 식별 정보)
이러한 분류는 처리 모드(processing mode)에 매핑됩니다:
EXTERNAL_ALLOWED (외부 허용)
LOCAL_ONLY (로컬 전용)
DENY (거부)
중요한 부분은 기본값입니다. 분류가 누락되었다고 해서 다음을 의미하는 것은 아닙니다:
아마도 공개
그것은 다음과를 의미합니다:
INTERNAL → LOCAL_ONLY
다시 말해, 시스템은 폐쇄적으로 실패(fails closed)합니다. 우리는 분류되지 않은 정보를 외부로 전송할 수 있는 권한으로 실수로 해석하기보다는, 불필요하게 로컬에서 처리하는 것을 선호합니다.
분류가 곧 승인(Authorization)은 아닙니다.
이것은 또 다른 중요한 구분점이었습니다. 내용이 다음과 같이 분류되었다 하더라도:
PUBLIC
단지 그것만으로는 외부 처리를 승인하지 않습니다. 우리는 별도의 제어 장치인 externalProcessingAllowed를 추가했습니다.
외부 AI/검색 작업이 진행되려면 두 가지 조건이 모두 충족되어야 합니다:
- 데이터 분류가 외부 처리를 허용하고
- 신뢰할 수 있는 서버 측 정책(trusted server-side policy)이 명시적으로 외부 처리를 허용해야 함
개념적으로는 다음과 같습니다:
PUBLIC + 승인됨 (authorized) → 외부 처리가 허용될 수 있음
PUBLIC + 승인되지 않음 (not authorized) → 외부 처리가 거부됨
INTERNAL → 로컬 전용(local only)
CONFIDENTIAL → 로컬 전용(local only)
PII → 로컬 전용(local only)
이러한 분리는 의도적입니다. 브라우저는 다음과 같이 말할 수 없어야 합니다:
{
"classification": "PUBLIC",
"externalProcessingAllowed": true
}
그리고 이를 통해 스스로 외부로 데이터를 전송할 권한을 부여해서는 안 됩니다.
저희의 경로 테스트(route tests)는 클라이언트가 제어하는 값을 신뢰할 수 있는 개인 정보 보호 승인으로 변환하는 것을 특별히 방지합니다. 제품 이용 자격(Product entitlement) 역시 개인 정보 보호 승인은 아닙니다. 플랜을 결제하거나, 기능을 활성화하거나, 모델에 접근할 수 있다는 것이 자동으로 데이터가 신뢰 경계(trust boundary)를 벗어날 권한이 있음을 의미하지는 않습니다.
민감한 데이터가 선언을 무효화할 수 있습니다.
분류를 문자 그대로 받아들이는 것에는 또 다른 명백한 문제가 있습니다. 누군가는 이것을 공개라고 분류할 수 있습니다:
제 이메일은 [email protected]
또는 그렇지 않은 무해한 요청 안에 실수로 API 키를 포함시킬 수도 있습니다. 그래서 Zorgax는 선언된 분류와 독립적으로 민감 데이터 감지(sensitive-data detection)를 수행합니다. 현재 정책 계층은 다음과 같은 카테고리를 찾습니다:
- 이메일 주소 (email addresses)
- IPv4 주소 (IPv4 addresses)
- 이탈리아 재정 코드 (Italian fiscal codes)
- IBAN
- 결제 카드 후보 (payment-card candidates)
- Bearer 토큰 (Bearer tokens)
- API 키 (API keys)
- 비밀번호/비밀 값/토큰 할당 (password/secret/token assignments)
- 개인 키 자료 (private-key material)
민감한 자료가 감지되면, 이는 선언된 PUBLIC 분류를 무효화할 수 있습니다.
따라서 이것은:
declared = PUBLIC
detected = PII
이라는 것이 단순히 누군가가 'PUBLIC'라는 단어를 제공했다고 해서 외부에서 처리 가능해지는 것은 아닙니다. 효과적인 분류는 여전히 민감합니다. 이는 우리에게 중요한 불변성(invariant)을 제공합니다: 사용자가 제공한 분류가 민감 데이터 감지를 무효화할 수 없습니다.
로컬은 루프백(Loopback)
우리 또한 LOCAL_ONLY가 실제로 의미하는 바를 강화했습니다. Zorgax를 위해 Ollama를 사용하는 전용 로컬 AI 서비스를 도입했습니다. 기본적으로:
http://127.0.0.1:11434
와 같은 로컬 모델(예: qwen2.5:3b)을 사용합니다.
하지만 여기에는 미묘한 세부 사항이 있습니다. 우리는 임의의 URL을 받아 그것을 '로컬'이라고 부르지 않습니다. 현재 LOCAL_ONLY 경계에 대해, Ollama 엔드포인트는 명시적으로 루프백(loopback)으로 해석되어야 합니다:
127.0.0.1
localhost
::1
어딘가 사설 LAN에 있는 기기가 로컬 처리와 동등하다고 조용히 간주되지 않습니다. 이것은 궁극적으로 또 다른 정책 범주(예: PRIVATE_NETWORK)가 될 수 있지만, 이는 다른 신뢰 경계이며 하나로 표현되어야 합니다.
따라서:
LOCAL_ONLY
은 이제 기술적으로 테스트 가능한 의미를 갖습니다.
'아마도 우리 인프라 내부에 있음.'이 아닙니다.
'Ollama와 호환되는 서버.'가 아닙니다.
'충분히 사적임.'도 아닙니다. 루프백입니다.
웹 검색 역시 데이터 이그레스(Data Egress)
AI 프라이버시 아키텍처에서 가장 쉬운 실수 중 하나는 LLM에만 전적으로 초점을 맞추는 것입니다. 하지만 외부 검색 제공업체에 검색 쿼리를 보내는 것 또한 이그레스입니다. Zorgax는 Brave Search, Tavily와 같은 외부 검색 시스템 및 공개 정보 출처와 상호 작용할 수 있습니다. 따라서 우리는 다음을 처리합니다:
OpenAI
공개 AI 게이트웨이(public AI gateway)
외부 웹 검색
를 외부 목적지로 간주합니다. 정책 결정은 이러한 호출 이전에 이루어집니다. 이는 또한 작업 순서를 변경했습니다.
다음과 같은 방식에서 벗어나서:
사용자 입력
↓
웹 검색
↓
사용할 모델 결정
아키텍처는 다음과 같이 가까워집니다:
사용자 입력
↓
분류(classify)
↓
정책 평가(evaluate policy)
↓
허용된 외부 전송 결정(decide permitted egress)
↓
검색/모델 처리(search / model processing)
이러한 순서가 매우 중요합니다. 이미 일부 내용을 검색 제공업체로 보냈다면, 민감한 프롬프트가 LLM에 도달하는 것을 막는 것은 거의 가치가 없습니다.
최소화 기능도 추가했습니다
권한 부여(Authorization)가 모든 것을 전송해야 한다는 의미는 아닙니다. 허용된 외부 처리를 위해 최소화 계층(minimization layer)을 도입했습니다. 승인된 외부 요청이 전송되기 전에 Zorgax는 다음 작업을 수행할 수 있습니다:
- 텍스트 정규화(normalize the text),
- 인식된 민감 패턴 비식별화(redact recognized sensitive patterns),
- 페이로드 길이 제한(limit payload length),
- 기록 최소화(minimize history),
- 해당 작업에 필요한 정보만 전송합니다. 개념적으로: 공개(PUBLIC) ↓ 외부 처리 승인? ↓ 예, 최소화 ↓ 외부 제공업체
하지만 최소화는 의도적으로 권한 메커니즘이 아닙니다. 저희는 이렇게 하지 않습니다:
PII → 무언가 비식별화 → 마법처럼 공개(PUBLIC)로 호출 → 외부 전송
분류와 권한 부여는 원본 콘텐츠를 기반으로 평가됩니다. 최소화는 이미 외부 작업이 허용된 후에 적용되는 방어 심층(defense in depth)입니다.
대화 기록도 중요합니다
프롬프트가 AI 요청에서 유일하게 민감한 것은 아닙니다. 다음을 고려해 보세요:
이전 메시지: "제 고객은 [email protected]입니다"
현재 메시지: "요약해 주시겠어요?"
최신 메시지만 보고 두 번째 요청을 무해하다고 잘못 분류할 수 있습니다. 따라서 Zorgax 개인 정보 보호 경로는 관련 대화 컨텍스트도 평가합니다. 최근 기록의 민감한 정보는 작업을 다음으로 강제할 수 있습니다:
LOCAL_ONLY
심지어 현재 메시지에 민감한 정보가 포함되어 있지 않더라도 그렇습니다. 이는 특히 어시스턴트에게 중요합니다. 왜냐하면 개인 정보 보호 결정은 단순히 최신 텍스트박스 값뿐만 아니라 모델로 전송되는 실제 페이로드(payload)를 따라야 하기 때문입니다.
그 후, 우리는 경계(Boundary)를 감사 가능하게 만들었습니다.
나중에 아무도 검사할 수 없는 개인 정보 보호 규칙은 회사에서 운영하기 어렵습니다. 그래서 우리는 개인 정보 보호 감사 계층(privacy audit layer)을 추가했습니다. 이 결정에 대해 시스템은 다음과 같은 정보를 기록할 수 있습니다:
- 분류(classification)
- 처리 모드(processing mode)
- 목적지(destination)
- 허용/거부(allowed / denied)
- 이유(reason)
- 콘텐츠 다이제스트(content digest)
- 타임스탬프(timestamp)
하지만 우리는 원본 프롬프트나 대화 기록을 개인 정보 보호 감사 기록에 넣는 것을 의도적으로 피했습니다. 그렇게 하면 이상한 반(反)패턴이 생기기 때문입니다. 즉, 민감한 프롬프트가 유출되는 것을 막기 위해 개인 정보 보호 시스템을 구축하고, 그 동일한 프롬프트를 감사 로그에 복사하는 것입니다.
로컬 감사 파일은 제한적인 파일 시스템 권한으로 생성됩니다. 이것이 아직 완전한 엔터프라이즈 감사 플랫폼(enterprise audit platform)은 아닙니다. 우리는 여전히 중앙 집중식 라이프사이클 관리, 구성 가능한 보존 기간 설정, 명시적 관리자 접근 제어와 같은 것들이 필요합니다. 하지만 이는 또 다른 원칙을 확립합니다. 즉, 결정의 원인이 된 데이터를 불필요하게 복제하지 않으면서 개인 정보 보호 결정을 감사하는 것입니다.
아키텍처가 이제 다르게 보입니다.
원래의 사고 모델은 대략 다음과 같았습니다:
USER
│
▼
ZORGAX
│
├──── OpenAI
│
├──── Web Search
│
└──── "Ollama"
│
└──── 어쩌면 공개 게이트웨이(public gateway)
새로운 모델은 다음과에 더 가깝습니다:
USER INPUT
│
▼
┌────────────────┐
│ CLASSIFICATION │
└───────┬────────┘
│
▼
┌─────────────────┐
│ PRIVACY POLICY │
└────────┬────────┘
│
┌────────────┼─────────────┐
│ │ │
▼ ▼ ▼
PUBLIC LOCAL_ONLY DENY
│ │ │
▼ │ └── X
External authorized? │
│ │ │
NO YES │
│ │ │
X ▼ ▼
MINIMIZE LOOPBACK
│ OLLAMA
▼
PERMITTED EGRESS
│
┌──────┼─────────┐
▼ ▼ ▼
OpenAI Search Public AI
Gateway
+
PRIVACY AUDIT
근본적인 변화는 또 다른 AI 모델을 추가하는 것이 아닙니다.
신뢰 경계(trust boundary) 이전에 명시적인 결정 지점(decision point)이 추가된 것입니다.
우리는 기능을 테스트한 것이 아니라, 그 경계를 테스트했습니다.
이 부분이 저에게 중요했습니다.
단순히 다음과 같은 단위 테스트(unit test)를 작성하고 끝내는 것은 쉬웠을 겁니다:
PII → 로컬 전용 (LOCAL_ONLY)
하지만 그것만으로는 런타임에 실수로 다른 곳에 외부 요청을 보내지 않을 것이라는 것을 증명할 수 없습니다.
그래서 우리는 네트워크 경계 자체를 중심으로 설계된 런타임 테스트(runtime test)를 추가했습니다.
예를 들어, PII 경로에서는 fetch 목업(mock)이 구성되어 있어 목적지가 로컬 Ollama 루프백 엔드포인트가 아닌 모든 네트워크 요청은 거부됩니다.
이는 다음과 같은 시도를 하는 회귀(regression)가 발생하면 테스트가 실패한다는 것을 의미합니다:
OpenAI
Tavily
Brave
Wikipedia/공개 출처
공개 AI 게이트웨이
또한 우리는 다음 사항도 테스트합니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기