BoxAgnts 툴 시스템 (2) — Wasmtime 샌드박싱 (Sandboxing)의 보안 모델
요약
BoxAgnts가 AI 에이전트의 툴 실행 보안을 위해 Wasmtime 기반의 WebAssembly 샌드박싱을 채택한 이유를 설명합니다. 기존 Docker의 시스템 콜 블랙리스트 방식과 커널 공유 문제를 지적하며, 기능 기반 주입(capability-based injection) 모델의 우수성을 다룹니다.
핵심 포인트
- Docker의 seccomp 방식은 예측 불가능한 AI 툴 호출에 취약함
- WASM은 호스트 커널과 격리된 가상 명령어 세트 레이어에서 실행됨
- WASI를 통한 기능 기반 주입으로 권한 없는 인터페이스를 원천 차단
- 파일 시스템 접근 시 경로 기반이 아닌 핸들 기반 격리 방식 사용
BoxAgnts가 WebAssembly 샌드박싱 (Sandboxing)을 선택한 핵심 근거는 "권한 축소 (permission reduction)"가 아닌 "기능 기반 주입 (capability-based injection)"입니다.
Wasmtime 샌드박스는 정확히 무엇을 격리할까요? 각 방어 계층의 경계는 어디일까요? 그리고 왜 일반적인 공격 벡터 (attack vectors)가 이 모델 앞에서는 무력할까요?
기존 샌드박스들이 누더기인 이유
Docker를 예로 들어보겠습니다. Docker의 보안 모델은 Linux 네임스페이스 (namespaces: UTS, PID, mount, network, IPC, user, cgroup)와 seccomp 프로필의 결합에 의존합니다. 이 조합은 애플리케이션 레벨에서는 상당히 잘 작동하지만, AI 에이전트 (AI Agent) 툴 시나리오에서는 몇 가지 문제가 발생합니다.
첫 번째 문제는 시스템 콜 (syscall) 블랙리스트의 내재적인 결함입니다. seccomp의 기본 동작은 "모든 시스템 콜을 허용하되, 지정된 목록만 차단한다"입니다. Docker는 기본적으로 약 44개의 시스템 콜(reboot, kexec_load, add_key 등)을 비활성화합니다. 만약 새로 발견된 위험한 호출이 목록에 없다면, 보호는 존재하지 않는 것이나 다름없습니다. 더 결정적으로, AI 모델이 주도하는 툴 호출 동작은 예측 불가능합니다. 인간 개발자는 다른 프로세스에 ptrace를 호출하는 코드를 작성하지 않겠지만, AI가 즉석에서 생성한 bash 명령은 실수로 차단되지 않은 시스템 콜을 트리거할 수 있습니다.
두 번째 문제는 공유된 커널 공격 표면 (attack surface)입니다. 네임스페이스는 뷰 격리 (view isolation, 컨테이너 내부의 PID 1은 호스트의 PID 1이 아님)를 제공하지만, 모든 컨테이너는 동일한 커널 인스턴스를 공유합니다. 만약 WASM 툴이 어떤 경로를 통해 커널 취약점(예: eBPF 관련 CVE)을 트리거하면, 탈출(escape) 위험이 호스트 레벨로 전파됩니다.
WASM 샌드박스는 구조적으로 여기서 차별화됩니다. WASM 프로그램은 호스트 CPU에서 직접 실행되지 않습니다. 대신 Wasmtime의 인터프리터/JIT에 의해 생성된 가상 명령어 세트 (virtual instruction set) 레이어에서 실행됩니다. 이 프로그램은 x86의 syscall 명령어가 무엇인지 알지 못하며, 호스트의 메모리 페이지에 접근할 수 없습니다. 이 프로그램의 "운영 체제"는 호스트 프로그램에 의해 명시적으로 주입된 함수 테이블인 WASI 인터페이스입니다.
// wasm-sandbox/src/run.rs - WASI capability injection (WASI 기능 주입)
run_common.common.wasi.cli = Some(true); // 명령줄 인자 (command-line arguments) 허용
run_common.common.wasi.http = Some(true); // HTTP 아웃바운드 (HTTP outbound) 허용
...
모든 Some(true)는 명시적인 권한 부여 결정입니다. 권한이 없는 WASI 인터페이스는 게스트 (Guest)에게 완전히 보이지 않습니다. 이는 단순히 "호출할 수 없는" 것이 아니라, "호출 대상이 존재하지 않는" 상태를 의미합니다. 이것이 바로 기능 기반 주입 모델 (capability-based injection model)의 핵심입니다.
파일 시스템 격리: Preopen 디렉토리 핸들 (Preopen Directory Handles)
전통적인 경로 기반 화이트리스트 (path-based whitelist) 방식은 심볼릭 링크 공격 (symbolic link attacks)과 TOCTOU (time-of-check-time-of-use, 검사 시점과 사용 시점의 차이) 문제에 직면합니다. WASM의 파일 시스템 격리는 다른 경로를 택합니다: 바로 preopen 디렉토리 핸들 (preopen directory handles) 입니다.
// wasm-sandbox/src/run.rs
let mut dirs: Vec<(String, String)> = Vec::new();
if let Some(dir) = option.work_dir {
...
이 원리는 다음과 같습니다: 컴포넌트 초기화 과정에서, Wasmtime은 preopen_dir 인터페이스를 통해 호스트 디렉토리를 WASI로 전달합니다. 이때 전달되는 것은 경로 문자열 (path string)이 아니라 파일 디스크립터 (file descriptor, fd)입니다. 게스트의 /는 이 fd의 가상 루트가 됩니다. 게스트가 내부적으로 cd, open, readdir을 어떻게 수행하든 관계없이, 게스트는 항상 Wasmtime이 제공한 fd를 사용하며, 이 fd의 가시성 범위는 생성 시점에 openat + O_NOFOLLOW에 의해 고정됩니다.
이는 게스트 내부에서 심볼릭 링크 공격이 무력함을 의미합니다. WASM 게스트는 심볼릭 링크의 대상이 포함된 디렉토리 fd를 아예 전달받지 못하며, 커널의 경로 해석 (path resolution)은 호스트 측에서 차단되기 때문입니다.
TOCTOU 문제 역시 유사하게 처리됩니다. Preopen은 WASM 컴포넌트 초기화 시점에 발생합니다. 일단 fd가 생성되면, 이후의 디렉토리 권한 변경은 이미 열려 있는 fd에 영향을 미치지 않습니다. 공격자는 디렉토리 내용을 교체함으로써 런타임 (runtime) 중에 게스트의 가시적 범위를 확장할 수 없습니다.
네트워크 ACL: 이중 채널 검증 (Dual-Channel Validation)
BoxAgnts는 Spin Framework에서 네트워크 제어 설계를 채택하여, 화이트리스트 (whitelist) + 블랙리스트 (blacklist) 이중 채널 검증 시스템을 구현했습니다.
화이트리스트 (OutboundAllowedHosts)는 WASM 툴이 연결할 수 있는 도메인을 정의합니다:
// 형식 예시
"https://api.github.com" // 정확한 일치
"https://*.example.com" // 서브도메인 와일드카드
...
블랙리스트 (BlockedNetworks)는 특정 IP 범위 및 내부 네트워크 접근을 차단합니다:
// 1.1.1.1/32 차단 (단일 IP)
// "private" 키워드는 모든 RFC 1918 주소 + 루프백(loopback)을 차단함
WASM 프로그램이 네트워크 인터페이스를 통해 연결을 시작할 때마다, 2단계 검증이 트리거됩니다:
wasmtime_wasi::socket_addr_check(addr, addr_use, hosts, networks)
│
├── 1단계: BlockedNetworks::is_blocked(ip)
...
여기에는 주목할 만한 구현 세부 사항이 있습니다. IPv6 프로토콜은 IPv4-mapped 주소 형식 (::ffff:10.0.0.1)을 정의합니다. 만약 블랙리스트가 IPv4 형식만 확인한다면, 공격자는 http://[::ffff:10.0.0.1]을 사용하여 10.0.0.0/8 블랙리스트 규칙을 우회할 수 있습니다. BoxAgnts의 BlockedNetworks::is_blocked()는 이 케이스를 명시적으로 처리합니다:
// blocked_networks.rs
if let IpAddr::V6(ipv6) = ip_addr {
if let Some(ipv4_compat) = ipv6.to_ipv4() {
...
마찬가지로, SocketAddrUse::TcpBind 및 UdpBind는 직접 거부됩니다. 즉, WASM 툴은 포트에서 리스닝(listen)하거나 서버로 동작할 수 없습니다.
명령어 수준의 리소스 제어: wasm_fuel
CPU 시간 제한은 일반적으로 타임아웃 (timeout)을 사용하여 구현됩니다. 하지만 타임아웃에는 입도 (granularity) 문제가 있습니다. 만약 WASM 프로그램이 1초 동안 10억 개의 명령어를 실행하여 CPU 코어를 점유한 뒤, 타임아웃에 의해 종료되기 직전에 차단된다면, 그 1초 동안의 CPU 소비는 이미 기정사실 (fait accompli)이 되어 버립니다. 밀집된 다중 툴 동시성 (concurrency) 시나리오에서 이는 호스트의 응답성에 영향을 미치기에 충분합니다.
Wasmtime은 더 미세한 입도의 솔루션을 제공합니다: 연료 계측 (Fuel Metering).
// run.rs
pub wasm_fuel: Option<u32>, // 초기 연료 할당량
각 WASM 명령어는 실행될 때 1 단위의 연료를 소비합니다 (nop, drop, block, loop 및 기타 제어 흐름 (control flow) 명령어는 0을 소비함). 연료가 소진되면 Wasmtime은 trap을 생성하며, 호스트(host)가 이를 포착하여 컴포넌트를 종료하고 모든 리소스를 회수합니다.
이 메커니즘은 내부적으로 Wasmtime의 Store::set_fuel() 및 Store::consume_fuel() API에 의존합니다. Wasmtime은 명령어 단위가 아닌 각 기본 블록 (basic block)의 진입점에서 남은 연료를 확인합니다. 이는 성능을 위한 타협점입니다 (명령어 단위 확인은 비용이 너무 많이 듭니다). 하지만 상당한 양의 코드를 가진 WASM 프로그램의 경우, 각 기본 블록은 보통 기껏해야 수십 개의 명령어만을 포함하므로 정밀도 손실은 허용 가능한 수준입니다.
wasm_timeout, wasm_max_memory_size, 그리고 wasm_max_wasm_stack과 결합하여, BoxAgnts는 WASM 툴을 위한 완전한 2차원(시간 + 공간) 리소스 제약 매트릭스를 형성합니다:
| 제약 차원 | 파라미터 | 위반 시 동작 |
|---|---|---|
| CPU 시간 | wasm_timeout | 타임아웃 → 컴포넌트 종료 |
| ... |
이러한 제약 사항은 Wasmtime 엔진 (Engine) 레벨에서 적용됩니다. WASM 프로그램은 어떤 코드 경로를 통해서도 이를 우회할 수 없습니다. 이는 샌드박스가
// 의사코드(Pseudocode); 실제 구현은 gateway/api/tool.rs에 위치함
match (tool.permission_level(), ctx.permission_mode) {
(_, PermissionMode::Full) => Ok(()), // 전체 권한 모드(full permission mode), 제한 없음
...
사용자는 대시보드(Dashboard)를 통해 서로 다른 에이전트(Agents)에 대해 다양한 PermissionMode 설정을 구성할 수 있습니다. 예를 들어, ReadOnly로 설정된 코드 리뷰 전용(code-review-only) 에이전트를 생성하면, 해당 에이전트가 Bash 툴을 호출하더라도 시스템은 실행 전에 이를 거부합니다.
이 모델이 AI 에이전트에게 AppArmor/seccomp보다 뛰어난 이유
전체적인 구조를 검토해 보겠습니다. 전통적인 방식의 보안 시퀀스(Security sequence)는 다음과 같습니다:
툴 코드 로드(Tool code loaded) → 실행(Execute) → 시스템 콜(Syscall) → seccomp 체크 → 허용/거부(Allow/Deny)
↑ 이 시점에 도달하기 전에 이미 위험이 발생함
BoxAgnts의 보안 시퀀스는 다음과 같습니다:
WASM 컴포넌트 로드(WASM component loaded) → 권한 주입(Capability injection) (파일 fd, 네트워크 화이트리스트, 리소스 제한) → 실행(Execute)
↑ 모든 권한이 실행 전에 결정되며, 확장 불가능함
차이점은 "보안 경계 설정의 타이밍(timing of security boundary establishment)"에 있습니다. 전통적인 방식에서는 실행과 권한 확인 사이에 자연스러운 시간 간격이 존재하며, 이 간격이 공격 표면(attack surface)이 됩니다. WASM 방식에서는 코드가 제어권을 얻기 전에 실행 환경이 완전히 제한됩니다. 게스트(Guest)는 자신의 권한 경계를 확장할 수 있는 수단이 없습니다.
이것이 WASM 샌드박스(sandbox)가 모든 보안 문제로부터 본질적으로 면역되어 있다는 뜻은 아닙니다. Wasmtime 자체에 보안 취약점이 있을 수 있으며 (RustSec advisory database의 wasmtime 관련 항목 참조), 컴파일러 인프라의 오류로 인해 게스트 탈출(Guest escape)이 발생할 수도 있습니다. 하지만 파일 작업, 네트워크 액세스, 데이터베이스 쿼리 등 AI 에이전트의 툴 사용 사례에 있어서, WASM 샌드박스의 보안 경계는 필요한 수준을 훨씬 상회합니다.
이는 또한 BoxAgnts의 내장 bash 툴이 왜 추가적인 WASM 샌드박스 (sandbox) 계층을 필요로 하지 않는지를 설명해 줍니다. bash 툴 자체는 WASM으로 컴파일되었습니다. 즉, 호스트 셸 (host shell)을 호출하는 것이 아니라, wasm32-wasi로 컴파일되어 샌드박스 내부에서 실행되는 완전한 셸 구현체입니다. 위에서 설명한 모든 격리 메커니즘이 이 툴에도 동일하게 적용됩니다.
요약 (Summary)
Wasmtime 샌드박싱은 기존 방식들이 동시에 충족할 수 없었던 세 가지 역량을 BoxAgnts에 제공합니다:
-
명령어 수준의 격리 (Instruction-level isolation). WASM 프로그램은 가상 명령어 세트 위에서 실행되며, 호스트의 CPU, 메모리 또는 시스템 호출 (syscall) 인터페이스에 직접 접근할 수 없습니다. 보안 경계는 단순히 "필터링된 시스템 호출"이 아니라, "시스템 호출을 호출할 수 있는 경로 자체가 존재하지 않음"입니다.
-
역량 기반 리소스 제어 (Capability-based resource control). 파일 시스템 (preopen fd), 네트워크 (화이트리스트 + 블랙리스트 이중 채널), CPU (wasm_fuel + timeout), 메모리 (max_memory_size + max_wasm_stack) 등 모든 리소스는 컴포넌트 실행 전에 정밀하게 제한되며, 런타임 중에 확장될 수 없습니다.
-
마이크로초 단위의 샌드박스 시작 (Microsecond-level sandbox startup). Docker의 초 단위 콜드 스타트 (cold start) 및 수백 밀리초 단위의 웜 스타트 (warm start)와 달리, WASM 컴포넌트 로딩 및 초기화 오버헤드는 마이크로초(microsecond) 범위 내에 있습니다. 이러한 속도 차이는 에이전트 대화 중 발생하는 고빈도 툴 호출에서 매우 중요합니다. AI가 매개변수 탐색을 위해 루프 내에서 반복적으로 툴을 호출할 때, 샌드박스 시작 지연 시간은 사용자 경험을 직접적으로 결정하기 때문입니다.
IPv4-mapped IPv6 우회 방지 처리와 4단계 권한 수준 (PermissionLevel) 분류는 보안 경계를 더욱 강화합니다. 전자는 IP 화이트리스트/블랙리스트의 취약점을 차단하며, 후자는 사용자가 신뢰 수준에 따라 서로 다른 에이전트에게 서로 다른 툴 세트를 권한 부여할 수 있게 합니다.
참고 문헌 (References)
참고 문헌 (References)
- BoxAgnts 소스 코드: https://github.com/guyoung/boxagnts
- Wasmtime 문서: https://docs.wasmtime.dev/
- WASI Preview2 사양: https://github.com/WebAssembly/wasi-cli
- Spin Framework 네트워크 ACL: https://github.com/fermyon/spin
- Claude 코드 샌드박스 설계: https://claude.com/blog/beyond-permission-prompts-making-claude-code-more-secure-and-autonomous
- Docker seccomp 보안 설정: https://docs.docker.com/engine/security/seccomp/
- RustSec 자문 데이터베이스: https://rustsec.org/
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기