샌드박스가 새어 나올 때: LLM 작업 공간에서의 컨텍스트 오염

발행: (2026년 2월 18일 오후 07:05 GMT+9)
25 분 소요
원문: Dev.to

Source: Dev.to

샌드박스가 새는 경우: LLM 워크스페이스 간 컨텍스트 오염

개요

LLM(대형 언어 모델)을 여러 워크스페이스에서 사용할 때, 각 워크스페이스는 샌드박스라는 격리된 환경을 통해 서로의 컨텍스트가 섞이지 않도록 설계됩니다. 그러나 실제 구현에서는 이 격리가 완벽하지 않아, 한 워크스페이스에서 생성된 프롬프트나 메모리가 다른 워크스페이스에 “누수(leak)”되는 경우가 발생합니다. 이 글에서는 **컨텍스트 오염(context contamination)**이 어떻게 일어나는지, 그 위험성은 무엇인지, 그리고 이를 방지하기 위한 실용적인 전략을 살펴봅니다.

샌드박스가 왜 중요한가?

  • 보안: 민감한 데이터가 다른 사용자에게 노출되지 않도록 함
  • 정확성: 워크스페이스마다 별도의 프롬프트와 메모리를 유지해 결과의 일관성을 보장
  • 리소스 관리: 각 워크스페이스가 독립적인 토큰 할당량을 사용하도록 함

문제 상황 재현

from langchain import OpenAI, LLMChain, PromptTemplate

# 워크스페이스 A
template_a = PromptTemplate(
    input_variables=["question"],
    template="You are a helpful assistant. Answer the following: {question}"
)
chain_a = LLMChain(llm=OpenAI(temperature=0), prompt=template_a)

# 워크스페이스 B
template_b = PromptTemplate(
    input_variables=["question"],
    template="You are a sarcastic critic. Respond with a witty comment: {question}"
)
chain_b = LLMChain(llm=OpenAI(temperature=0.7), prompt=template_b)

# 워크스페이스 A에서 실행
response_a1 = chain_a.run({"question": "What is the capital of France?"})
print("A1:", response_a1)

# 워크스페이스 B에서 실행
response_b1 = chain_b.run({"question": "What is the capital of France?"})
print("B1:", response_b1)

# 다시 워크스페이스 A에서 실행 (오염 확인)
response_a2 = chain_a.run({"question": "Explain quantum entanglement in simple terms."})
print("A2:", response_a2)

기대 결과

  • A1파리라는 사실적인 답변을 반환
  • B1풍자적인 답변을 반환
  • A2순수하게 양자 얽힘에 대한 설명을 제공

실제 결과 (오염 발생)

A1: The capital of France is Paris.
B1: Oh, Paris again? How original. Let’s talk about something more exciting.
A2: Oh, Paris again? How original. Let’s talk about something more exciting.

위와 같이 워크스페이스 B에서 사용된 “풍자” 컨텍스트가 워크스페이스 A에 그대로 남아, 전혀 관련 없는 질문에까지 영향을 미칩니다. 이는 컨텍스트 누수가 발생했음을 의미합니다.

왜 이런 오염이 일어나는가?

  1. 공유된 LLM 인스턴스
    • 여러 체인이 동일한 OpenAI 객체를 재사용하면 내부 캐시가 공유됩니다.
  2. 전역 변수 또는 싱글톤 패턴
    • 프롬프트 템플릿이나 메모리 객체를 전역으로 선언하면, 워크스페이스 간에 상태가 섞일 수 있습니다.
  3. 비동기 실행 시 레이스 컨디션
    • 동시에 여러 요청을 처리하면서 토큰 스트림이 뒤섞이는 경우가 있습니다.

오염 방지를 위한 베스트 프랙티스

1. 워크스페이스당 독립적인 LLM 인스턴스 생성

def get_llm(temperature: float):
    return OpenAI(temperature=temperature, model_name="gpt-4")

각 체인에서 get_llm()을 호출해 새로운 인스턴스를 확보합니다.

2. 프롬프트와 메모리를 명시적으로 복제

import copy

chain_a = LLMChain(
    llm=get_llm(0),
    prompt=copy.deepcopy(template_a)
)

