샌드박스(Sandboxes)로 에이전트 격리하기
샌드박스(Sandboxes)로 에이전트 격리하기
에이전트는 코드를 만들고, 파일시스템을 다루고, 셸 커맨드를 실행해요. 에이전트가 뭘 할지 예측할 수 없으니, 크레덴셜·파일·네트워크에 접근할 수 없도록 환경을 격리하는 게 중요하죠. 샌드박스는 에이전트 실행 환경과 호스트 시스템 사이에 경계를 만들어 이 격리를 제공해요. Deep Agents에서 **샌드박스는 에이전트가 동작하는 환경을 정의하는 backends**예요. 이 글에서는 샌드박스가 필요한 이유와 패턴, 동작 원리를 알아볼게요.
출처: 공식문서
State·Filesystem·Store 같은 다른 backend는 파일 연산만 노출하지만, 샌드박스 backend는 추가로 에이전트에게 셸 커맨드를 실행하는 execute 도구를 줘요. 샌드박스 backend를 구성하면 에이전트는 다음을 갖습니다.
- 모든 표준 파일시스템 도구 (
ls,read_file,write_file,edit_file,delete,glob,grep) - 샌드박스에서 임의의 셸 커맨드를 실행하는
execute도구 - 호스트 시스템을 보호하는 안전한 경계
왜 샌드박스를 쓸까?
샌드박스는 보안 때문이에요. 에이전트가 크레덴셜·로컬 파일·호스트 시스템을 위험에 빠뜨리지 않으면서 임의 코드를 실행하고, 파일에 접근하고, 네트워크를 쓸 수 있게 해요. 에이전트가 자율적으로 실행될 때 이 격리는 필수적이죠. 샌드박스는 특히 다음에 유용해요.
- 코딩 에이전트: 자율 실행되는 에이전트가 셸·git을 쓰고, 리포를 클론하고(많은 프로바이더가 네이티브 git API 제공, 예: Daytona의 git 연산), 빌드·테스트 파이프라인에 Docker-in-Docker를 실행할 수 있어요.
- 데이터 분석 에이전트: 파일을 로드하고, 데이터 분석 라이브러리(pandas, numpy 등)를 설치하고, 통계 계산을 돌리고, 안전한 격리 환경에서 PowerPoint 같은 산출물을 만들 수 있어요.
기본 사용법 (Basic usage)
아래 예시들은 이미 프로바이더 SDK로 샌드박스/devbox를 만들고 크레덴셜을 설정했다고 가정해요. 가입·인증·프로바이더별 생명주기 상세는 Available providers를 참고하세요.
- LangSmith, Daytona, E2B, Modal, Runloop, Vercel
사용 가능한 프로바이더 (Available providers)
프로바이더별 설정·인증·생명주기 상세는 sandbox integrations를 참고하세요.
생명주기와 범위 (Lifecycle and scoping)
대부분의 애플리케이션은 thread당 샌드박스 하나(thread-scoped) 또는 같은 assistant의 모든 스레드가 공유하는 샌드박스 하나(assistant-scoped)를 골라요. 샌드박스는 종료할 때까지 리소스를 소비하고 돈이 들어요. 더 이상 안 쓰면 반드시 종료하세요. 전체 생명주기 표, 비동기 graph factory 노트, TTL 동작, LangGraph Deployment 연결, 클라이언트 예시는 Going to production의 Sandbox lifecycle을 참고하세요.
Thread-scoped (기본값)
각 대화가 자기 샌드박스를 가져요. 첫 실행이 만들고, 같은 스레드의 후속 턴이 재사용해요. 스레드가 끝나거나 샌드박스 TTL이 만료되면 환경이 사라져요. 아래 예시처럼 매핑을 샌드박스 이름이나 메타데이터로 저장해서, 각 실행이 같은 샌드박스로 resolve 되게 하세요.
Assistant-scoped
같은 assistant의 모든 스레드가 하나의 샌드박스를 재사용해요. 파일·설치된 패키지·클론된 리포가 대화 간에 유지되죠.
graph factory 밖에서 수동으로 만들고 실행하고 정리하려면 Basic usage와 sandbox integrations의 프로바이더별 API를 참고하세요.
통합 패턴 (Integration patterns)
에이전트가 어디서 실행되느냐에 따라 샌드박스와 통합하는 두 가지 아키텍처 패턴이 있어요.
에이전트-인-샌드박스 패턴 (Agent in sandbox pattern)
에이전트가 샌드박스 안에서 실행되고 네트워크로 통신해요. 에이전트 프레임워크를 미리 설치한 Docker나 VM 이미지를 만들고 샌드박스 안에서 실행한 뒤, 외부에서 연결해 메시지를 보내는 거죠.
- ✅ 로컬 개발을 밀접하게 반영함
- ✅ 에이전트와 환경이 긴밀하게 결합됨
단점:
- 🔴 API 키가 샌드박스 안에 있어야 함 (보안 위험)
- 🔴 업데이트하려면 이미지 재빌드 필요
- 🔴 통신 인프라(WebSocket 또는 HTTP 레이어) 필요
샌드박스 안에서 에이전트를 실행하려면 이미지를 만들고 그 위에 deepagents를 설치한 뒤, 샌드박스 안에서 에이전트를 실행해요. 샌드박스 안의 에이전트를 쓰려면 애플리케이션과 샌드박스 안 에이전트 사이의 통신을 다루는 추가 인프라를 더해야 합니다.
샌드박스-도구 패턴 (Sandbox as tool pattern)
에이전트가 내 머신이나 서버에서 실행돼요. 코드를 실행해야 할 때 샌드박스 도구(execute, read_file, write_file 등)를 호출하면, 그 도구가 프로바이더 API를 호출해 원격 샌드박스에서 연산을 실행하지요.
- ✅ 이미지 재빌드 없이 에이전트 코드 즉시 업데이트
- ✅ 에이전트 상태와 실행의 더 깔끔한 분리 (API 키가 샌드박스 밖에 있고, 샌드박스 실패가 에이전트 상태를 잃지 않으며, 여러 샌드박스에서 병렬 실행 가능)
- ✅ 실행 시간에 대해서만 비용 지불
단점:
- 🔴 호출마다 네트워크 지연 발생
이 문서의 예시는 샌드박스-도구 패턴을 사용해요. 프로바이더 SDK가 통신 레이어를 처리하고 프로덕션에서 로컬 개발을 반영하고 싶다면 에이전트-인-샌드박스 패턴을, 에이전트 로직을 빨리 반복하고 싶거나 API 키를 샌드박스 밖에 두거나 관심사를 더 깔끔히 분리하고 싶다면 샌드박스-도구 패턴을 고르세요.
샌드박스 동작 원리 (How sandboxes work)
격리 경계 (Isolation boundaries)
모든 샌드박스 프로바이더는 에이전트의 파일시스템·셸 연산으로부터 호스트 시스템을 보호해요. 에이전트는 내 로컬 파일을 읽을 수 없고, 내 머신의 환경 변수에 접근할 수 없고, 다른 프로세스를 방해할 수 없어요. 하지만 샌드박스만으로는 다음을 막지 못해요.
- 컨텍스트 주입(Context injection): 에이전트 입력의 일부를 제어하는 공격자가 에이전트에게 샌드박스 안에서 임의 커맨드를 실행하라고 지시할 수 있어요. 샌드박스는 격리되어 있지만 그 안에서 에이전트는 완전한 제어력을 가져요.
- 네트워크 탈취(Network exfiltration): 네트워크 접근을 막지 않으면 컨텍스트 주입된 에이전트가 HTTP나 DNS로 샌드박스 밖으로 데이터를 보낼 수 있어요. 일부 프로바이더는 네트워크 접근 차단을 지원해요(예: Modal의
blockNetwork: true).
시크릿을 다루고 이런 위험을 완화하는 방법은 보안 고려사항을 참고하세요.
execute 메서드
샌드박스 backend는 단순한 아키텍처예요. 프로바이더가 반드시 구현해야 하는 유일한 메서드는 셸 커맨드를 실행하고 출력을 반환하는 execute()예요. 다른 모든 파일시스템 연산(read, write, edit, delete, ls, glob, grep)은 execute() 위에 BaseSandbox 베이스 클래스가 스크립트를 구성해 샌드박스 안에서 execute()로 실행하는 방식으로 만들어져요. 이 설계는 다음을 의미합니다.
- 새 프로바이더 추가가 간단해요.
execute()만 구현하면 베이스 클래스가 나머지를 모두 처리해요. execute도구는 조건부로 사용 가능해요. 매 모델 호출마다 harness가 backend가SandboxBackendProtocol을 구현했는지 확인해요. 아니면 도구가 필터링되어 에이전트가 절대 못 보게 되죠.
에이전트가 execute 도구를 호출하면 command 문자열을 제공하고 stdout/stderr 결합본, 종료 코드, 출력이 너무 크면 잘림(truncation) 알림을 돌려받아요. 애플리케이션 코드에서 backend의 execute() 메서드를 직접 호출할 수도 있어요.
- LangSmith, AgentCore, Daytona, E2B, Modal, NVIDIA OpenShell, Runloop, Vercel
커맨드가 매우 큰 출력을 만들면 결과가 자동으로 파일에 저장되고, 에이전트는 컨텍스트 윈도우 오버플로를 막기 위해 read_file로 증분 접근하라는 안내를 받아요.
파일 접근의 두 평면 (Two planes of file access)
파일이 샌드박스로/에서 이동하는 방식이 두 가지로 뚜렷하게 나뉘는데, 언제 어느 것을 쓸지 아는 게 중요해요.
에이전트 파일시스템 도구: read_file, write_file, edit_file, delete, ls, glob, grep, execute는 LLM이 실행 중 호출하는 도구예요. 이것들은 샌드박스 안의 execute()를 통해 동작해요. 에이전트가 작업의 일부로 코드를 읽고, 파일을 쓰고, 커맨드를 실행할 때 써요.
파일 전송 API: 애플리케이션 코드가 호출하는 uploadFiles()와 downloadFiles() 메서드예요. 이것들은 셸 커맨드가 아닌 프로바이더의 네이티브 파일 전송 API를 쓰며, 호스트 환경과 샌드박스 사이에서 파일을 이동하도록 설계됐어요. 다음에 쓰면 좋아요.
- 에이전트가 실행되기 전에 소스 코드·설정·데이터로 샌드박스를 시드(seed)
- 에이전트가 끝난 뒤 산출물(생성된 코드, 빌드 출력, 보고서) 회수
- 필요할 의존성 사전-채우기
파일 다루기 (Working with files)
deepagents 샌드박스 backend는 애플리케이션과 샌드박스 사이에서 파일을 옮기는 파일 전송 API를 지원해요.
샌드박스 시드하기 (Seeding the sandbox)
에이전트가 실행되기 전에 upload_files()를 써서 샌드박스를 채워요. 경로는 절대 경로여야 하고 내용은 bytes예요. (LangSmith, AgentCore, Daytona, E2B, Modal, Runloop, Vercel)
산출물 회수하기 (Retrieving artifacts)
에이전트가 끝난 뒤 download_files()로 샌드박스에서 파일을 가져와요. (LangSmith, AgentCore, Daytona, E2B, Modal, Runloop, Vercel)
보안 고려사항 (Security considerations)
샌드박스는 호스트 시스템에서 코드 실행을 격리하지만 컨텍스트 주입은 막지 못해요. 에이전트 입력의 일부를 제어하는 공격자가 에이전트에게 샌드박스 안에서 파일을 읽고, 커맨드를 실행하고, 데이터를 탈취하라고 지시할 수 있죠. 이 때문에 샌드박스 안의 크레덴셜은 특히 위험해요.
시크릿 안전하게 다루기 (Handling secrets safely)
에이전트가 인증된 API를 호출하거나 보호된 리소스에 접근해야 한다면 두 가지 옵션이 있어요.
- 시크릿을 샌드박스 밖의 도구에 두세요. 호스트 환경(샌드박스가 아닌)에서 실행되고 거기서 인증을 처리하는 도구를 정의해요. 에이전트는 도구를 이름으로 호출하지만 크레덴셜을 절대 못 봐요. 권장 방식이에요.
- 크레덴셜을 주입하는 네트워크 프록시를 쓰세요. 일부 샌드박스 프로바이더는 샌드박스에서 나가는 HTTP 요청을 가로채고 전달 전에 크레덴셜(예:
Authorization헤더)을 붙이는 프록시를 지원해요. 에이전트는 시크릿을 못 보고 그냥 URL에 일반 요청을 보낼 뿐이죠. 이 방식은 아직 프로바이더별로 널리 지원되진 않아요.
일반 모범 사례 (General best practices)
- 애플리케이션에서 사용하기 전에 샌드박스 출력을 검토하세요
- 필요 없을 때 샌드박스 네트워크 접근을 차단하세요
- middleware로 도구 출력의 민감 패턴을 필터링하거나 리댁트하세요
- 샌드박스 안에서 생산된 모든 것을 신뢰할 수 없는 입력으로 취급하세요
더 알아보기 (Learn more)
- Backends와 sandbox integrations
- Going to production의 Sandbox lifecycle
- Middleware로 출력 필터링