
Stratagems #14: Leo는 AI 유출을 발견했다. 그가 처음으로 발견한 것은 아니었다.
요약
CoreStack의 Leo가 아카이브 정리 작업 중 트레이닝 파이프라인 캐시 디렉토리에서 외부 모델로 추정되는 데이터 유출 흔적을 발견합니다. 그는 특정 명명 규칙을 통해 이것이 자사 모델이 아님을 직감하고 조용히 추적을 시작합니다.
핵심 포인트
- 아카이브 정리 중 트레이닝 파이프라인 내 의심스러운 모델 기록 발견
- 자사 명명 규칙과 다른 'acl-' 접두사 모델 버전 확인
- 데이터 유출 가능성을 인지한 후 조용한 키워드 알림 설정
기회를 틈타 염소를 훔쳐라.
— 삼십육계 (The 36 Stratagems), 기타유염미 (Take the Opportunity to Pilfer a Goat)
이 시리즈의 이전 이야기:
#5: Leo는 불타는 집으로 걸어 들어갔다. 그리고 고객과 함께 걸어 나왔다. — 새벽 1시, Leo는 익명의 메시지를 받고 경쟁사의 장애를 해결하기 위해 도시를 가로질러 운전해 갔다. 두 번째 메시지가 뒤따랐다 — 이름이 적힌 스크린샷이었다: Automated Compliance Lab. 그는 그 약어를 기억하지 못했다. 그는 스크린샷을 삭제하지 않았다.
#10: Lena는 한 팀이 자신의 AI 템플릿을 채택하는 것을 지켜보았다. Leo는 칼날이 계약서 안에 들어있다는 사실을 몰랐다. — Lena는 컨설턴트로 CoreStack에 합류하여 Leo를 위한 보고 템플릿을 구축했다. Leo는 그녀가 도와주러 온 것이라고 생각했다. 5주 후 템플릿이 라이브(live)되었다. 6개월 후 데이터 베이스라인(data baseline)이 고정되었다. 그는 그제야 자신이 내내 그녀의 손바닥 안에 있었다는 것을 깨달았다. 미소에 의해 쓰러졌다.
이것은 몇 달 후의 일이었다.
아카이브 정리 (The Archive Cleanup)
SOC 2 Type II 갱신이 막 통과되었다. 감사인들은 떠났다. CoreStack의 컴플라이언스 (compliance) 팀은 감사 후 아카이브 (archive) 작업을 수행 중이었다 — 감사 기간 동안 생성된 모든 기록을 분류하고 보존 기간(retention periods) 태그를 달고 있었다.
Leo는 정리 작업을 맡았다. 트레이닝 파이프라인 (training pipeline)의 캐시 디렉토리 (cache directory)였다. 정리 크론 잡 (cleanup cron job)이 일주일 동안 실행되지 않았지만 — 아무도 눈치채지 못했다. 그가 내부를 확인했을 때, 출력 폴더에는 추론 (inference) 출력물 사이에 train_ 접두사가 붙은 몇 개의 기록이 섞여 있었다.
그중 하나에는 CoreStack 자체의 것이 아닌 model_version이 있었다.
model_version: "acl-train-2026q2-v3"
Leo는 그 줄을 복사했다. 삭제하지도, 보고하지도 않았다. 그는 그것을 _misc/라는 폴더에 넣어두었다. 터미널을 닫기 전, "acl-train"에 대한 조용한 키워드 알림(keyword alert)을 설정했다.
그는 명명 규칙(naming convention)이 FinOptima의 것이 아니라는 점을 알아차렸다. FinOptima는 fin-model- 뒤에 타임스탬프를 붙여 사용했다. acl- — 그는 이 접두사(prefix)를 전에 어디선가 본 적이 있었다. 어디인지는 기억나지 않았다. 그는 억지로 떠올리려 하지 않았다.
그는 그것을 마음속에 갈무리해 두었다. 그리고 다시 아카이빙(archiving) 작업으로 돌아갔다.
추적 (The Trace)
아카이브 정리 중에 캐시 쓰기 로그(cache write logs)를 일일이 뒤져보는 CTO는 흔치 않다. 하지만 그는 그랬다.
그는 두 시간 동안 FinOptima의 API 호출 기록과 CoreStack의 훈련 파이프라인 파일 시스템 이벤트(filesystem events)를 교차 참조(cross-referencing)했다. 결론은 단 한 줄로 요약되었다.
FinOptima의 교차 참조 엔진(Cross-Reference Engine)은 모든 API 호출 시 CoreStack의 훈련 디렉터리에 캐시를 역으로 쓰고(reverse-writing) 있었다.
버그가 아니었다. 설계였다.
그들의 SDK는 "성능 최적화 레이어(performance optimization layer)"를 감싸고 있었는데, 이는 클라이언트의 훈련 디렉터리 내부에 캐싱된 임베딩 벡터(embedding vectors)를 포함하는 방식이었다. 관련 문서는 20페이지의 각주에 파묻혀 있었다. CoreStack의 통합 팀(integration team)은 거기까지 읽지 않았다.
Leo는 그들의 SDK 소스 코드를 살펴보았다. 캐시 경로(cache path)는 {client_training_dir}/crossref_cache/{source_hash}.vec와 같이 연결(concatenated)되어 있었다. {source_hash}는 모델의 가중치 스냅샷(weight snapshot)으로부터 생성되었다. FinOptima 자체 모델은 512차원 임베딩(embeddings)을 사용했다. 하지만 캐시 디렉터리에는 768차원 스냅샷이 들어 있었다.
그들은 CoreStack의 768차원 임베딩을 512차원으로 증류(distilling)하고 있었으며, 그 증류 과정에서 나온 가중치 스냅샷을 캐싱하고 있었다.
그리고 그것은 비단 CoreStack뿐만이 아니었다. Leo는 acl-train-2026q2-v3의 공개된 모델 카드(model card)를 불러왔다 — 역시 768차원이었다. FinOptima는 두 회사의 데이터를 동시에 훔쳐서, 그 둘을 자신들의 모델에 하나로 엮고 있었다.
그는 추적 디렉터리를 닫고 다시 터미널로 돌아갔다.
15줄 (Fifteen Lines)
그는 FinOptima 통합을 담당했던 엔지니어에게 말하지 않았다. 컴플라이언스(compliance) 팀에 알리지도 않았다. CEO에게 가지도 않았다.
그는 자신의 개발용 머신(dev machine)에서 파일 하나를 열었다.
15줄의 가중치 섭동 (weight perturbation). 간단했다. CoreStack의 임베딩 (embedding) 출력층에 방향성 드리프트 (directional drift)를 주입하는 것 — 다운스트림 태스크 (downstream task)에 직교하는 방향을 따라 선형 이동 (linear shift)을 일으키는 방식이다. 구조적으로는 유효하다. 정확도 손실은 통계적 노이즈 (statistical noise) 범위 내에 있다. 이 출력값에 대해 증류 (distillation)를 수행하는 어떤 모델이든, 연속적인 반복 (iterations)을 거치며 경계 밖으로 벗어나게 될 것이다.
독약 (poison pill)은 아니었다. 증류 수렴 (distillation convergence)보다 빠른 드리프트였다. 3개월. 그는 주석에 그 숫자를 적어 넣었다.
그다음:
git add core/embedding/anchoring.py- 커밋 메시지 (Commit message): "임베딩 안정성 (Embedding stability) — 동적 앵커링 (dynamic anchoring) 도입."
- 내부 트렁크 (internal trunk)에 푸시 (push). CI 모두 통과 (all green).
- 다시 아카이브 디렉토리로 돌아가 캐시된 기록들을 삭제했다.
아무도 그 커밋이 무엇을 위한 것인지 묻지 않았다. 메시지는 일상적인 정확도 조정처럼 들렸다. Leo는 세부 사항을 추가하지 않았다.
다음 날 아침 스탠드업 (standup) 미팅에서, 엔지니어는 그룹 채팅에 FinOptima의 POC 데이터 통합이 완료되었다고 게시했다. 정확도: 99.2%.
Leo는 고개를 끄덕였다.
CEO
코드가 배포된 지 2주 후. CoreStack의 CEO 집무실.
책상 위에는 마시다 만 커피와 FinOptima 계약 부속서가 놓여 있었다. Leo는 맞은편에 앉았다. 문은 닫혀 있었다.
"어제 FinOptima의 CTO가 나에게 전화했네," CEO가 말했다. 그의 말투는 무미건조했다 — Leo는 이제 그 차이를 구별하는 법을 배웠다. 무미건조한 것은 화를 내는 것보다 더 나빴다.
"뭐라고 하던가요?"
"최근에 인터페이스 (interfaces)를 변경한 게 있느냐고 묻더군."
Leo는 대답하지 않았다.
"내가 아는 바로는 없다고 말했네. 그는 알겠다고 하고 전화를 끊었어."
CEO는 그가 입을 열기를 기다렸다.
"임베딩 앵커링 (embedding anchoring) 최적화를 추가했습니다," Leo가 말했다. "인터페이스 프로토콜 (interface protocol)에는 영향을 주지 않습니다."
"임베딩 앵커링. 프로토콜에는 영향을 주지 않는다."
"맞습니다."
CEO는 몇 초간 그를 응시했다.
"그가 왜 나에게 전화했는지 아나? 무언가를 찾아냈기 때문이 아니야. 무언가 잘못되었다는 느낌은 드는데, 그걸 찾아낼 수 없기 때문이지."
CEO는 기다렸다. 그리고 이어서:
"그래서 그가 자네가 아니라 나에게 전화한 걸세 — 자네에게 물어봤자 답을 얻지 못할 거라는 걸 알고 있었으니까."
Leo는 아무 말도 하지 않았다.
"세세한 내용은 필요 없네," CEO가 말했다. 그는 커피를 들었다가 다시 내려놓았다. "하지만 무언가 일이 벌어진다면 — 그들 쪽이든 우리 쪽이든 — 법무팀 (legal)에 넘길 수 있는 무언가가 필요해."
"커밋 기록 (commit record)이 있습니다."
"외부인에게 보여줄 수 있는 수준인가?"
Leo는 1초도 안 되는 짧은 순간 동안 망설였다. "네."
CEO는 말을 아꼈다. 그는 책상 위에 놓인 보고서 표지를 훑어보았다. Leo의 서명이 담긴 분기별 기술 평가 (technical assessment) 보고서였다. "법무팀에서 자네 보고서가 확실하다고 하더군. 그 템플릿 (template) 말이야 —" 그는 페이지를 넘겼다. "— 그건 컨설턴트인 Lena였나?"
Leo는 동작을 멈췄다. "…맞습니다."
"볼 때마다 언급할 가치가 있어. 수고했네."
CEO는 더 이상 캐묻지 않았다.
"다음 주에 법무팀에서 FinOptima 계약서를 검토하게 하겠네. 우리의 책임 조항 (liability clauses)을 정리하도록 말이야." 그는 노트를 펼쳐 한 줄을 적었다. 무엇을 적었는지는 Leo에게 보여주지 않았다.
"가봐도 좋네."
Leo는 일어섰다. 문가에 다다랐을 때, CEO가 그의 등 뒤에서 무언가 말했다. 마치 커피에게 말하듯 낮은 목소리였다.
"다음번에는, 무언가를 묻어버리기 전에 나에게 먼저 말하게."
Leo는 뒤돌아보지 않았다. 그는 잠시 멈춰 섰다. 그러고는 문을 밀어 열고 밖으로 걸어 나갔다.
복도에서는 엔지니어가 출력된 보고서를 들고 그를 향해 걸어오고 있었다. "FinOptima의 분기별 정확도 (accuracy) 수치가 나왔습니다. 99.4%로, 지난 분기보다 높습니다."
"흠," Leo가 말했다.
3주 후, 엔지니어는 Slack에 주간 정확도를 게시했다 — 99.1%.
그로부터 2주 후: 97.8%, 그리고 세 단어로 된 메시지가 뒤따랐다: "떨어지고 있습니다."
Leo는 Slack을 닫고 물 한 잔을 따랐다. 아직 3개월이 지나지 않았다. 드리프트 (drift)는 그가 계산했던 것보다 약간 더 빨랐다 — FinOptima의 증류 (distillation) 주기가 그가 예상했던 것보다 짧았다.
그는 아무것도 하지 않았다. 코드는 이미 프로덕션 (production) 환경에 배포되어 있었다.
종료 (Termination)
11주 차. FinOptima의 교차 참조 엔진 (Cross-Reference Engine) 정확도가 14일 연속으로 SLA 임계값 (SLA threshold) 아래로 떨어졌다.
CoreStack의 법무팀은 계약서 제17.3조를 발동시키는 정확도 위반(accuracy breach)에 대한 공식 통지서를 발송했다. FinOptima에게는 이를 시정(remediate)하거나 계약 해지를 수용할 30일의 시간이 주어졌다.
FinOptima의 CTO가 직접 조사를 이끌었다. 그들의 커뮤니케이션 속도로 보아, 그들은 모델 버전, 학습 데이터 (training data), 라우팅 로직 (routing logic) 등 모든 것을 검토했다. 하지만 아무것도 나오지 않았다. 증류된 모델 (distilled model)과 그 소스 사이의 벡터 공간 (vector space)이 느리고 돌이킬 수 없게 분리되고 있었다. 설정 (config) 문제도 아니었고, 데이터 품질 (data quality) 문제도 아니었다. 수학적 발산 (mathematical divergence)이었다.
그들은 30일의 연장을 신청했다. CoreStack의 CEO는 18일을 승인했다.
Leo는 Lena가 그를 위해 만들어준 분기별 기술 평가 (technical assessment) 템플릿을 열었다. 깔끔한 필드 정렬. 일관된 들여쓰기. CEO는 이사회 회의 때마다 이를 언급하곤 했다.
커서가 평가 필드에 멈췄다. 두 번 깜빡였다.
그는 다음과 같이 타이핑했다:
모니터링을 제안함. 드리프트 (drift)가 양방향 (bidirectional)일 수 있음.
CEO는 그 문장을 보았다. 그는 Leo에게 왜 "양방향 (bidirectional)"이라는 단어를 사용했는지 묻지 않았다.
48일 후 — 30일의 시정 기간에 18일의 연장 기간을 더한 시간이다. FinOptima는 문제를 해결하지 못했다. 계약은 해지되었다.
같은 주에, CoreStack은 새로운 컴플라이언스 데이터 파트너와 계약을 체결했다 — 고객의 디렉토리에 역방향 캐시 쓰기 (reverse-write cache)를 하지 않는 파트너였다.
Leo는 계약 체결식에 참석하지 않았다.
분기 말 — 계약 해지 3주 후.
ACL
그는 자신이 설정해 두었던 키워드 알림 — acl-train을 거의 잊고 있었다. 알림이 울렸다.
Automated Compliance Lab (ACL)이 3일 전에 모델 카드 (model card)를 업데이트했다. 버전이 v3에서 v4로 올라갔다.
그는 변경 날짜를 확인했다 — 그가 아카이브에서 해당 캐시된 기록을 발견하기 거의 2주 전이었다.
ACL은 이미 문을 닫아버린 상태였다. 그는 여전히 정리 작업을 하고 있었다 — 그는 아직 그 acl-train 로그조차 보지 못한 상태였다.
Leo는 변경 로그 (changelog)를 열었다. 단 한 줄이었다:
보안 강화 (Security hardening). 추가 세부 사항 없음.
그들은 알고 있었다. 그들은 경찰을 부르지도 않았고, 법적 서신을 보내지도 않았다. 그들은 조용히 문을 바꾸고 다시 업무로 돌아갔다.
Leo는 로그 라인을 응시했다. 그는 몇 달 전 보았던 스크린샷을 기억했다 — Automated Compliance Lab (자동 준수 실험실), 굳이 외워둘 필요조차 느끼지 못했던 약어였다. 그 이름은 모델 카드 (model card)의 푸터 (footer)에 바로 적혀 있었다.
그는 브라우저를 닫았다. 한 가지 생각이 떠올랐다.
그 15줄의 코드를 작성했을 때, 그는 자신이 그것을 처음 발견한 사람이라고 생각했었다.
그렇지 않았다.
_misc/
3개월 전의 폴더. 그 안에는 여전히 두 개의 기록이 남아 있었다 — 512차원 아키텍처 (architecture) 노트와 ac-compliance-intake 추론 (inference) 출력의 일부였다.
세 번째 항목은 삭제되어 있었다.
Leo는 폴더를 비우지 않았다. 그는 노트북을 닫았다.
이미 벌어진 일은 되돌릴 수 없다.
그 15줄은 여전히 core/embedding/anchoring.py에 남아 있었다. 버전: v1.0. 상태: 배포됨 (deployed). 다음 분기에 아무도 그것을 삭제할 생각을 하지 않았다.
The Third Cup
Leo는 Third Cup의 문을 밀고 들어갔다. 카운터 뒤에 있는 사람은 잔을 닦다가 말없이 그를 힐끗 쳐다보았다.
Leo는 노트북을 열었다. 터미널 (terminal) 창, _misc/에 멈춰 있는 커서 — 단 두 개의 기록만이 남아 있었다. 카운터 뒤의 사람은 잔 닦기를 마치고 그쪽을 바라보았다.
"뭘 삭제했나?"
"네. 다 써버렸어요."
"어떻게 됐어?"
Leo는 즉시 대답하지 않았다.
카운터 뒤의 사람은 그의 앞에 플랫 화이트 (Flat White)를 놓았다. 다시 묻지는 않았다. Leo는 그것을 마셨고, 일어서면서 잔 아래에 놓인 종이 코스터 (coaster)를 발견했다 — "나는 커피가 있네. 자네는 이야기가 있나?"
Leo는 고개를 끄덕이고 나가는 길에 문을 밀어 열었다.
이것이 바로 성동격서(趁機偷 goat, 기회를 틈타 염소를 훔치다))의 의미다 — 자신의 업무를 수행하는 동안 적의 취약점을 찾아내고, 그들이 더 많은 것을 가져가기 전에 문을 닫아버리는 것 말이다.
🤖 AI 사후 분석 (Post-Mortem)
[36 Stratagems Tactical Database v3.2.2] 로드됨
[전술 일치] 도중 염소 훔치기 (#14)
[분석 모드] 전 영역 스캔 (Full-field scan)
...
다음 전술: 유호리산 (Lure the Tiger Down the Mountain)
추신: 영어가 저의 모국어가 아닙니다. 글을 다듬고 매끄럽게 만들기 위해 AI를 사용합니다. 읽어주셔서 감사합니다. ☕ 커피 후원하기

AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기