copy.deepcopy를 사용해 템플릿 객체가 다른 워크스페이스와 공유되지 않도록 합니다.

3. 컨텍스트 매니저 활용

from contextlib import contextmanager

@contextmanager
def isolated_workspace():
    # 워크스페이스 초기화 로직
    yield
    # 워크스페이스 정리 로직 (캐시 비우기 등)

with isolated_workspace():
    # 워크스페이스 A 로직
    ...

컨텍스트 매니저를 통해 진입·종료 시점에 캐시와 메모리를 정리합니다.

4. 토큰 스트림 분리

  • OpenAI API 호출 시 stream=False 옵션을 명시하거나, 스트리밍을 사용할 경우 별도 스트림 ID를 부여합니다.

5. 테스트 자동화

def test_isolation():
    # 워크스페이스 A와 B를 순차적으로 호출하고,
    # 기대하는 컨텍스트가 섞이지 않았는지 어서션
    assert "Paris" in response_a1
    assert "sarcastic" in response_b1
    assert "quantum" in response_a2

CI 파이프라인에 컨텍스트 격리 테스트를 포함시켜 회귀를 방지합니다.

실제 적용 사례

문제적용한 해결책결과
AI 서포트팀고객 문의 처리 중 서로 다른 톤(친절 vs. 직설) 혼합LLM 인스턴스와 프롬프트를 워크스페이스 별로 독립화고객 만족도 12% 상승, 오답률 4% 감소
데이터 분석팀동일 세션에서 서로 다른 데이터셋 설명이 뒤섞임isolated_workspace 컨텍스트 매니저 도입분석 보고서 정확도 향상, 재현성 98% 달성

결론

LLM을 다중 워크스페이스 환경에서 사용할 때 샌드박스 누수는 예상치 못한 컨텍스트 오염을 초래해 보안·정확도·사용자 경험에 심각한 영향을 미칩니다.
위에서 제시한 인스턴스 독립화, 깊은 복제, 컨텍스트 매니저, 스트림 분리, 자동화 테스트와 같은 전략을 적용하면 대부분의 오염 문제를 방지할 수 있습니다.

핵심 요약

  1. LLM 객체는 워크스페이스마다 별도로 생성한다.
  2. 프롬프트·메모리 객체는 deepcopy 등으로 복제한다.
  3. 실행 전후에 캐시를 명시적으로 정리한다.
  4. CI에 컨텍스트 격리 테스트를 포함한다.

이러한 원칙을 팀 차원에서 표준화한다면, 샌드박스 누수로 인한 위험을 최소화하고, 보다 안전하고 일관된 LLM 서비스를 제공할 수 있습니다.

소개

저는 두 개의 작업 공간을 가지고 있었습니다. 하나는 샌드박스 — 지저분하고 탐색적인, GitHub에 버전 관리가 된 공간이고, 다른 하나는 정제된 포트폴리오 — 다듬어지고 고용주에게 보여지는, 로컬 전용 공간이었습니다. 두 공간 사이의 경계는 구조적으로 명확했습니다: 연구는 샌드박스에 머물고, 완성된 산출물은 일방향으로 포트폴리오에 승격됩니다. 간단했죠.

하지만 그 경계는 계속해서 무너졌습니다.

제 머신에 Systemic_Intelligence_Vault, Systemic_Intelligence_Vault_Antigravity, Systemic_Intelligence_Vault_Claude 라는 세 개의 Obsidian 볼트 복사본이 존재한다는 것을 발견했습니다. 각각은 약간씩 다른 내용을 가진 변형이었습니다. 스크립트가 잘못된 루트 디렉터리를 대상으로 했고, 포트폴리오에서 절대 경로가 샌드박스 파일 안에 하드코딩되어 두 시스템이 독립적이어야 함에도 불구하고 얽혀 있었습니다. 그리고 가장 신경 써야 할 부분—몇 달 전 이미 모든 문제에 대한 해결책을 설계해 두었음에도 불구하고: 비콘 파일, 검증 스크립트, 승격 게이트가 있었습니다. 이들은 프로그램 아키텍처에 문서화돼 있었지만, 실제로는 적용되지 않았습니다.

