테마 전환

AI Agent Sandbox 구축 가이드: 안전한 코드 실행 환경 만들기

Easton editorial illustration: one shielded sandbox containing an agent code cube

2025년 봄, 보안 연구원들은 YCombinator Spring 배치에 공개된 AI Agent 16개를 모두 테스트했습니다. 그 결과 7개가 공격에 뚫렸습니다. 어떤 Agent는 사용자 데이터를 유출했고, 어떤 Agent는 원격 코드 실행을 허용했으며, 데이터베이스를 통째로 삭제한 사례도 있었습니다.

AI Agent에게 코드를 실행하게 할 때 치러야 하는 대가입니다. 자유롭게 두면 위험도 함께 커집니다.

AI로 코드를 작성하고, 스크립트를 실행하고, 데이터를 처리하는 일은 이제 흔합니다. 하지만 대규모 언어 모델이 생성한 코드를 서버에서 바로 실행해도 될까요? rm -rf /를 실행하거나 AWS 키를 외부 서버로 몰래 전송한다면 손쓸 틈도 없습니다.

이것이 Agent Sandbox가 필요한 이유입니다.

AI Agent와 기존 애플리케이션의 가장 큰 차이는 대화 능력도, 지시 이해 능력도 아닙니다. 스스로 코드를 작성하고 실행할 수 있다는 점입니다.

이런 상황을 생각해 보겠습니다. 데이터 분석 Agent에게 1GB 규모의 매출 데이터를 처리해 달라고 요청하자 Agent가 Python 코드를 작성해 데이터를 읽고 분석하고 차트를 생성합니다. 코드는 대규모 언어 모델이 자동으로 만들었으며 사람은 검토하지 않았습니다. 그리고 그 코드가 곧바로 실행됩니다.

여기에는 치명적인 위험이 몇 가지 있습니다.

임의 코드 실행. 대규모 언어 모델은 보안 경계를 이해하지 못합니다. os.system(), subprocess.run() 같은 함수를 필요하다고 판단하면 결과를 고려하지 않고 사용합니다. 정교하게 설계된 prompt 하나만으로도 임의의 시스템 명령을 실행하게 만들 수 있습니다.

리소스 고갈 공격. Agent가 작성한 코드는 리소스 한계를 고려하지 않습니다. 무한 루프 하나가 CPU를 모두 점유하고, 끝없는 재귀 호출이 메모리를 가득 채워 서버가 멈출 수 있습니다.

파일 시스템 권한 침해. 파일 접근 경로를 제한하지 않으면 디스크 전체를 읽고 임의의 파일을 쓸 수 있습니다. 설정 파일, 키, 사용자 데이터가 모두 노출됩니다.

네트워크를 통한 데이터 유출. 코드에 HTTP 요청 하나를 숨겨 민감한 데이터를 공격자 서버로 전송해도 알아차리기 어렵습니다.

OWASP는 2025년에 AI Agent 보안 위협 Top 10을 발표했으며, 1위는 ‘Agent 도구 상호작용 조작’이었습니다. 간단히 말해 공격자가 prompt injection이나 다른 수단을 이용해 Agent가 의도와 다르게 도구를 호출하도록 만들 수 있다는 뜻입니다.

실제 공격 사례도 이미 있습니다.

  • Langflow RCE 취약점: Horizon3가 발견한 원격 코드 실행 취약점으로, 공격자가 악성 입력을 통해 서버에서 임의의 코드를 실행할 수 있었습니다.
  • Cursor 자동 실행 취약점: Cursor가 특정 MCP 명령을 자동으로 실행하며, 공격자가 악성 prompt를 구성해 이를 유발할 수 있다는 사실이 연구를 통해 드러났습니다.
  • Replit 데이터베이스 삭제: AI가 생성한 코드가 실수로 데이터베이스 전체를 삭제했습니다.

샌드박스는 선택 사항이 아닙니다. 방화벽 없이 서버를 공용 인터넷에 노출하지 않는 것처럼, 샌드박스 없이 AI가 코드를 실행하게 해서는 안 됩니다. 샌드박스는 AI Agent 애플리케이션의 기반 인프라입니다.

샌드박스의 핵심 가치는 세 가지입니다. 격리는 위험한 코드를 울타리 안에 가두고, 제한은 CPU, 메모리, 네트워크, 파일 접근에 상한을 설정하며, 감사는 실행한 작업을 기록해 사고 발생 시 추적할 수 있게 합니다.

주요 샌드박스 기술 비교

