AI 에이전트에게 셸 명령 실행 권한을 주는 것은 내 컴퓨터에 RCE를 허용하는 것입니다. 저는 Docker가 아닌 커널로 이를 해결했습니다.
요약
AI 에이전트에게 셸 실행 권한을 부여할 때 발생하는 RCE 보안 위험을 해결하기 위해, Docker 대신 커널 수준의 격리 기술을 사용하는 방법을 제안합니다. 저자는 콜드 스타트 문제를 해결하고 프로세스 단위의 세밀한 제어를 위해 macOS의 Seatbelt, Linux의 Landlock/seccomp 등을 통합한 Skarn 도구를 구축했습니다.
핵심 포인트
- AI 에이전트의 셸 실행 권한은 원격 코드 실행(RCE) 위험을 초래함
- Docker는 콜드 스타트 지연과 과도한 리소스 사용으로 에이전트 실행에 부적합함
- 커널 레벨의 격리(Seatbelt, Landlock, AppContainer)를 통해 5ms 미만의 빠른 실행 가능
- Skarn 도구를 통해 네트워크 및 파일 시스템 접근을 명령 단위로 제한 가능
몇 주 전, 저는 코딩 에이전트에게 셸 (shell) 명령을 실행할 수 있는 권한을 주었고, 그것이 cargo test를 실행하는 것을 지켜보며 뿌듯함을 느꼈습니다. 그러다 제가 실제로 무엇을 했는지 깨달았습니다. "모델이 셸 명령을 실행하게 하라"는 말은 "내가 완전히 제어할 수 없는 프로그램이 내 노트북에서 임의의 코드 (arbitrary code)를 실행하게 하라"는 말을 친근하게 표현한 것뿐입니다. 이것은 원격 코드 실행 (Remote Code Execution, RCE)의 교과서적인 정의입니다. 저는 스스로 RCE 머신을 구축하고 그 열쇠를 넘겨준 셈이었습니다.
그래서 저는 이를 가두어 둘 방법을 찾아 나섰습니다. 제가 시도했던 것들, 왜 Docker가 잘못된 도구였는지, 그리고 대신 무엇을 구축하게 되었는지에 대해 말씀드리겠습니다.
뻔한 정답, 그리고 그것이 틀린 이유
"컨테이너 (container)에 넣으세요"는 모두의 첫 번째 본능이며, 아주 말도 안 되는 생각은 아닙니다. 하지만 Docker는 이 특정 작업에 적합한 형태가 아닙니다:
- 콜드 스타트 (Cold start). 에이전트는 단 하나의 명령만 실행하는 것이 아니라, 수백 개의 짧은 명령을 실행합니다. 명령당 200ms 이상의 구동 시간이 걸린다면, 빠릿빠릿해야 할 세션이 슬라이드 쇼처럼 느려지게 됩니다.
- 데몬 (daemon)과 루트 (root) 권한이 필요하며, macOS에서는 전체 Linux VM이 필요합니다. 단순히
ls를 안전하게 실행하기 위해 관리해야 할 움직이는 부품이 너무 많습니다. - 입도 (granularity)가 맞지 않습니다. 컨테이너는 전체 환경을 격리합니다. 제가 실제로 원했던 것은 거의 비용 부담 없이, 명령당 단일 프로세스 (single process)를 제한하는 것이었습니다.
문제는, 모든 주요 OS가 이미 정확히 그러한 원시 기능 (primitive)을 탑재하고 있다는 점입니다. 우리가 그것을 거의 사용하지 않을 뿐입니다.
커널은 이미 이 일을 하고 있습니다
각 플랫폼은 데몬 없이도 커널 레벨에서 단일 프로세스를 제한할 수 있는 내장된 방법을 가지고 있습니다:
- macOS: Seatbelt. Chrome과 그 친구들이 사용하는 것과 동일한
sandbox_init메커니즘입니다. 프로세스가 무엇을 건드릴 수 있는지 설명하는 프로필을 전달하면, 커널이 이를 강제합니다. - Linux: Landlock + seccomp. Landlock (5.13 버전부터 메인라인에 포함된 LSM)은 파일 시스템 접근을 제한하며, seccomp-bpf는 프로세스가 수행할 수 있는 시스템 호출 (syscall)을 필터링합니다.
- Windows: AppContainer + Job Object. 기능 기반의 제한 (Capability-based confinement)과 리소스 제한을 제공합니다.
문제는 이 세 가지가 서로 완전히 다른 API이며 세 가지의 서로 다른 멘탈 모델 (mental models)을 가지고 있다는 점이고, 그중 두 개는 문서화가 거의 되어 있지 않다는 것입니다. 이를 하나의 인터페이스("이 명령을 이 디렉터리로 제한하고, 네트워크를 거부하라") 뒤로 숨기는 것이 작업의 대부분이었습니다. 그 대가로 얻은 이점은 모델에게 정중하게 요청하는 대신 커널 (kernel)에 의해 격리 (confinement)가 강제된다는 것이며, 구축할 컨테이너가 없기 때문에 콜드 스타트 (cold start)가 5ms 미만으로 유지된다는 점입니다.
제가 만든 도구 (Skarn)에서는 다음과 같이 작동합니다:
bash
skarn run --net deny -- cargo test
이 명령은 네트워크 유출 (network egress)이 거부된 상태로 프로젝트 디렉터리에 잠긴 채 명령을 실행합니다. 만약 모델이 당신의 비밀 정보를 어딘가로 curl 하거나 저장소 외부의 경로를 rm -rf 하려고 결정한다면, 시스템 콜 (syscall)은 실패합니다. 정책 프롬프트 (policy prompt) 때문이 아니라, 커널이 거부했기 때문입니다.
더 어려운 문제: 모델이 작성한 코드 실행하기
셸 명령을 샌드박싱 (sandboxing)하는 것은 쉬운 절반에 불과합니다. 저는 또한 에이전트가 짧은 스크립트를 작성하여 도구들을 오케스트레이션 (orchestrate)하기를 원했는데, 이는 거대한 도구 스키마 (tool schemas)를 컨텍스트 윈도우 (context window)에서 제외할 수 있게 해줍니다 (이는 별도의 포스팅에서 다룹니다). 하지만 모델이 생성한 코드를 실행하는 것은 더 멋진 모자를 쓴 동일한 RCE 문제일 뿐입니다.
JavaScript 격리 환경 (isolate) 하나만으로는 보안 경계 (security boundary)가 되지 않습니다. 사람들은 이를 탈출합니다. 그래서 저는 그것이 보안 경계가 될 것이라고 신뢰하지 않았습니다. 스크립트는 QuickJS 격리 환경에서 실행되며, 해당 격리 환경은 모델의 코드를 로드하기 전에 스스로를 샌드박싱 (deny network, no workspace writes)하는 워커 프로세스 (worker process) 내부에서 실행됩니다.
이를 통해 두 개의 독립적인 벽을 구축했습니다:
- 격리 환경 (The isolate). 정적 검증 (Static validation)을 통해
eval,Function,require,import,process를 거부하며, 실행은 메모리, 스택, 실제 시간 (wall-clock), 그리고 출력 크기 제한에 의해 경계가 설정됩니다. - 그 아래의 커널 샌드박스 (The kernel sandbox underneath it). 격리 환경을 완전히 탈출하더라도, 여전히 네트워크에 접속하거나 워크스페이스 외부로 파일을 쓸 수 없는 프로세스에 도달하게 됩니다.
당신은 이 두 가지를 모두 통과해야 하며, 외부 벽은 운영체제 (OS)에 의해 강제됩니다. 내부 계층은 사용 편의성 (ergonomics)을 위한 것이고, 외부 계층은 실제로 당신을 막기 위한 것입니다.
위협 모델 (threat model)에 대한 솔직한 고백
성공 사례만 나열하는 보안 포스트는 마케팅에 불과합니다. 따라서 솔직히 말씀드리자면: 이 시스템은 신뢰할 수 없는 모델 생성 코드 (model-generated code)를 의도적으로 실행하며, 누군가 이 시스템을 깨뜨리려고 시도하는 것이야말로 가장 유용한 작업입니다. 직접 작성한 해당 커널 API들에 대한 unsafe FFI 부분은 제가 가장 확신이 없는 부분인데, 그 이유는 해당 인터페이스들이 문서화가 매우 부족하기 때문입니다. 이 시스템이 방어하지 못하는 부분들도 존재하며, 그렇기에 리포지토리에는 이를 명확히 밝히는 SECURITY.md 파일이 포함되어 있습니다. 만약 취약점을 발견하신다면, 이 시스템이 멋지다는 말보다는 그 취약점에 대해 듣고 싶습니다.
나머지 절반에 대하여 (간략히)
동일한 게이트웨이는 노이즈가 많은 셸 출력 (shell output)을 압축하여 에이전트의 토큰 사용량을 줄여주기도 하며 (토큰을 70-90% 절감하면서도 에러와 경고는 항상 유지), 앞서 언급한 스키마 회피 (schema-avoidance) 트릭을 통해서도 비용을 절감합니다. 이는 파일 시스템을 보호하기보다는 비용을 절감하는 부분이며, 이는 별개의 이야기입니다.
코드를 읽어보거나, 직접 테스트해 보거나, 샌드박스 (sandbox)를 공격해 보고 싶다면, 여기 하나의 Rust 바이너리가 있습니다: https://github.com/Rani367/Skarn
현재 초기 단계이며, MIT 또는 Apache-2.0 라이선스를 따릅니다. 샌드박스 크레이트 (sandbox crate)에 대한 리뷰를 보내주신다면 무엇보다 환영하겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기