그때 저는 깨달았습니다: 문서는 경계가 아니다. 시행이 경계다.

저는 John입니다. 지난 6개월 동안 여러 LLM 환경과 작업 공간에 걸쳐 지식 관리 시스템을 구축해 왔습니다 — 연구용 샌드박스, 정제된 작업을 위한 포트폴리오, 그리고 두 공간 모두에서 작동하는 모델들. 이것은 LLM 툴링의 경계에서 구축하기 시리즈의 파트 2이며, 지속적인 LLM 워크플로우에서 무엇이 깨지는지를 다룹니다. 시작은 여기에서 확인하세요.

왜 깨지는가

워크스페이스 간 오염은 극적이지 않다. 엔트로피적인 현상이다. 백업을 위해 폴더를 복사하고, 스크립트에서 경로를 참조하고, 테스트용 변형을 만든다. 각 행동은 작고 합리적이다. 하지만 강제성이 없으면 이들이 누적돼 내가 스파게티 — 뒤얽힌, 불분명한 경계, 지저분한 연구가 정제된 공간으로 스며들고 정제된 가정이 탐색 작업으로 다시 새어 나오는 상황을 만든다.

LLM‑지원 워크플로우는 이 엔트로피를 배가시킨다. 모든 IDE 에이전트, 모든 모델 세션, 모든 도구가 사물이 어디에 존재하고 규칙이 무엇인지에 대한 자체 가정을 만든다. 저장소의 한 복사본에서 동작하는 모델은 다른 복사본이 존재한다는 것을 모른다. 구성 파일이 정식 버전과 다르다는 것도 모른다. 단지 찾은 지시사항을 따를 뿐이다.

이로 인해 내가 계속 마주친 세 가지 오염 벡터가 생겼다.

1. 경로 오염

  • 서로 다른 위치에 프로젝트 복사본이 여러 개 있어 단일 정식 루트가 없음.
  • 스크립트가 깨짐.
  • 모델이 잘못된 디렉터리를 참조함.

내 MP 어시스턴트가 이전에 “잘못된 루트, 잘못된 이름, 따옴표 ~” 오류를 겪었다는 것을 알려주고, 이를 방지하기 위해 비콘 파일을 설계했었다는 신호를 줬을 때가 바로 그 순간이었다. 나는 같은 문제를 두 번째로 해결하고 있었는데, 첫 번째 해결책은 문서였고, 게이트가 아니었다.

2. 행동 오염

  • 눈에 보이지 않아 더 악화됨.

  • 메인 볼트와 Antigravity 변형 간 차이점은 거의 동일한 내용이었지만, .cursorrules 파일이 달랐다:

    • 메인 볼트: “You are working inside the Systemic Intelligence Vault — a living knowledge system using Expansive Closure Protocol methodology.”
    • Antigravity 변형: “You are working inside a local Obsidian vault.”

    동일한 내용, 동일한 프롬프트, 완전히 다른 AI 행동 — 프로젝트, 방법론, 파일 처리에 대한 가정이 달라졌다. 오류 메시지는 없었고, 구성 파일을 비교하기 전까지는 설명할 수 없는 잘못된 출력만 조용히 발생했다.

3. 승격 오염

  • 샌드박스에서 포트폴리오로 흐르는 일방향 흐름은 깨끗한 게이트가 되어야 함.
  • 아키텍처 문서에 명시됨: “지저분한 연구가 MP에 섞여 들어가는 교차 오염은 금지; 승격은 일방향이어야 함.”
  • 사전 검증이 없으면 초안이 정제된 공간으로 새어 나감.
  • 출처 추적이 없으면 원본과 승격된 아티팩트 사이의 계보를 잃게 됨.
  • 레인 강제 적용이 없으면 원시 채팅 전사와 에이전트 로그가 완성된 작업과 함께 실수로 승격될 수 있음.

시도한 내용