샌드박스가 필요하다는 사실을 알았다면 다음 질문은 어떤 기술을 선택할지입니다. 현재 시장의 주요 기술 경로는 컨테이너(Docker), gVisor, Firecracker microVM 세 가지입니다.

먼저 비교표로 차이를 살펴보겠습니다.

방식보안 격리시작 속도리소스 효율적합한 환경
Docker 컨테이너★★☆☆☆★★★★★★★★★★개발 테스트, 위험도가 낮은 코드
gVisor★★★★☆★★★★☆★★★☆☆프로덕션 환경, 중간 위험도
Firecracker★★★★★★★★★☆★★★☆☆높은 보안 요구 사항, 프로덕션 배포

Docker 컨테이너: 빠르지만 충분히 안전하지 않음

Docker는 가장 일반적인 선택입니다. 시작이 빠르고 리소스 사용량이 적으며 생태계도 성숙했습니다. 하지만 Docker 컨테이너는 호스트와 커널을 공유합니다.

컨테이너 내부 프로세스는 namespace로 격리되지만, 공격자가 커널 취약점을 악용하면 컨테이너 경계를 넘어 호스트의 root 권한을 얻을 수 있다는 뜻입니다.

2024년에도 여러 컨테이너 탈출 취약점이 공개됐습니다. AI가 생성한 신뢰할 수 없는 코드를 실행하기에는 Docker의 보안 경계만으로 충분하지 않습니다.

gVisor: 사용자 공간에 ‘가상 커널’ 만들기

gVisor는 Google이 공개한 오픈 소스 프로젝트입니다. 호스트 커널을 직접 사용하는 대신 사용자 공간에 Sentry라는 ‘가상 커널’을 구현합니다.

컨테이너 안의 프로그램이 시스템 호출을 실행하면 gVisor가 이를 가로채 Sentry에서 처리합니다. Sentry는 안전한 작업만 허용하고 위험한 작업은 거부합니다. 따라서 코드가 악의적인 작업을 시도해도 실제 커널에는 접근할 수 없습니다.

gVisor는 호환성이 좋아 대부분의 Docker 이미지를 그대로 실행할 수 있습니다. 단점은 약 10~20%의 성능 저하가 있고 일부 특수 시스템 호출을 지원하지 않을 수 있다는 점입니다.

GKE(Google Kubernetes Engine)는 gVisor를 기본 지원합니다. Pod 설정에 runtimeClassName: gvisor 한 줄만 추가하면 사용할 수 있습니다.

Firecracker: 실제 하드웨어 수준 격리

Firecracker는 AWS가 공개한 오픈 소스 microVM 기술입니다. 각 샌드박스는 독립적인 커널을 가진 소형 가상 머신으로 실행됩니다.

공격자가 샌드박스 안에서 root 권한을 얻고 커널 취약점까지 악용해도 하나의 가상 머신 안에서만 움직일 뿐 호스트에는 전혀 영향을 주지 못합니다.

Firecracker는 100~800밀리초 안에 시작할 수 있고, 기존 가상 머신보다 리소스 오버헤드가 훨씬 작습니다. 가상 머신 하나에 필요한 최소 메모리는 128MB에 불과합니다.

E2B, AWS Bedrock AgentCore 같은 전문 AI 코드 샌드박스 서비스도 내부적으로 Firecracker를 사용합니다.

선택 기준

선택은 다음과 같은 간단한 의사결정 트리로 정리할 수 있습니다.

  1. 로컬에서 개발하고 테스트하기만 하나요? Docker면 충분하며 편리하고 빠릅니다.
  2. 프로덕션 환경에 배포하나요?
    • 보안 요구 사항이 중간 수준이고 성능이 중요함 → gVisor
    • 보안 요구 사항이 높고 규정을 준수해야 함 → Firecracker
  3. 인프라를 직접 운영하고 싶지 않나요? E2B나 Bedrock AgentCore 같은 관리형 서비스를 사용합니다.

실습: 로컬 개발용 샌드박스 구축하기

이제 이론을 실제 환경으로 옮겨 보겠습니다. 사용할 조합은 FastAPI + Jupyter Kernel + gVisor 컨테이너입니다.

이 조합을 선택한 이유는 다음과 같습니다.

  • FastAPI는 간결한 HTTP API를 제공하므로 AI Agent가 REST API를 통해 코드 실행을 요청할 수 있습니다.
  • Jupyter Kernel은 변수를 유지할 수 있는 대화형 Python 실행 환경을 제공합니다.
  • gVisor 컨테이너는 안전한 격리를 제공해 악성 코드가 호스트에 영향을 주지 못하게 합니다.

