- Published on
AutoGPT·BabyAGI 권한 샌드박스로 탈주 막기
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 AutoGPT·BabyAGI 류 에이전트를 돌리다 보면, 모델 성능보다 먼저 부딪히는 문제가 있습니다. 도구(tool) 권한이 곧 공격면이라는 점입니다. 에이전트는 “목표 달성”을 위해 파일을 읽고, 쉘을 실행하고, 웹에 접속하고, API 키로 결제까지 할 수 있습니다. 이때 도구 호출이 느슨하면, 의도치 않은 데이터 유출·비용 폭탄·시스템 파괴 같은 ‘탈주(escape)’가 발생합니다.
이 글은 AutoGPT·BabyAGI 스타일 에이전트의 도구 권한을 **샌드박스(sandbox) + 정책(policy) + 관측(observability)**으로 감싸서, “할 수 있는 일” 자체를 안전하게 제한하는 실전 설계를 다룹니다. 장기 메모리나 그래프 기반 확장까지 고려한다면, AutoGPT 장기메모리 - Neo4j 그래프RAG 구축처럼 저장소가 늘어나는 만큼 권한 경계도 더 중요해집니다.
왜 AutoGPT·BabyAGI는 ‘도구권한’이 핵심 리스크인가
에이전트 프레임워크는 대체로 아래 루프를 반복합니다.
- 계획 수립: 목표를 쪼개고 다음 액션을 결정
- 도구 호출: 브라우징, 파일 I/O, DB 쿼리, 쉘 실행, 외부 API 호출
- 결과 반영: 관찰 결과로 다음 계획을 갱신
문제는 도구 호출이 “사람이 클릭하는 UI”가 아니라 LLM이 생성하는 문자열로 트리거된다는 점입니다. 즉, 프롬프트 인젝션이나 데이터 오염이 있으면 다음과 같은 상황이 쉽게 생깁니다.
- 웹 페이지의 숨은 지시문에 속아 민감 파일을 읽어 업로드
- 로그에 찍힌 토큰을 읽어 외부로 전송
- 무한 루프 호출로 API 비용 폭증(429/재시도 폭주 포함)
특히 재시도·백오프를 잘못 설계하면 “안정성 개선”이 “비용 폭탄”으로 바뀝니다. 이 주제는 Claude 429 과금폭탄 막는 재시도·백오프 전략과도 직결됩니다.
위협 모델: ‘탈주’는 보통 5가지 경로로 일어난다
샌드박스를 설계하려면 먼저 경로를 분류해야 합니다.
1) 파일 시스템 탈주
~/.ssh,.env, kubeconfig, 서비스 계정 키 등을 읽는 시도- 작업 디렉터리 밖으로
..이동
2) 네트워크 탈주
- 내부망 스캔, 메타데이터 서비스 접근(예: 클라우드 인스턴스 메타데이터)
- 임의의 외부 업로드 엔드포인트로 데이터 전송
3) 실행 권한 탈주
- 쉘 실행으로 패키지 설치, 크론 등록, 백도어 심기
- “테스트”라는 명목으로 위험 명령 실행
4) 비밀키·토큰 탈주
- 에이전트가 로그/파일/환경변수에서 토큰을 수집
- 도구 출력에 토큰이 섞여 LLM 컨텍스트로 재유입
5) 비용·자원 탈주
- 무한 계획 루프, 도구 호출 폭주
- 대용량 다운로드/업로드로 egress 비용 증가
설계 원칙: 샌드박스는 ‘격리’가 아니라 ‘정책 집행 지점’이다
안전한 구조는 대개 다음 4층으로 구성됩니다.
- 도구 인터페이스 레이어: 에이전트가 직접 OS/API를 만지지 못하고, “승인된 도구”만 호출
- 정책 엔진: 도구별 허용 범위(경로, 도메인, 쿼리, 비용)를 검사
- 실행 샌드박스: 컨테이너/VM/Firecracker 같은 격리 런타임
- 관측과 차단: 모든 호출을 로깅하고, 이상 징후에서 즉시 중단
핵심은 “컨테이너로 감쌌으니 끝”이 아니라, 도구 호출 전에 정책을 강제하고 실행 중에도 네트워크·파일·자원 제한을 강제하는 것입니다.
도구권한 샌드박스 아키텍처 (권장)
아래 구조가 운영에서 가장 다루기 쉽습니다.
- 에이전트 프로세스: LLM과 대화, 계획 수립
- Tool Gateway(중앙 관문): 모든 도구 호출은 HTTP/gRPC로 여기만 통과
- Sandbox Runner: 실제 실행(파일/쉘/브라우저)은 격리 환경에서 수행
- Policy Store: allowlist, 비용 한도, 테넌트별 규칙
- Audit Log: 호출/입력/출력/정책결정 기록
이렇게 하면 에이전트가 프레임워크를 바꿔도( AutoGPT, BabyAGI, LangGraph 등) 권한 경계는 동일하게 유지됩니다.
구현 1: “도구 호출”을 강제 게이트웨이로 통과시키기
에이전트가 직접 subprocess나 requests를 쓰는 순간 통제가 무너집니다. 따라서 도구는 “함수”가 아니라 “원격 서비스”로 취급하고, 게이트웨이가 정책을 검사합니다.
아래는 FastAPI로 만든 최소 게이트웨이 예시입니다.
# tool_gateway.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from urllib.parse import urlparse
import os
app = FastAPI()
ALLOWED_DOMAINS = {"api.github.com", "example.com"}
ALLOWED_TOOLS = {"http_get", "read_file"}
WORKDIR = "/workspace"
class ToolCall(BaseModel):
tool: str
args: dict
def ensure_under_workdir(path: str) -> str:
real = os.path.realpath(path)
wd = os.path.realpath(WORKDIR)
if not real.startswith(wd + os.sep):
raise HTTPException(403, "Path escapes workspace")
return real
@app.post("/tool")
def tool(call: ToolCall):
if call.tool not in ALLOWED_TOOLS:
raise HTTPException(403, "Tool not allowed")
if call.tool == "http_get":
url = call.args.get("url", "")
host = urlparse(url).hostname
if host not in ALLOWED_DOMAINS:
raise HTTPException(403, "Domain not allowed")
# 실제 요청은 여기서 수행(타임아웃/크기 제한 필수)
return {"ok": True, "data": "(fetched)"}
if call.tool == "read_file":
path = ensure_under_workdir(call.args.get("path", ""))
with open(path, "r", encoding="utf-8") as f:
content = f.read(8192) # 최대 읽기 크기 제한
return {"ok": True, "content": content}
raise HTTPException(400, "Unknown tool")
포인트는 3가지입니다.
- 도구 allowlist: 도구 종류 자체를 제한
- 입력 검증: URL 도메인, 파일 경로, 최대 크기
- 출력 제한: 파일 전체를 LLM에 주지 말고 상한을 둠
구현 2: 파일 시스템 샌드박스 (워크스페이스 + 읽기 전용)
에이전트가 접근할 수 있는 파일은 원칙적으로 WORKDIR 아래로 제한합니다.
- 컨테이너 실행 시
WORKDIR만 read-write - 나머지는 read-only 또는 마운트하지 않음
- 홈 디렉터리,
/etc,/proc등은 노출 최소화
Docker 기준 예시입니다.
docker run --rm -it \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=256m \
-v $(pwd)/workspace:/workspace:rw \
-e WORKDIR=/workspace \
my-agent-sandbox:latest
여기서 --read-only는 전체 파일 시스템을 읽기 전용으로 만들고, 쓰기가 필요한 곳만 tmpfs나 볼륨으로 열어줍니다. 이 패턴만으로도 “시스템 파일 수정” 류 탈주가 크게 줄어듭니다.
구현 3: 네트워크 샌드박스 (기본 차단 + 목적지 allowlist)
가장 흔한 사고는 “아무 URL로나 업로드”입니다. 해결은 간단합니다.
- 기본 정책: egress 전면 차단
- 예외: 필요한 도메인만 allowlist
- 내부망 대역(예:
10.0.0.0/8,169.254.0.0/16)은 항상 차단
Kubernetes라면 NetworkPolicy로 막는 게 정석입니다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agent-egress-allowlist
spec:
podSelector:
matchLabels:
app: agent-sandbox
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
위 예시는 단순화된 형태라 “전부 허용”처럼 보일 수 있으니, 실제로는 ipBlock과 특정 목적지(프록시)만 허용하는 방식이 실무에서 더 안전합니다. 가장 좋은 패턴은 샌드박스는 외부로 직접 나가지 못하고, HTTP 프록시 한 곳만 나가게 만든 뒤 프록시에서 도메인 allowlist를 강제하는 것입니다.
구현 4: 쉘/코드 실행 도구는 ‘금지’가 아니라 ‘제한된 VM’로
AutoGPT·BabyAGI는 종종 “파이썬 코드를 작성해 실행” 같은 도구를 붙입니다. 이때 로컬에서 subprocess를 열어주면 거의 끝입니다.
권장 패턴은 다음 중 하나입니다.
- Firecracker 같은 마이크로VM에서 실행
- 컨테이너라도
seccomp/AppArmor/capabilities 제거 - 실행 시간, 메모리, 파일/네트워크 접근을 함께 제한
리눅스 capabilities를 제거하는 Docker 예시입니다.
docker run --rm -it \
--cap-drop=ALL \
--security-opt no-new-privileges \
--pids-limit=256 \
--memory=1g \
--cpus=1 \
my-agent-sandbox:latest
여기에 네트워크 차단과 read-only를 결합하면, “코드 실행” 도구가 있더라도 피해 반경을 제한할 수 있습니다.
구현 5: 비밀키는 ‘환경변수’에 두지 말고 “권한 있는 프록시”로
에이전트 컨테이너에 API 키를 넣어두면, 어떤 형태로든 노출될 수 있습니다.
- 로그에 찍힘
- 파일로 덤프됨
- LLM 컨텍스트에 섞임
대신 다음 구조를 추천합니다.
- 에이전트는 “결제 가능한 API”를 직접 호출하지 않음
- Tool Gateway가 서버 측 자격증명으로 호출
- Gateway는 요청 파라미터를 검증하고, 응답에서 민감값을 마스킹
예를 들어 “웹 검색” 도구가 필요하면, 에이전트가 검색 API 키를 갖는 게 아니라 Gateway가 갖고 쿼리 길이/금칙어/호스트를 검증한 뒤 대행 호출합니다.
구현 6: 비용·루프 탈주 방지 (스텝 예산, 타임아웃, 회로 차단기)
에이전트는 종종 “조금만 더” 하다가 끝없이 돈을 태웁니다. 아래 3가지는 필수입니다.
- 스텝 예산: 한 작업(run)당 도구 호출 N회 제한
- 타임아웃: 도구별 최대 실행 시간 제한
- 회로 차단기: 오류율/429/5xx가 일정 수준이면 즉시 중단
특히 5xx가 섞일 때 무한 재시도가 발생하기 쉬운데, API 게이트웨이에서 이를 통제해야 합니다. OpenAI 계열 호출에서 502가 반복될 때의 대응은 OpenAI Responses API 502 Bad Gateway 원인과 해결처럼 “원인 분리 + 재시도 정책”으로 접근해야 합니다.
간단한 “스텝 예산” 예시 코드입니다.
class Budget:
def __init__(self, max_steps: int, max_tool_calls: int):
self.max_steps = max_steps
self.max_tool_calls = max_tool_calls
self.steps = 0
self.tool_calls = 0
def step(self):
self.steps += 1
if self.steps > self.max_steps:
raise RuntimeError("Step budget exceeded")
def tool(self):
self.tool_calls += 1
if self.tool_calls > self.max_tool_calls:
raise RuntimeError("Tool call budget exceeded")
이 예산 체크는 에이전트 루프 내부가 아니라 Tool Gateway에서도 한 번 더 강제하는 게 안전합니다. 에이전트 코드가 버그나 프롬프트 인젝션으로 예산 체크를 무시할 수 있기 때문입니다.
프롬프트 인젝션 대응: “모델에게 맡기지 말고 시스템이 강제”
많은 팀이 “프롬프트에 지침을 넣어서” 위험 행동을 막으려 합니다. 하지만 도구권한은 지침으로 막는 게 아니라, 시스템이 막아야 합니다.
- 모델 지침: 예방 효과는 있으나 우회 가능
- 정책 엔진: 우회 불가능(거부 응답)
실전에서는 아래처럼 결합합니다.
- 모델 프롬프트에는 “민감정보 접근 금지, 외부 업로드 금지” 같은 규칙을 명시
- Tool Gateway는 실제로 파일 경로·도메인·쿼리·출력 크기를 검사
- 위반 시 에이전트에게 “거부 사유”만 반환하고, 원문 데이터는 절대 전달하지 않음
운영 관점 체크리스트 (이대로만 해도 사고가 크게 줄어든다)
최소 권한
- 도구 allowlist는 “필요한 것만”
- 네트워크 기본 차단, 목적지 allowlist
- 파일은 워크스페이스만 read-write
격리
- 컨테이너
--read-only, capabilities drop,no-new-privileges - 가능하면 VM급 격리(Firecracker 등)
데이터 보호
- 비밀키는 에이전트에 주지 않고 프록시가 보관
- 도구 출력에서 토큰/키 패턴 마스킹
비용 보호
- 스텝 예산, 도구 호출 예산
- 타임아웃, 최대 응답 크기
- 429/5xx 재시도는 지수 백오프 + 상한 + jitter
감사 가능성
- 모든 도구 호출에
request_id부여 - 입력/출력/정책결정 로그 분리 저장
- “왜 거부됐는지”가 재현 가능해야 함
마무리: ‘에이전트’가 아니라 ‘권한 설계’를 배포하라
AutoGPT·BabyAGI를 운영 환경에 붙이는 순간, LLM은 단순 채팅이 아니라 권한을 가진 자동 실행기가 됩니다. 따라서 안정성은 모델 선택보다 “도구권한 경계”에서 결정됩니다.
- 에이전트는 도구를 직접 호출하지 못하게 하고
- Tool Gateway에서 정책을 강제하며
- 실행은 샌드박스에서 격리하고
- 비용과 재시도까지 시스템적으로 제한
이 4가지를 갖추면, 프롬프트 인젝션이나 예측 불가능한 계획 루프가 있어도 ‘탈주’는 구조적으로 어려워집니다. 결국 배포하는 것은 에이전트가 아니라, 권한이 잘린 안전한 자동화 시스템이어야 합니다.