
요즘 왜 다시 샌드박싱 이야기가 나올까요
혹시 브라우저에서 수상한 사이트에 들어갔는데도 내 컴퓨터 파일이 멀쩡한 이유, 궁금해 본 적 있으세요? 그게 바로 샌드박싱(sandboxing) 덕분이거든요. Lua 기반 실행 환경인 Emilua를 만드는 쪽 블로그에 "소프트웨어 샌드박싱의 기본"이라는 글이 올라왔는데요, 요즘 AI 에이전트가 코드를 직접 생성하고 실행하는 시대가 되면서 "이 코드가 뭘 할지 모르는데 어떻게 안전하게 돌리지?"라는 고민이 다시 뜨거워졌어요. 그래서 오늘은 이 글을 바탕으로 샌드박싱이 뭔지, 어떻게 동작하는지, 운영체제마다 뭐가 다른지 차근차근 풀어볼게요.
샌드박싱이 뭐냐면
이게 뭐냐면, 프로그램을 "모래놀이터" 안에 가둬서 그 밖으로는 손을 못 뻗게 만드는 기술이에요. 아이가 모래놀이터 안에서는 뭘 하든 괜찮지만, 밖으로 나가서 뭔가 망가뜨리는 건 막는 거죠. 프로그램도 마찬가지로 "너는 이 파일만 읽을 수 있고, 네트워크는 못 쓰고, 다른 프로세스는 건드릴 수 없어"라고 미리 선을 그어두는 거예요.
여기서 중요한 포인트가 하나 있는데요. 샌드박싱은 "악성 코드를 돌리기 위한" 기술이라기보다 "내 코드에 버그가 있어도 피해를 줄이기 위한" 기술에 더 가까워요. 아무리 잘 짠 프로그램도 취약점은 있기 마련이거든요. 이미지 파서 하나가 뚫려서 시스템 전체가 넘어가는 일을 막으려면, 그 파서를 애초에 아무 권한도 없는 상자 안에서 돌리면 되는 거죠. 이걸 최소 권한 원칙(principle of least privilege)이라고 불러요. "필요한 것만 딱 주고 나머지는 다 뺏는다"는 뜻이에요.
기본 구조: 브로커와 타깃
실제로 샌드박스를 구현할 때 가장 흔한 구조는 프로세스를 두 종류로 나누는 거예요. 하나는 권한을 가진 브로커(broker), 다른 하나는 권한이 거의 없는 타깃(target)이에요. 타깃이 "이 파일 좀 열어줘"라고 브로커한테 부탁하면, 브로커가 정책을 확인하고 괜찮으면 직접 열어서 파일 디스크립터만 넘겨주는 식이죠. 크롬 브라우저가 딱 이렇게 동작해요. 웹페이지를 렌더링하는 프로세스는 파일 시스템에 직접 접근을 못 하고, 필요한 건 전부 브라우저 메인 프로세스한테 부탁하거든요.
이게 왜 중요하냐면, 타깃 프로세스가 뚫려도 공격자가 손에 쥔 건 "브로커한테 부탁할 수 있는 권한"뿐이에요. 브로커가 거절하면 거기서 끝이죠. 취약점이 있어도 피해 범위가 상자 하나로 제한되는 거예요.
운영체제마다 도구가 달라요
문제는 운영체제마다 샌드박스를 만드는 도구가 완전히 다르다는 거예요.
리눅스는 솔직히 좀 복잡해요. 여러 부품을 조합해야 하거든요. 먼저 네임스페이스(namespace)로 프로세스가 보는 세상을 분리해요. 마운트 네임스페이스로 텅 빈 루트 파일시스템을 보여주고, 네트워크 네임스페이스로 네트워크를 끊고, PID 네임스페이스로 다른 프로세스를 못 보게 만드는 식이죠. 그 다음 seccomp-BPF로 "이 시스템 콜만 허용"하고 나머지는 막아요. 시스템 콜이 뭐냐면, 프로그램이 커널한테 "파일 열어줘", "네트워크 연결해줘" 하고 부탁하는 창구인데, 이 창구 자체에 필터를 거는 거예요. 최근엔 Landlock이라는 기능도 생겨서 루트 권한 없이도 파일시스템 접근 범위를 제한할 수 있게 됐고요. 도커 같은 컨테이너도 사실 이 부품들을 조립한 결과물이에요. 다만 컨테이너는 원래 "배포를 편하게 하려고" 만든 거라, 기본 설정이 보안 목적의 샌드박스만큼 빡빡하지는 않아요.
FreeBSD는 Capsicum이라는 게 있어서 훨씬 깔끔해요. 프로그램이 cap_enter()를 딱 한 번 부르면 그 순간부터 "이미 열어둔 파일 디스크립터" 말고는 아무것도 새로 열 수 없게 돼요. 새 파일도, 새 소켓도 안 돼요. 이걸 케이퍼빌리티 모드(capability mode)라고 하는데, 보안 쪽에서 이 방식을 선호하는 이유가 있어요. "다 되는 상태에서 하나씩 빼는" 게 아니라 "아무것도 안 되는 상태에서 필요한 것만 미리 챙겨 들어가는" 방식이라, 뭔가 빠뜨려서 구멍이 생길 여지가 적거든요.
OpenBSD는 pledge()와 unveil()이라는 짝이 있어요. pledge는 "나는 앞으로 표준 입출력이랑 파일 읽기만 쓸게"라고 약속하는 거고, unveil은 "이 경로만 보이게 해줘"라고 정하는 거예요. 몇 줄이면 끝나서 개발자들이 실제로 쓰게 만드는 데 성공한 사례로 자주 언급돼요. macOS에는 Seatbelt라는 프로파일 기반 시스템이 있고, 윈도우는 잡 오브젝트(job object)나 AppContainer 같은 걸 써요.
그래서 Emilua는 어떻게 했나
Emilua는 액터 모델(actor model)을 쓰는 Lua 런타임인데요. 액터가 뭐냐면, 각각 독립된 상태를 가지고 서로 메시지만 주고받으면서 일하는 작은 일꾼이라고 생각하면 돼요. Emilua에서는 이 액터를 새로 띄울 때 "샌드박스 안에서 돌려"라고 옵션을 줄 수 있어요. 그러면 리눅스에서는 네임스페이스와 seccomp을, FreeBSD에서는 Capsicum을 런타임이 알아서 세팅해 주고, 부모 액터가 넘겨준 파일 디스크립터 말고는 아무것도 못 쓰는 상태로 시작하는 거죠. 개발자는 "이 액터한테는 이 소켓만 줄게" 하고 명시적으로 넘겨주기만 하면 돼요. 운영체제별 차이를 런타임이 감춰준다는 게 핵심 포인트예요.
업계 맥락에서 보면
지금 샌드박싱이 다시 주목받는 이유는 명확해요. LLM 에이전트가 만든 코드를 실행하는 서비스가 쏟아지고 있거든요. 그래서 Firecracker 같은 마이크로 VM, gVisor 같은 유저스페이스 커널, WebAssembly 런타임 같은 선택지가 서로 경쟁 중이에요. 마이크로 VM은 격리가 가장 강하지만 상대적으로 무겁고, 프로세스 기반 샌드박스는 가볍지만 커널 취약점에는 노출될 수 있고, Wasm은 실행 환경 자체를 바꿔야 하는 부담이 있죠. 이 글이 다루는 건 그중 가장 기본인 "OS 프로세스 수준 샌드박스"인데, 다른 방식들이 뭘 개선하려는 건지 이해하려면 이 기본기가 꼭 필요해요.
한국 개발자에게는
당장 실무에서 뭘 해볼 수 있을까요. 리눅스 서버에서 외부 입력을 파싱하는 프로세스가 있다면 bubblewrap(bwrap)이나 systemd 서비스의 샌드박스 옵션(ProtectSystem, NoNewPrivileges, PrivateNetwork 등)부터 켜보세요. 코드를 거의 안 바꾸고 방어선이 하나 생겨요. 도커를 쓰고 있다면 "컨테이너니까 안전하다"는 생각은 버리는 게 좋아요. 사용자 코드를 돌린다면 seccomp 프로파일, 읽기 전용 파일시스템, 네트워크 차단은 기본이에요. 그리고 AI 코딩 에이전트를 사내에 도입하려는 팀이라면, 에이전트가 실행하는 명령을 어디서 어떤 권한으로 돌릴지 설계하는 게 첫 번째 과제가 될 거예요. 이 글에 나오는 브로커-타깃 구조와 "처음부터 아무것도 못 하게 하고 필요한 것만 준다"는 원칙은 그때 그대로 써먹을 수 있어요.
마무리
한 줄로 정리하면, 샌드박싱은 "코드를 믿지 말고 권한을 제한하라"는 오래된 원칙을 OS 기능으로 실현하는 기술이고, 리눅스는 조립식, FreeBSD와 OpenBSD는 완제품에 가까워요.
여러분 팀은 신뢰할 수 없는 코드를 어디서 어떻게 돌리고 계세요? 컨테이너로 충분하다고 보시나요, 아니면 마이크로 VM까지 가야 한다고 생각하세요?
🔗 출처: Hacker News