1단계: FastAPI 서비스 작성하기

main.py 파일을 만듭니다.

# main.py
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from jupyter_client.manager import AsyncKernelManager
from pydantic import BaseModel

app = FastAPI()

class CodeRequest(BaseModel):
    code: str

class ExecutionResult(BaseModel):
    output: str

@asynccontextmanager
async def kernel_client():
    """Jupyter Kernel의 수명 주기를 관리합니다."""
    km = AsyncKernelManager(kernel_name="python3")
    await km.start_kernel()
    kc = km.client()
    kc.start_channels()
    await kc.wait_for_ready()
    try:
        yield kc
    finally:
        kc.stop_channels()
        await km.shutdown_kernel()

async def execute_code(code: str, timeout: int = 30) -> str:
    """코드를 실행하고 결과를 반환합니다."""
    async with kernel_client() as kc:
        msg_id = kc.execute(code)
        try:
            while True:
                reply = await asyncio.wait_for(
                    kc.get_iopub_msg(),
                    timeout=timeout
                )
                if reply["parent_header"]["msg_id"] != msg_id:
                    continue
                msg_type = reply["msg_type"]
                if msg_type == "stream":
                    return reply["content"]["text"]
                elif msg_type == "error":
                    return f"Error: {reply['content']['evalue']}"
                elif msg_type == "status" and reply["content"]["execution_state"] == "idle":
                    break
        except asyncio.TimeoutError:
            return "Error: Execution timed out"
    return ""

@app.post("/execute", response_model=ExecutionResult)
async def execute(request: CodeRequest):
    """코드 실행 API입니다."""
    try:
        output = await execute_code(request.code)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
    return ExecutionResult(output=output)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

이 코드의 핵심 로직은 실행 요청을 받을 때마다 독립된 Jupyter Kernel을 시작해 코드를 실행하고 결과를 반환한 뒤 Kernel을 종료하는 것입니다.

2단계: Dockerfile 작성하기

FROM jupyter/base-notebook:latest

WORKDIR /app

COPY main.py /app/main.py
COPY requirements.txt /app/requirements.txt

RUN pip install --no-cache-dir -r requirements.txt

# root가 아닌 사용자 사용(보안 모범 사례)
USER jovyan

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

USER jovyan 설정에 주목해야 합니다. root가 아닌 사용자로 컨테이너를 실행하면 코드가 격리 경계를 벗어나더라도 얻을 수 있는 권한이 제한됩니다.

3단계: GKE에 배포하고 gVisor 활성화하기

GKE를 사용한다면 Pod 설정에 다음 한 줄만 추가하면 됩니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: agent-sandbox
spec:
  template:
    spec:
      runtimeClassName: gvisor  # 핵심: gVisor 활성화
      containers:
      - name: sandbox
        image: your-registry/agent-sandbox:latest
        ports:
        - containerPort: 8000
        resources:
          limits:
            memory: "512Mi"
            cpu: "500m"

이제 코드 실행 환경이 gVisor 샌드박스 안에서 동작합니다.

4단계: 보안 제한 추가하기

위 설정만으로는 아직 충분하지 않습니다. 프로덕션 환경에서는 적어도 다음 제한을 추가해야 합니다.

# 네트워크 정책: 필요한 API에만 접근하도록 제한
# 파일 시스템을 읽기 전용으로 설정
securityContext:
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false

# 실행 제한 시간 설정
# FastAPI 코드에 timeout 매개변수가 이미 추가되어 있음

고급 구성: Kubernetes 클러스터 배포

AI Agent 애플리케이션을 대규모로 배포해야 한다면 단일 컨테이너만으로는 부족합니다. 이때 Kubernetes의 Agent Sandbox 컨트롤러를 사용할 수 있습니다.

Google은 2025년에 선언형 샌드박스 관리 API를 제공하는 Agent Sandbox 프로젝트를 오픈 소스로 공개했습니다.

Sandbox CRD의 핵심 개념

Agent Sandbox는 다음과 같은 사용자 정의 리소스를 정의합니다.

  • Sandbox: 안정적인 ID, 영구 스토리지, 수명 주기 관리를 제공하는 개별 샌드박스 인스턴스
  • SandboxTemplate: 표준 구성을 정의하는 샌드박스 템플릿
  • SandboxClaim: 필요할 때 샌드박스 인스턴스를 요청하는 리소스

간단한 Sandbox 설정 예시는 다음과 같습니다.

apiVersion: sandbox.k8s.io/v1alpha1
kind: Sandbox
metadata:
  name: my-agent-sandbox
