research

ZeroClaw — Sandboxing 시스템

ZeroClaw — Sandboxing 시스템

Layer 3 학습. 실제 소스 코드 기반. 파일: src/security/traits.rs, detect.rs, firejail.rs, landlock.rs, docker.rs, bubblewrap.rs


핵심 개념

Sandboxing = shell 명령 실행 시 OS 레벨에서 프로세스를 격리하는 것.

SecurityPolicy가 "이 명령을 실행해도 되는가?"를 결정한다면, Sandbox는 "실행 허가된 명령이 실행될 때 피해 범위를 제한"한다.

두 레이어가 독립적으로 동작:

validate_command_execution()  ← SecurityPolicy (허용 여부)
    → sandbox.wrap_command()  ← Sandbox (격리된 환경에서 실행)
        → 실제 OS 실행

Sandbox trait

pub trait Sandbox: Send + Sync {
    fn wrap_command(&self, cmd: &mut Command) -> std::io::Result<()>;
    fn is_available(&self) -> bool;
    fn name(&self) -> &str;
    fn description(&self) -> &str;
}

wrap_command()가 핵심. Command를 받아서 in-place로 수정해 샌드박스 래퍼로 교체.

예: echo hellofirejail --private=home ... echo hello


백엔드 4종 비교

| 백엔드 | 방식 | 플랫폼 | 의존성 | 격리 강도 | |--------|------|--------|--------|----------| | Landlock | 커널 LSM (kernel 5.13+) | Linux only | 없음 (커널 기능) | 파일시스템 접근 제어 | | Firejail | SUID 래퍼 | Linux only | firejail 바이너리 | 홈/dev/사운드/비디오 격리 | | Bubblewrap | user namespace | Linux/macOS | bwrap 바이너리 | 네임스페이스 완전 격리 | | Docker | 컨테이너 | 크로스 플랫폼 | Docker 데몬 | 메모리/CPU/네트워크 제한 | | Noop | 없음 (폴백) | 모든 플랫폼 | 없음 | 없음 |


자동 감지 우선순위 (Auto 모드)

Linux:
  1. Landlock (feature = "sandbox-landlock", kernel 5.13+)
  2. Firejail (`firejail --version` 성공 여부)

macOS:
  1. Bubblewrap (feature = "sandbox-bubblewrap")

공통:
  3. Docker (`docker --version` 성공 여부)
  4. Noop (폴백)
// detect.rs
fn detect_best_sandbox() -> Arc<dyn Sandbox> {
    // Linux: Landlock 시도
    if let Ok(sandbox) = LandlockSandbox::probe() { return Arc::new(sandbox); }
    // Linux: Firejail 시도
    if let Ok(sandbox) = FirejailSandbox::probe() { return Arc::new(sandbox); }
    // macOS: Bubblewrap 시도
    if let Ok(sandbox) = BubblewrapSandbox::probe() { return Arc::new(sandbox); }
    // 공통: Docker 시도
    if let Ok(sandbox) = DockerSandbox::probe() { return Arc::new(sandbox); }
    // 폴백
    Arc::new(NoopSandbox)
}

각 백엔드 상세

Landlock (Linux 커널 LSM)

// wrap_command()가 실제로 하는 일:
// child process를 감싸는 게 아니라
// "현재 프로세스"에 Landlock 규칙셋을 적용
// → child는 부모의 제한을 상속

self.apply_restrictions()  // 파일 접근 규칙 적용

허용되는 경로:

  • workspace_dir → 읽기/쓰기/디렉토리 나열
  • /tmp → 읽기/쓰기
  • /usr, /bin → 읽기 전용 (명령 실행용)

특징: 의존성 없음. 순수 커널 기능. landlock Rust 크레이트로 구현.

Firejail (Linux user-space)