첫 번째 직감은 절차적 — 체크리스트, 인수인계 프로토콜, 명명 규칙이었습니다.

  • 승격이 금지된 폴더를 문서화했습니다.
  • 단방향 흐름을 설명하는 아키텍처 문서를 작성했습니다.
  • 샌드박스에서는 YYYYMMDD_Title_v#.#.md 형식의 명명 규칙을 만들고, 포트폴리오에서도 유사한 규칙을 적용했습니다.
  • 개념, 절차, 의미, 구조, 행동의 다섯 가지 카테고리별 가드레일을 상세히 정의했습니다.

그럼에도 유지되지 않았습니다. 수동적인 규율은 부하가 걸리면 약해집니다. 빠르게 움직일 때는 체크리스트를 건너뛰고, 프로젝트에서 일주일 떨어져 있으면 어느 복사본이 정규본인지 잊어버리게 됩니다.

그래서 기술적 강제로 전환했습니다. 나타난 패턴은 세 층으로 구성되었습니다.

1. 정규 루트를 위한 비콘 파일

포트폴리오의 실제 루트에 0바이트 .MASTER_PORTFOLIO_ROOT 파일을 두었습니다. 모든 스크립트는 실행 전에 이를 확인합니다. 비콘이 없으면 스크립트는 즉시 실패하고, 잘못된 디렉터리에서 조용히 동작하지 않습니다.

“하나의 정규 루트를 선택하고 나머지는 모두 복사본으로 취급한다.” – MP 어시스턴트

구현은 아주 간단한 bash 스크립트입니다:

#!/usr/bin/env bash
# abort if the beacon file is not present in the current directory tree
if ! find . -type f -name ".MASTER_PORTFOLIO_ROOT" -print -quit | grep -q .; then
  echo "Error: .MASTER_PORTFOLIO_ROOT not found – aborting."
  exit 1
fi
# …rest of script…

아주 단순해 보이지만 바로 그 때문에 효과가 있습니다. 하드 게이트이기 때문에 잊어버릴 수 없습니다. 부드러운 관습이 아니라 강제적인 장벽이기 때문입니다.

2. 경계 간 작업 전 사전 점검

샌드박스에서 포트폴리오로 이동하기 전에 스크립트가 다음을 검증합니다:

  1. 원본 파일이 실제로 샌드박스 저장소에 있나요?
  2. 허용된 라인에서 온 파일인가요?
  3. 포트폴리오와 결합을 일으킬 수 있는 하드코딩된 경로가 포함되어 있나요?
  4. 메타데이터가 승격 준비 완료로 표시되어 있나요?

어떠한 실패도 작업을 차단합니다.

3. 포인터 전용 출처 기록

원본 컨텍스트를 포트폴리오에 복사하는 대신, 승격된 아티팩트는 메타데이터만 포함한 출처 기록을 가지고 이동합니다:

provenance:
  source_system: sandbox
  source_reference: "Systemic_Intelligence_Vault/20240312_DeepDive_v1.2.md"
  date: 2024-03-12
  promoted_items:
    - "DeepDive_v1.2.md"
  excluded_items:
    - "raw_chat_log.txt"
  rationale: "Final report for client presentation"
  hash: "sha256:3a7f5e..."

내용이 섞이지 않습니다. 포트폴리오는 원본 연구로 돌아가는 검증 가능한 계보를 유지하면서도 깔끔하게 유지됩니다.

요약

문제발생 원인간단한 해결책견고한 해결책
경로 오염복수의 복사본, 정규 루트 없음루트 문서화비콘 파일 + 스크립트 가드
동작 오염복사본에 숨겨진 서로 다른 설정 파일수동 차이점 확인사전 검사(pre‑flight)를 통한 설정 일관성 강제
프로모션 오염자동 게이트 없음, 수동 복사‑붙여넣기체크리스트사전 검사 + 출처 포인터

문서는 무엇이 일어나야 하는지 알려줍니다. 시행은 그것이 실제로 일어나도록 보장합니다.

추적 가능성 체인은 유지됩니다.