spec:
  template:
    spec:
      runtimeClassName: gvisor
      containers:
      - name: executor
        image: python:3.11-slim
        command: ["sleep", "infinity"]
      # 영구 스토리지
      volumes:
      - name: workspace
        emptyDir: {}
      # 리소스 제한
      resources:
        limits:
          memory: "1Gi"
          cpu: "1"

수명 주기 관리

Agent Sandbox의 주요 장점 중 하나는 일시 중지와 재개를 지원한다는 점입니다.

# 샌드박스 일시 중지(CPU와 대부분의 메모리 해제)
kubectl patch sandbox my-agent-sandbox --type=merge -p '{"spec":{"paused":true}}'

# 샌드박스 재개
kubectl patch sandbox my-agent-sandbox --type=merge -p '{"spec":{"paused":false}}'

간헐적으로 실행되는 AI Agent에 특히 유용합니다. 작업이 없을 때 일시 중지하면 리소스를 거의 사용하지 않고, 작업이 생기면 몇 초 안에 재개할 수 있습니다.

웜 풀(Pool)

시작 지연을 더 줄이기 위해 Agent Sandbox는 ‘웜 풀’을 지원합니다. 일시 중지 상태인 샌드박스를 미리 여러 개 만들어 두었다가 필요할 때 바로 활성화하는 방식입니다.

이 방법을 사용하면 샌드박스의 ‘콜드 스타트’ 시간을 초 단위에서 밀리초 단위로 줄일 수 있습니다.

관리형 서비스 선택 가이드

인프라를 직접 운영하고 싶지 않다면 관리형 서비스가 좋은 선택입니다. 현재 주요 서비스는 다음과 같습니다.

E2B: 오픈 소스와 클라우드 호스팅의 두 가지 방식

E2B는 AI Agent를 위해 설계된 코드 샌드박스 서비스입니다. 두 가지 버전을 제공합니다.

  • E2B Cloud: E2B의 클라우드 서비스를 직접 사용하며 사용량에 따라 비용을 지불합니다.
  • E2B on AWS: 오픈 소스 버전을 자체 AWS 계정에 배포합니다.

E2B는 내부적으로 Firecracker를 사용하므로 강한 보안 격리를 제공합니다. SDK도 간결합니다.

from e2b import Sandbox

# 샌드박스 생성
sandbox = Sandbox()

# 코드 실행
result = sandbox.run_code("print('Hello, World!')")

# 샌드박스 종료
sandbox.close()

E2B on AWS는 데이터 주권 요구 사항이 있는 기업에 특히 적합합니다. 모든 데이터가 자체 계정 안에 있으므로 서드파티에 데이터를 맡길 필요가 없습니다.

AWS Bedrock AgentCore

AWS는 2025년에 AI Agent의 코드 실행과 브라우저 조작을 지원하는 Bedrock AgentCore를 출시했습니다.

Code Interpreter는 Python/JavaScript/TypeScript 런타임을 제공합니다. 각 세션은 독립된 microVM에서 실행되며 최대 5GB 파일을 처리할 수 있습니다.

Browser Tool을 사용하면 AI Agent가 브라우저에서 웹 페이지를 열고, 양식을 작성하고, 버튼을 클릭할 수 있습니다. 웹 데이터를 수집하거나 SaaS 애플리케이션을 조작해야 하는 Agent에 특히 유용합니다.

과금 방식도 합리적입니다. 인스턴스 실행 시간이 아니라 실제 vCPU 및 메모리 사용 시간에 따라 비용이 부과됩니다. 코드 실행이 끝나면 리소스가 해제되므로 비용 낭비를 줄일 수 있습니다.

선택 권장 사항

상황권장 방식
빠른 검증, 소규모 애플리케이션E2B Cloud
엔터프라이즈 환경, 데이터 로컬라이제이션 필요E2B on AWS 또는 Bedrock AgentCore
AWS 생태계를 깊이 사용 중Bedrock AgentCore
브라우저 자동화 필요Bedrock AgentCore Browser Tool
완전한 자체 제어 및 운영 역량 보유자체 구축 Kubernetes + Agent Sandbox

결론

핵심은 한 문장으로 정리할 수 있습니다. 보안은 선택 사항이 아니라 AI Agent 애플리케이션의 기반 인프라입니다.

기술 선택 기준은 다음과 같습니다.

  • 소규모 팀에서 빠르게 검증한다면 Docker 또는 gVisor로 충분합니다.
  • 엔터프라이즈 애플리케이션이나 높은 보안 요구 사항에는 Firecracker 또는 관리형 서비스를 사용합니다.
  • Kubernetes를 이미 사용하고 있다면 Agent Sandbox 컨트롤러를 도입합니다.

