샌드박싱과 격리
왜 샌드박싱이 필요한가?
섹션 제목: “왜 샌드박싱이 필요한가?”에이전트가 코드를 실행하고 파일을 수정할 수 있다면, 하나의 잘못된 명령이 호스트 시스템 전체에 영향을 미칠 수 있습니다. 이를 막는 것이 샌드박싱(Sandboxing) — 에이전트의 실행 환경을 호스트와 격리하는 기술입니다.
샌드박싱에는 크게 두 가지 접근법이 있습니다.
| 방식 | 설명 | 강도 |
|---|---|---|
| 스키마 레벨 격리 | 위험한 도구를 에이전트 스키마에서 제거 | 중간 |
| 런타임 권한 검사 | 도구 호출 시 실시간으로 권한 확인 | 중간 |
| 컨테이너 격리 | 별도 OS 레벨 격리 환경에서 실행 | 강함 |
| 전용 샌드박스 서비스 | E2B 같은 관리형 격리 환경 사용 | 매우 강함 |
스키마 레벨 vs 런타임 검사
섹션 제목: “스키마 레벨 vs 런타임 검사”스키마 레벨 격리
섹션 제목: “스키마 레벨 격리”에이전트에게 제공하는 도구 목록 자체에서 위험 도구를 제거합니다. 에이전트는 해당 도구의 존재를 알지 못하므로 호출 시도 자체가 불가능합니다.
// 읽기 전용 에이전트를 위한 도구 스키마const readOnlyTools = [ { name: "read_file", description: "파일 내용 읽기" }, { name: "list_directory", description: "디렉터리 목록 조회" }, { name: "search_files", description: "파일 내용 검색" },];
// exec_shell, write_file, delete_file 은 스키마에 포함하지 않음const agent = new Agent({ tools: readOnlyTools });런타임 권한 검사
섹션 제목: “런타임 권한 검사”도구 스키마에는 포함되어 있지만, 실제 실행 시점에 권한을 확인하고 차단합니다.
def execute_tool(tool_name: str, params: dict, context: AgentContext) -> Any: # 런타임 권한 테이블 확인 if tool_name not in context.allowed_tools: raise PermissionError(f"{tool_name}은 이 컨텍스트에서 허용되지 않습니다")
# 경로 제한 검사 if tool_name == "write_file": path = params.get("path", "") if not is_within_allowed_paths(path, context.allowed_write_paths): raise PermissionError(f"허용되지 않은 경로: {path}")
return tools[tool_name](**params)파일 시스템 격리
섹션 제목: “파일 시스템 격리”허용 경로 화이트리스트
섹션 제목: “허용 경로 화이트리스트”ALLOWED_READ_PATHS = ["/workspace/src", "/workspace/tests"]ALLOWED_WRITE_PATHS = ["/workspace/src", "/workspace/output"]FORBIDDEN_PATHS = ["/etc", "/sys", "/proc", "/home", "~/.ssh"]
def validate_path(path: str, operation: str) -> None: resolved = os.path.realpath(path) # symlink 해결
# 금지 경로 확인 for forbidden in FORBIDDEN_PATHS: if resolved.startswith(forbidden): raise SecurityError(f"금지된 경로: {path}")
# 쓰기 작업 화이트리스트 확인 if operation == "write": allowed = any(resolved.startswith(p) for p in ALLOWED_WRITE_PATHS) if not allowed: raise SecurityError(f"쓰기 불가 경로: {path}")Chroot / 바인드 마운트
섹션 제목: “Chroot / 바인드 마운트”# Docker로 작업 디렉터리만 마운트docker run --rm \ -v /host/workspace:/workspace:rw \ -v /host/readonly-data:/data:ro \ --network none \ agent-image:latest셸 실행 격리
섹션 제목: “셸 실행 격리”셸 명령 실행은 에이전트 도구 중 가장 강력하고 위험합니다.
import subprocessimport shlex
ALLOWED_COMMANDS = {"python", "node", "npm", "pytest", "git"}SHELL_TIMEOUT = 30 # 초
def safe_exec(command: str) -> str: # 명령어 파싱 및 허용 목록 확인 parts = shlex.split(command) if not parts or parts[0] not in ALLOWED_COMMANDS: raise SecurityError(f"허용되지 않은 명령: {parts[0] if parts else 'empty'}")
result = subprocess.run( parts, capture_output=True, text=True, timeout=SHELL_TIMEOUT, cwd="/workspace", # 작업 디렉터리 제한 env={"PATH": "/usr/bin"}, # 환경 변수 제한 ) # 출력 크기 제한 (토큰 폭발 방지) return result.stdout[:10_000]네트워크 격리
섹션 제목: “네트워크 격리”| 격리 수준 | 구현 방법 | 허용 범위 |
|---|---|---|
| 완전 차단 | --network none (Docker) | 없음 |
| 내부망만 허용 | iptables / 방화벽 규칙 | 사내 API만 |
| 도메인 화이트리스트 | 프록시 서버 경유 | 허용된 외부 API만 |
| 전체 허용 | 제한 없음 | 모든 인터넷 (위험) |
Docker 기반 격리
섹션 제목: “Docker 기반 격리”FROM python:3.12-slim
# 최소 권한 사용자RUN useradd -m -u 1000 agentUSER agent
WORKDIR /workspace
# 필요한 패키지만 설치COPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
# 실행 시 네트워크 · 마운트 옵션은 docker run에서 제어CMD ["python", "agent_runner.py"]docker run --rm \ --user 1000:1000 \ --memory 512m \ --cpus 1 \ --network none \ --read-only \ --tmpfs /tmp:size=100m \ -v $(pwd)/workspace:/workspace:rw \ agent-sandbox:latestE2B — 관리형 샌드박스 서비스
섹션 제목: “E2B — 관리형 샌드박스 서비스”E2B는 에이전트를 위한 관리형 클라우드 샌드박스입니다. 직접 Docker를 관리하지 않고 안전한 코드 실행 환경을 제공합니다.
import { Sandbox } from '@e2b/code-interpreter';
const sandbox = await Sandbox.create();
// 에이전트가 생성한 코드를 격리된 환경에서 실행const result = await sandbox.runCode(`import pandas as pddf = pd.read_csv('/data/input.csv')print(df.describe())`);
console.log(result.text);await sandbox.kill();샌드박싱은 스키마 레벨(도구 제거), 런타임 검사(실행 시 권한 확인), 컨테이너 격리(Docker), 관리형 서비스(E2B) 네 계층으로 구성됩니다. 파일 시스템 경로 제한, 셸 명령 화이트리스트, 네트워크 차단을 조합하여 에이전트가 의도치 않게 호스트 시스템에 영향을 미치는 것을 차단합니다.