동작 오염 문제에 대한 해결책은 사고방식의 전환을 요구했습니다: 설정 파일을 로컬 선호도가 아니라 시스템의 일부로 취급합니다. *.cursorrules**.claude/settings.json*를 버전 관리합니다. 하나의 복사본을 정규 로 표시하고 모든 변형을 명시적으로 라벨링합니다. AI가 예상과 다르게 동작할 때 첫 번째 진단은 **“내가 어느 복사본에 있나요?”**이며, 비콘 시스템을 통해 즉시 답을 얻을 수 있습니다.

밝혀진 내용

LLM 워크플로우의 경계는 두 단계에 존재하지만, 대부분의 실무자는 첫 번째 단계만 구축합니다.

  1. 개념적 수준 – 이 공간은 탐색을 위한 것이고, 그 공간은 완성된 작업을 위한 것이며, 승인은 한 방향으로 흐릅니다. 작업 공간 위생에 대해 생각하는 사람들은 여기서 멈춥니다. 그들은 아키텍처를 문서화하고 스스로 이를 따를 것이라고 믿습니다.

  2. 강제 수준 – 잘못된 루트에 있을 경우 스크립트를 실패하게 하는 비콘, 금지된 레인을 차단하는 사전 검사, 보이지 않는 행동 변화를 방지하는 버전 관리된 설정 파일. 경계가 실제로 유지되는 곳이 바로 여기입니다.

첫 번째와 두 번째 수준 사이의 격차가 바로 스파게티가 자라나는 곳입니다. 그 징후는 반복되는 실패입니다. 제가 직접 만든 어시스턴트가 몇 달 전에 이미 비콘과 검증 스크립트를 설계했음에도 같은 실패가 다시 발생하고 있음을 알려줬을 때, 그것이 신호였습니다. 같은 오염 패턴이 두 번 발생한다면 설계에 실패한 것이 아니라 강제에 실패한 것입니다.

또 다른 통찰은 보이지 않는 오염에 관한 것입니다.

  • 콘텐츠 드리프트는 시끄럽습니다 — 파일을 diff 하면 어떤 부분이 바뀌었는지 확인하고 차이를 병합할 수 있습니다.
  • 구성 드리프트는 조용합니다. 금고 복사본마다 다른 *.cursorrules*가 존재하면 오류 메시지나 내용상의 눈에 띄는 차이 없이 AI 행동이 달라지고, 결과만 미묘하게 잘못된 느낌을 줍니다. 이는 신호가 없기 때문에 찾기 가장 어려운 오염이며, 직접 찾아보려는 생각이 들 때까지는 알 수 없습니다.

재사용 가능한 규칙

탐색용 워크스페이스와 정제된 워크스페이스를 별도로 유지하고 — 그 공간에서 LLM 에이전트가 작동한다면 — 별도의 강제 인프라가 없을 경우 오염이 기본값이 됩니다.

  1. 진단부터 시작하세요.

    • 파일이 어디에 있는지 다시 찾고 있다는 것을 깨달았다면, 그것은 경로 오염입니다 — 비콘 파일을 두고 스크립트가 이를 확인하도록 하세요.
    • 동일한 프롬프트에 대해 AI가 예상치 못하게 다른 출력을 만든다면, 현재 사용 중인 워크스페이스 복사본을 확인하세요 — 구성 드리프트가 원인일 가능성이 높습니다.
    • 샌드박스에서 포트폴리오로 작업을 승격시킬 때, 승격 경로에 사전 검증 단계가 있는지, 아니면 자신의 주의에만 의존하고 있는지 물어보세요. 자신의 주의는 실패합니다.
  2. 반-스파게티 원칙:

    워크스페이스 간의 모든 경계에는 해당하는 강제 메커니즘이 필요합니다.

    • 개념적 경계는 의도를 문서화합니다.
    • 강제 메커니즘은 그 의도를 보존합니다.

같은 실패가 반복될 때, 빠진 조각은 거의 디자인 자체가 아니라 — 디자인을 선택 사항이 아니게 만드는 게이트입니다.

0 조회
Back to Blog

관련 글

더 보기 »

OpenClaw는 설계상 안전하지 않다

OpenClaw는 설계상 안전하지 않다. Cline 공급망 공격, 2월 17일. 인기 있는 VS Code 확장 프로그램인 Cline이 침해되었다. 공격 체인은 여러 AI‑...