어떤 방식을 선택하든 로컬 테스트부터 시작합니다. 가장 간단한 FastAPI + Docker 구성을 작성해 실행한 뒤 보안 강화와 프로덕션 배포를 검토하면 됩니다.

샌드박스는 가능한 한 일찍 추가해야 합니다. 보안 사고가 발생한 뒤 보완하려면 훨씬 더 큰 비용이 듭니다.

AI Agent 샌드박스 환경 구축하기

안전한 AI 코드 실행 환경을 처음부터 구축합니다.

⏱️ Estimated time: 60 min

  1. 1

    Step 1: FastAPI 서비스 만들기

    main.py 파일을 작성해 코드 실행 API를 구현합니다.

    • AsyncKernelManager로 Jupyter Kernel 관리
    • 실행 제한 시간 설정(기본 30초)
    • 실행 결과 또는 오류 정보 반환
  2. 2

    Step 2: Dockerfile 작성하기

    jupyter/base-notebook 이미지를 기반으로 빌드합니다.

    • 의존성(FastAPI, uvicorn) 설치
    • root가 아닌 사용자(jovyan)로 실행
    • 8000 포트 노출
  3. 3

    Step 3: Kubernetes에 배포하기

    Pod에서 gVisor를 사용하도록 설정합니다.

    • runtimeClassName: gvisor 설정
    • 리소스 제한(CPU/메모리) 구성
    • 보안 컨텍스트(읽기 전용 파일 시스템) 추가
  4. 4

    Step 4: 샌드박스 격리 검증하기

    보안 경계를 테스트합니다.

    • 호스트 파일 시스템 접근 시도(거부되어야 함)
    • 리소스를 많이 사용하는 코드 실행(제한되어야 함)
    • 네트워크 격리가 적용되는지 확인

FAQ

Docker 컨테이너와 gVisor는 무엇이 다른가요?
Docker 컨테이너는 호스트와 커널을 공유하므로 공격자가 커널 취약점을 악용하면 컨테이너를 탈출할 수 있습니다. gVisor는 사용자 공간에 ‘가상 커널’인 Sentry를 구현해 모든 시스템 호출을 가로채고 안전한 작업만 허용하므로 더 강한 격리를 제공합니다.
gVisor 대신 Firecracker를 사용해야 하는 경우는 언제인가요?
보안 요구 사항이 매우 높을 때 Firecracker를 선택합니다.

• 금융 또는 의료 데이터처럼 하드웨어 수준 격리가 필요한 경우
• 엄격한 규정 준수 요건을 충족해야 하는 경우
• 완전히 신뢰할 수 없는 서드파티 코드를 처리하는 경우

gVisor는 성능 저하가 비교적 작아(10~20%) 대부분의 프로덕션 환경에 적합합니다.
E2B와 AWS Bedrock AgentCore 중 무엇을 선택해야 하나요?
E2B는 빠르게 검증하거나 오픈 소스 기반의 제어가 필요한 상황에 적합합니다.
• 소규모 애플리케이션은 E2B Cloud로 시작
• 데이터 로컬라이제이션이 필요하면 E2B on AWS 사용

Bedrock AgentCore는 AWS 생태계를 깊이 사용하는 조직에 적합합니다.
• 기존 AWS 서비스와 더 쉽게 통합 가능
• 브라우저 자동화가 필요하면 Browser Tool 선택
샌드박스가 코드 실행 성능에 영향을 주나요?
어느 정도 영향을 줍니다. Docker는 성능 손실이 거의 없고, gVisor는 약 10~20%, Firecracker는 약 15~30%의 손실이 있습니다. 하지만 데이터 분석이나 스크립트 처리 같은 AI Agent 코드 실행 시나리오에서는 대체로 감수할 수 있는 수준입니다.
로컬 개발 환경에서 샌드박스를 빠르게 구축하려면 어떻게 해야 하나요?
가장 간단한 방법은 gVisor를 적용한 컨테이너를 Docker로 실행하는 것입니다. GKE를 사용한다면 Pod 설정에 `runtimeClassName: gvisor` 한 줄만 추가하면 됩니다. 순수 로컬 개발에서는 Docker 격리만으로도 충분하며, 핵심은 리소스 제한과 사용자 권한을 설정하는 것입니다.

3분 읽기 · 게시일: 2026년 3월 23일 · 수정일: 2026년 9월 4일

댓글

GitHub로 로그인하여 댓글을 남기세요

Easton BlogEaston Blog