-
에이전트를 샌드박싱한다 — 그 한 문장이 숨기는 세 개의 경계IT 2026. 8. 7. 21:00
지난 글에서 로컬 MCP 서버에 샌드박스를 씌우는 건 공급망 문제의 답이지 프롬프트 인젝션의 답이 아니라고 썼다. 검증되지 않은 코드를 내 계정 권한으로 실행하는 문제 — 그건 샌드박스가 상당 부분 해결한다. 그래서 말한 대로 실제로 씌워보려고 어느 오픈소스 에이전트의 샌드박싱 문서를 펼쳤는데, 첫 페이지에서 멈칫했다.
첫 문단이 이랬다 — 게이트웨이 프로세스는 호스트에 그대로 남고, 샌드박스가 감싸는 건 툴 실행뿐이다. 그러면 내가 씌우려던 MCP 서버는 어디서 도는가? 그 페이지에는 답이 없었다. 다른 문서 두 곳을 찾아 맞춰본 뒤에야 알았다 — MCP도 담 밖이다. 나는 그때까지 "에이전트를 샌드박싱한다"를 하나의 동작으로 생각하고 있었다. 그런데 문서는 무엇을 경계 안에 넣는지가 이미 갈라져 있다고 말하고 있었다. 심지어 그 갈라진 조각들이 한 페이지에 모여 있지도 않았다. 이 글은 그 "무엇을"을 세 갈래로 풀어본다. 결론부터 말하면, 같은 "샌드박싱했다"는 문장이 최소 세 개의 다른 경계를 뜻하고, 어디에 긋느냐가 어떤 공급망 공격이 막히고 어떤 게 그대로 새는지를 결정한다.
먼저 용어 정리 — MCP 서버가 실제로 뭐가 되는가
MCP(Model Context Protocol, AI 모델과 외부 도구를 잇는 표준 규격 — 흔히 "AI의 USB-C 포트"에 비유된다)에서 로컬 서버는 대개 내 컴퓨터에서 도는 하나의 프로세스다. 에이전트가
npx some-mcp-server같은 명령으로 자식 프로세스를 띄우고, 표준입출력으로 대화한다. 즉 MCP 서버를 하나 붙인다는 건 남이 만든 코드를 내 권한으로 상주 실행한다는 뜻이다. 공급망 위험이 바로 여기서 생긴다.그러면 샌드박스를 "씌운다"고 할 때, 정확히 어느 프로세스를 가두는가? 후보는 셋이다 — 에이전트가 생성한 명령을 실제로 실행하는 툴 실행, 에이전트 본체인 게이트웨이, 그리고 방금 말한 MCP 서버 프로세스들. 이 셋 중 어디에 담을 긋느냐가 세 가지 배치를 만든다.
다이어그램 설명. "샌드박싱한다"는 한 마디가 갈라지는 세 갈래를 보여준다. "툴 실행만 가둔다"는 에이전트가 뱉은 셸 명령·파일 쓰기만 격리하고 MCP 서버와 본체는 그냥 호스트에 둔다. "에이전트 전체를 가둔다"는 본체와 MCP 서버를 통째로 하나의 컨테이너에 밀어넣는다. "MCP 서버마다 따로"는 서버 하나하나에 개별 담을 친다. 핵심은 이 셋이 전부 "샌드박싱"이라는 같은 단어를 쓰지만 경계 안에 들어가는 대상이 다르다는 것 — 그리고 그 차이가 공급망 방어의 성패를 가른다. 하나씩 실물로 보자.
① 툴 실행만 가둔다 — MCP 서버는 담 밖에 있다
내가 문서를 펼친 그 에이전트(OpenClaw, 커뮤니티 개인 AI 어시스턴트)가 이 배치다. 샌드박스는
exec·파일 읽기/쓰기·브라우저 같은 툴 실행에만 걸린다. 기본값은 꺼짐이라 설정에서 켜야 컨테이너가 생기고, 켰을 때의 기본 백엔드는 도커다. 놀랍게도 그 컨테이너의 네트워크 기본값은none— 아예 밖으로 못 나간다. 에이전트가 "이 셸 명령을 실행해"라고 결정하면 그 명령이 이 격리된 컨테이너 안에서 돌아, 호스트의~/.ssh나 임의 사이트에 손대지 못한다.여기까진 훌륭하다. 그런데 MCP는 통째로 이 담 밖에 있다. MCP 서버는 게이트웨이(에이전트 본체)가 띄우는 평범한 호스트 자식 프로세스이고, 게이트웨이의 권한을 그대로 물려받는다. 서버가 밖이면 그 서버가 제공하는 툴도 밖이다 — 모델이 "메일 보내"나 "파일 찾아"를 호출할 때 실제로 일하는 코드는 컨테이너가 아니라 호스트에서 돈다. 샌드박스를 켜든 끄든 이건 달라지지 않는다. MCP를 담 안에 넣는 선택지가 애초에 메뉴에 없다.
그래서 이 배치가 실제로 막는 건 "모델이 생성한 셸·파일 명령의 폭주"다. 이건 진짜 위협이고 잘 막는다. 하지만 오늘의 주제인 악성 MCP 서버는 이 담이 전혀 건드리지 못한다. 남이 만든 MCP 패키지가
~/.ssh/id_rsa를 읽어 밖으로 보내려 하면, 그 서버는 샌드박스 밖 호스트에서 풀 권한으로 돌고 있으므로 그냥 성공한다. 공급망 문제에 대해 이 배치는 거의 아무것도 하지 않는다.▲ 배치 ① — 툴 실행만 담 안
다이어그램 설명. 호스트 위에 게이트웨이와 MCP 서버가 나란히 얹혀 있고, 자물쇠가 걸린 건 오직 "툴 실행 샌드박스" 하나다. "MCP 서버 (담 밖, 풀 권한)" 노드가 자물쇠 바깥에 그대로 노출돼 있다는 점이 핵심이다 — 악성 MCP 패키지는 이 노출된 자리에서 호스트 파일을 읽고 밖으로 보낼 수 있다. 툴 실행 샌드박스의
network: none은 모델이 만든 명령은 가두지만, MCP 서버는 애초에 그 담 안에 있지도 않다.② 에이전트 전체를 가둔다 — 이번엔 MCP 서버도 담 안이다
NVIDIA가 2026년 공개한 오픈소스 런타임 OpenShell이 이 배치다. Claude Code·Codex·OpenClaw 같은 코딩 에이전트를 통째로 하나의 컨테이너에 넣는다. 본체가 그 안에서 돌고, 본체가 띄우는 MCP 서버들도 같은 컨테이너 안에서 태어난다. 신뢰 경계가 호스트 ↔ 에이전트 전체로 그어진다.
▲ 배치 ② — 에이전트 전체가 담 안
다이어그램 설명. 이번엔 게이트웨이와 MCP 서버들이 모두 하나의 자물쇠 안, 즉 단일 컨테이너 안에 들어가 있다. "MCP 서버 B (악성 가능)"가 호스트의
~/.ssh에 손대려 해도 컨테이너 밖이라 닿지 않는다 — 그래서 공급망 위협에는 배치 ①보다 훨씬 강하다. 대신 놓치기 쉬운 함정이 하나 있다. 마지막 노드 "허용목록 egress (공유)"가 보여주듯, 이 컨테이너의 나가는 네트워크 허용목록은 하나로 합쳐져 안의 모든 서버가 공유한다. 악성 서버 B가 옆에 있는 정상 서버 A에게 허용된 통로를 그대로 빌려 쓸 수 있다는 뜻이다.OpenShell이 이 컨테이너 안에서 실제로 조이는 방식은 촘촘하다. 파일시스템은 컨테이너를 만드는 순간 잠긴다(Landlock, 리눅스 커널이 프로세스별 파일 접근을 제한하는 기능). 네트워크는 기본 차단에 YAML로 적은 목적지만 열어준다. 위험한 시스템 호출은 seccomp(커널이 프로세스가 부를 수 있는 syscall을 화이트리스트로 거르는 기능)로 막는다. API 키는 실행 시점에 환경변수로만 주입돼 디스크에 아예 닿지 않는다. 구현은 단일 도커 컨테이너 안에 작은 쿠버네티스(K3s)를 통째로 넣는 방식인데, 그만큼 기동 지연과 메모리 오버헤드를 대가로 치른다.
정리하면 배치 ②는 공급망 문제의 진짜 답이다 — 악성 MCP 서버든 악성 에이전트든 호스트에 손을 못 대게 한다. 배치 ①이 못 하던 바로 그 일을 한다. 대신 담 안은 한 덩어리라는 비용을 치른다.
③ MCP 서버마다 따로 가둔다 — 가능하지만 아직 흔한 기본값은 아니다
세 번째 배치는 서버 하나하나에 개별 담을 치는 것이다. 산수 도구 서버는 네트워크를 완전히 막고, 메일 서버는 메일 API 하나만 열어주는 식으로 서버별 최소 권한을 준다. 도커의 MCP Gateway처럼 클라이언트와 서버 사이에 프록시를 두고 서버마다 정책을 다르게 거는 접근이 여기 가깝다.
이론상 가장 안전하다. 배치 ②가 못 막던 문제 하나를 실제로 막는다 — 악성 서버 프로세스가 스스로 옆 서버용으로 열린 통로를 타고 나가는 것. 통로가 서버별로 갈라져 있으면 산수 도구 서버는 애초에 나갈 구멍이 없다.
그런데 현실에서 이 배치는 아직 흔한 기본값이 아니다. 방금 말한 Docker MCP Gateway처럼 서버별 격리를 기본으로 주는 도구가 나오고 있지만, 우리가 실제로 MCP를 꽂는 자리 — 코딩 에이전트와 데스크톱 클라이언트 — 의 기본 경로는 여전히 배치 ①이다. 이유는 단순하다. 서버가 열 개면 정책도 열 벌을 관리해야 하고, 오버헤드도 열 배다. 완벽한 격리와 운영 현실 사이의 거리가 그대로 기본값에 반영돼 있다.
세 배치를 지난 글의 세 공격에 넣어보면
지난 글에서 로컬 MCP의 세 공격 — 도구 설명문에 숨은 지시를 넣는 tool poisoning, 신뢰받던 서버가 업데이트로 돌변하는 rug pull, 악성 서버가 다른 신뢰 서버를 오용하게 만드는 tool shadowing — 을 다뤘다. 이 셋을 세 배치에 통과시켜 보면 규칙이 하나로 모인다.
배치가 촘촘해질수록(①→②→③) 공급망 계열 공격은 점점 잘 막힌다. 배치 ①은 악성 MCP 서버를 아예 못 건드리고, ②는 호스트를 지켜내고, ③은 서버 사이까지 갈라놓는다. 여기까지는 "격리를 강화할수록 안전해진다"는 직관이 맞다. 그런데 tool shadowing 하나는 세 배치 어디에서도 그대로 통과한다. 그 공격의 유출은 신뢰받는 서버가 자기 정당한 권한으로 수행하기 때문에, 담을 어디에 긋든 그 담은 "정상 동작"으로 보이는 그 행위를 막을 이유가 없다. 경계를 옮기는 일은 프로세스가 무엇을 할 수 있는가를 바꾸지, 모델이 무엇을 하기로 결정하는가를 바꾸지 못한다.
그래서 배치 ②가 ①보다 항상 안전한 것도 아니다. 공급망에는 ②가 강하지만, 담 안이 한 덩어리라 내부에서 서버끼리 신뢰가 새는 문제는 오히려 ②에서 더 크다. "통째로 가두면 다 해결"이라는 생각이 어디서 깨지는지 — 그건 다음 글에서 tool shadowing만 따로 떼어 자세히 다룬다.
정리 — "샌드박싱했다"는 말을 들으면 경계부터 묻는다
세 배치를 한 줄로 줄이면 이렇다. 배치 ①(툴 실행만)은 모델이 만든 명령의 폭주를 막지만 악성 MCP 서버 앞에선 무력하다. 배치 ②(에이전트 전체)는 공급망 문제의 실질적 답이지만 담 안이 단일 신뢰존이다. 배치 ③(서버별)은 가장 안전하지만 운영 비용 때문에 아직 널리 기본이 되진 못했다. 셋 다 tool shadowing에는 닿지 못한다.
그러니 누군가 "에이전트를 샌드박싱했다"고 말하면, 이제 나는 안심하기 전에 한 가지를 먼저 묻는다 — 무엇을 경계 안에 넣었는가. 툴 실행만인지, 에이전트 전체인지, 서버별인지에 따라 그 문장이 방어하는 공격이 완전히 다르기 때문이다. 내 서버에 샌드박스를 씌우기로 했다면, 첫 질문은 "켤까 말까"가 아니라 "담을 어디에 그을까"다.
참고한 공개 자료:
- OpenClaw Gateway Sandboxing 문서 (tool-execution 샌드박스, Docker/SSH/OpenShell 백엔드, network:none 기본) — OpenClaw (https://github.com/openclaw/openclaw)
- NVIDIA OpenShell: Secure Containers for AI Coding Agents — NVIDIA (https://perspectives.nvidia.com/nvidia-openshell/get-started-ai-coding-agents-secure-containers/)
- Practical Security Guidance for Sandboxing Agentic Workflows and Managing Execution Risk — NVIDIA Technical Blog (https://developer.nvidia.com/blog/practical-security-guidance-for-sandboxing-agentic-workflows-and-managing-execution-risk/)
- How To Sandbox or Containerize Your MCP Servers — MCP Manager (https://mcpmanager.ai/blog/sandbox-mcp-servers/)
- Docker MCP Gateway: Secure Infrastructure for Agentic AI — Docker (https://www.docker.com/blog/docker-mcp-gateway-secure-infrastructure-for-agentic-ai/)
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
MCP를 전부 컨테이너에 가뒀다, 딱 하나만 빼고 — 샌드박스 담에 문을 내는 법과 그 대가 (0) 2026.08.07 샌드박스 정책에 반드시 들어가는 세 줄 — 그리고 각 줄이 막는 공격 (0) 2026.08.07 130개의 보안 경고를 가진 에이전트를 집에서 안전하게 돌리는 법 — OpenShell로 케이지에 넣기 (0) 2026.08.07 cherry-pick과 revert — 브랜치 전체가 아니라 커밋 하나만 다루고 싶을 때 (0) 2026.08.06 충돌은 왜 rebase에서 더 자주 만날까 — merge는 한 번, rebase는 커밋마다 (0) 2026.08.06 rebase가 위험하다는 말의 진짜 의미 — 커밋은 이동하지 않고 복제된다 (0) 2026.08.06 fast-forward의 정체 — 어떤 날은 merge commit이 생기고 어떤 날은 안 생기는 이유 (0) 2026.08.06 브랜치를 합치는 세 가지 방법 — merge, rebase, squash는 히스토리 모양이 다르다 (0) 2026.08.06 생성은 값싸졌지만 검증은 아니다 — AI 코딩의 새로운 병목 (0) 2026.08.05 스펙이 새로운 소스코드다? — 가장 뜨거운 주장의 팽팽한 뒷면 (0) 2026.08.05