firejail --private=home   # 새 임시 홈 디렉토리
         --private-dev    # 최소 /dev (실제 장치 차단)
         --nosound        # 오디오 장치 차단
         --no3d           # 3D 가속 차단
         --novideo        # 비디오 장치 차단
         --nowheel        # 입력 장치 차단
         --notv           # TV 장치 차단
         --noprofile      # 프로파일 로딩 스킵
         --quiet          # 경고 억제
         <original command>

apt install firejail로 설치. SUID 바이너리.

Bubblewrap (user namespace)

bwrap --ro-bind /usr /usr    # /usr 읽기 전용 마운트
      --dev /dev             # 가상 /dev
      --proc /proc           # 가상 /proc
      --bind /tmp /tmp       # /tmp 바인드
      --unshare-all          # 모든 네임스페이스 분리 (네트워크 포함)
      --die-with-parent      # 부모 종료 시 자동 종료
      <original command>

--unshare-all이 핵심 — 네트워크 네임스페이스도 분리돼서 네트워크 완전 차단. --die-with-parent — 부모 프로세스 종료 시 자식도 같이 종료 (고아 프로세스 방지).

Docker

docker run --rm           # 종료 후 컨테이너 자동 삭제
           --memory 512m  # 메모리 512MB 제한
           --cpus 1.0     # CPU 1코어 제한
           --network none # 네트워크 완전 차단
           alpine:latest  # 기본 이미지
           <original command>

크로스 플랫폼이지만 Docker 데몬이 필요해서 무거움. 컨테이너 이미지 커스터마이즈 가능.


config.toml 설정

[security.sandbox]
enabled = true        # true / false / null(auto)
backend = "auto"      # auto / landlock / firejail / bubblewrap / docker / none

# firejail 추가 인수 (선택)
firejail_args = ["--net=none", "--caps.drop=all"]

백엔드 명시 + 설치 안 됨 → 경고 로그 + Noop 폴백:

WARN: Firejail requested but not available, falling back to application-layer

Sandbox가 "없을 때" (Noop)

pub struct NoopSandbox;

impl Sandbox for NoopSandbox {
    fn wrap_command(&self, _cmd: &mut Command) -> std::io::Result<()> {
        Ok(())  // 아무것도 하지 않음
    }
    fn is_available(&self) -> bool { true }
    fn name(&self) -> &str { "none" }
}

Noop이어도 SecurityPolicy의 허용/차단 로직(allowlist, risk gate, path check)은 여전히 동작. Sandbox는 허용된 명령의 실행 환경을 격리할 뿐, 허용 여부 자체는 SecurityPolicy가 담당.


격리 범위 비교

| 보호 대상 | Landlock | Firejail | Bubblewrap | Docker | |----------|----------|----------|------------|--------| | 파일시스템 | ✅ (규칙 기반) | ✅ (홈 격리) | ✅ (ro-bind) | ✅ (컨테이너) | | 네트워크 | ❌ | ❌ | ✅ (--unshare-all) | ✅ (--network none) | | 프로세스 네임스페이스 | ❌ | 부분 | ✅ | ✅ | | 메모리/CPU 제한 | ❌ | ❌ | ❌ | ✅ | | 루트 권한 필요 | ❌ | SUID | ❌ | 데몬 권한 |


내 환경 (로컬 Linux)에서 권장 설정

[security.sandbox]
enabled = true
backend = "landlock"   # 의존성 없음, 커널 5.13+

kernel 버전 확인: uname -r 5.13 미만이면 firejail 또는 none.

Landlock이 자동 감지되면 로그에:

INFO zeroclaw: Landlock sandbox enabled (Linux kernel 5.13+)

설계 원칙

Defense in depth (심층 방어):

Layer 1: allowed_commands allowlist          → 허용된 명령만
Layer 2: command_risk_level + approval gate  → 위험 명령 차단/승인
Layer 3: forbidden_path_argument()           → 위험 경로 인수 차단
Layer 4: Sandbox.wrap_command()              → OS 레벨 격리
Layer 5: is_resolved_path_allowed()          → 심볼릭 링크 탈출 방지

각 레이어가 독립적으로 작동. 하나가 뚫려도 다음 레이어가 방어.


관련