Hub Python Library documentation

내부의 샌드박스

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v2.4.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

내부의 샌드박스

이 가이드는 Sandboxes가 내부적으로 어떻게 작동하는지, 왜 이러한 방식으로 구축되었는지, 그리고 어떤 제한 사항이 있는지를 설명합니다. 샌드박스를 단순히 사용하려는 경우라면 Sandboxes guide만 참고하시면 충분합니다. 메커니즘을 이해하거나 신뢰 모델을 평가하거나 문제를 디버깅하려는 경우라면 계속 읽어보세요.

Sandbox API는 실험적입니다. API 및 동작이 사전 통보 없이 변경될 수 있습니다.

“샌드박스 서비스”는 없습니다

가장 먼저 이해해야 할 점은 전용 샌드박스 백엔드가 없다는 것입니다. 샌드박스는 HTTP를 지원하는 단일 소형 정적 바이너리 sbx-server를 실행하는 HF Job(VM)일 뿐입니다. 클라이언트는 해당 서버와 Jobs 프록시(Job가 노출하는 *.hf.jobs URL)를 통해 통신합니다. 인증, 서비스 발견, 여러 샌드박스를 하나의 Job에 패키징하는 것 등 나머지 모든 기능은 기존 Jobs 프리미티브인 라벨, 환경 변수, 시크릿으로 구성됩니다.

이러한 “새로운 인프라 없음” 설계 덕분에 샌드박스는 어떤 Docker 이미지에서도 작동하며, Jobs의 과금, 하드웨어 플레버, 네임스페이스 권한을 자동으로 상속받습니다.

서버 부트스트랩

작업 시작 시, Job의 command는 sbx-server 바이너리를 가져와 실행 권한을 부여하고 exec로 실행하는 작은 /bin/sh -c 스크립트입니다. 이 바이너리는 런타임 의존성이 전혀 없는 약 640KB의 정적 musl 빌드이므로, 어떤 x86_64 Linux 이미지에서도 실행됩니다.

다음과 같은 몇 가지 결정 사항이 주목할 가치가 있습니다:

  • 마운트 폴백을 사용하여 다운로드합니다. 빠른 경로는 wget 또는 curl(모든 일반적인 base image에 하나 이상 포함됨)를 사용하여 HF CDN에서 바이너리를 다운로드하며, 빠르고 무료입니다. 안전장치로, 서버의 Hub repo는 볼륨으로 모든 job에 마운트됩니다. 이미지에 wget도 curl도 없으면, 스크립트는 해당 마운트에서 바이너리를 복사합니다. 마운트는 투명합니다 — 실제로 읽지 않는 한 비용이 들지 않습니다 — 하지만 읽으면 콜드 스타트에 약 2~3초가 추가되므로 기본값이 아닙니다. 이미지에 대한 유일한 필수 요건은 /bin/sh입니다.
  • 프레임워크 없이 직접 구현한 HTTP/1.1 서버. 라이브 출력 스트리밍은 각 청크가 생성될 때마다 즉시 플러시해야 합니다. 일반적인 최소 Rust HTTP 서버(예: tiny_http)는 응답이 완료될 때까지 청크된 응답을 버퍼링하므로 스트리밍이 깨집니다. 따라서 이 서버는 HTTP/1.1을 직접 구현합니다: exec용 NDJSON 이벤트 스트림, 파일용 원본 바디, 청크별 명시적 플러시.
  • 포트 49983. 서버는 일반적인 개발 포트(3000, 8000, 8080, …)가 사용자의 코드에 자유롭게 사용될 수 있도록 의도적으로 덜 사용되는 포트를 사용합니다.

서버는 github.com/huggingface/sandbox-server에서 오픈 소스로 공개되어 있습니다.

인증은 무상태(stateless)입니다

샌드박스를 보호하는 두 개의 독립적인 레이어가 있습니다:

  1. 프록시 게이트. Jobs 프록시는 해당 job의 namespace에 대한 읽기 접근 권한이 있는 HF 토큰을 포함하는 요청만 전달합니다. 인터넷의 무작위 사용자는 해당 URL에 접근할 수 없습니다.
  2. 애플리케이션 게이트. sbx-server는 /health를 제외한 모든 요청에서 X-Sandbox-Token 헤더를 추가로 확인합니다. 이는 다층 방어 전략입니다. 읽기 전용 네임스페이스 멤버가 프록시에 접근할 수 있더라도 명령을 실행할 수 없도록 합니다.

샌드박스 토큰은 저장되지 않고 파생됩니다:

nonce  = random 128-bit hex                       # stored in the job label "hf-sandbox-nonce"
token  = HMAC-SHA256(key=your_hf_token, msg="hf-sandbox:" + nonce)

nonce는 공개되어 있고 HMAC 키는 HF 토큰이므로, 해당 토큰을 보유한 어떤 기계든 샌드박스 토큰을 재계산할 수 있습니다. 이것이 재연결을 무상태(stateless)로 만드는 정확한 이유입니다. 또한 이는 토큰이 호출자에 대한 신원 확인이 아니라 자격 증명에서 파생된 capability임을 의미합니다. 서버는 호출자가 누구인지가 아니라 호출자가 토큰을 알고 있는지 확인합니다.

모든 샌드박스 작업에는 발견을 위한 두 개의 안정된 라벨이 포함됩니다 — hf-sandbox=1(모든 작업에 적용)과 hf-sandbox-mode=dedicated 또는 hf-sandbox-mode=pool — 이를 통해 서버 측에서 목록을 조회하거나 필터링할 수 있습니다. 예: hf jobs ps --label hf-sandbox=1.

토큰은 Job secret를 통해 서버로 전달됩니다. 클라이언트는 레이블의 공개 nonce에서 필요할 때 이를 다시 유도합니다. 이 방식에는 다음과 같은 장점이 있습니다:

  • 상태 없는 재연결. Sandbox.connect(id)은 동일한 HF 토큰을 보유한 어떤 기계에서도 작동합니다 — 라벨에서 nonce를 읽고 토큰을 다시 계산합니다. 로컬 파일이 필요 없고 복사할 상태도 없습니다.
  • HF 토큰은 환경 변수나 job secret으로 샌드박스에 전달되지 않습니다(forward_hf_token=True로 opt-in하지 않는 한). 이는 자격 증명이 접근 불가능하다는 것을 보장하는 것이 아닙니다. 샌드박스 포트에서 리스닝하는 프로세스는 이미지가 시작하는 첫 번째 프로세스이므로, 신뢰할 수 없는 이미지는 클라이언트가 보내는 요청을 관찰할 수 있으며, 여기에는 Authorization 헤더도 포함됩니다. 샌드박스에서 접근 가능한 자격 증명은 샌드박스에 노출될 수 있다고 간주하십시오.

토큰 범위

Job당 하나의 nonce가 발급되므로, 파생된 토큰은 job 자격 증명입니다. dedicated 모드에서는 job이 샌드박스이므로 두 개념이 일치합니다. pool 모드에서는 job이 호스트이며, 하나의 호스트는 여러 샌드박스를 보유합니다. 따라서 풀링된 샌드박스는 자신만의 두 번째, 더 좁은 범위의 자격 증명을 받게 됩니다:

자격 증명derived how권한을 부여합니다
전용 샌드박스 토큰HMAC(hf_token, nonce)를 작업의 라벨에서 가져옵니다그 샌드박스(즉, 전체 작업)
풀 호스트 토큰호스트 작업의 라벨에서 HMAC(hf_token, nonce)풀 관리: 해당 호스트에서 샌드박스를 생성, 나열 및 삭제하고 토큰을 복구합니다
풀링된 샌드박스 토큰호스트 서버가 샌드박스별로 생성하는 무작위 256비트그 하나의 샌드박스 — 형제 노드가 아니고, 풀도 아닙니다

클라이언트는 자동으로 좁은 범위의 토큰을 사용합니다: pool.create()는 생성 응답에서 이를 수신하고, Sandbox.connect("<host>.<id>")는 호스트 토큰으로 이를 복원하며, proxy_headers는 호스트의 토큰이 아닌 샌드박스의 토큰을 반환합니다(읽는 시점에 HF bearer를 해결하므로, 장수명 핸들이 오래된 토큰을 반환하지 않습니다). 이러한 헤더는 일반적으로 브라우저나 WebSocket 클라이언트에 전달되므로, 하나의 샌드박스에 대한 접근 권한만 부여해야 합니다.

누출의 의미: 풀링된 샌드박스 토큰이 유출되면 해당 샌드박스가 손상됩니다. 호스트 토큰이 유출되면 호스트가 손상되며, 현재 및 향후 호스트에 있는 모든 샌드박스와 관리 라우트가 포함되므로 풀의 관리자 자격 증명으로 취급해야 합니다. 네임스페이스의 다른 멤버는 다른 HF 토큰을 보유하고 있어 사용자의 토큰을 유도할 수 없지만, 토큰이 전달될 수 있는 잘못된 위치에 대해 알려진 제한 사항을 참조하세요.

클라이언트는 풀링된 연산에 대해 비어 있지 않은 샌드박스 스코프 토큰을 요구하며, 이를 제공하지 않는 호스트는 거부합니다. 호스트 관리 자격 증명을 대체하지 않습니다. 이 API를 사용하기 전에 클라이언트를 업그레이드하고 이전 풀 호스트를 재시작하십시오.

전용 샌드박스(Sandbox.create)

가장 단순한 모델: 샌드박스당 Job 하나. Job은 실제 VM이므로 가장 강력한 격리(VM 수준)를 제공하며, GPU를 포함한 모든 하드웨어 구성을 지원합니다. 상호 신뢰할 수 없는 코드를 실행하는 경우 적합한 선택입니다. 해당 API 라우트는 /v1/*에 있으며, 파일 경로는 컨테이너 파일 시스템에서 절대 경로입니다. kill()은 단순히 Job을 취소합니다.

전용 샌드박스가 제공하는 경계는 Job과 그 외부의 모든 것 사이입니다. VM 내부에서 사용자의 코드와 sbx-server는 모두 동일한 컨테이너에서 root 권한으로 실행됩니다. sbx-server는 사용자를 대신해 실행하는 명령어를 샌드박싱하지 않으며, HTTP를 통해 노출하는 것만 수행합니다. 따라서 신뢰할 수 없는 코드가 전용 샌드박스에서 실행된 경우, 해당 VM(컨트롤 플레인 포함)을 그 코드의 소유물로 간주해야 합니다. 이 코드는 서버를 종료하거나 교체할 수 있으며, 이후 해당 샌드박스에서 수행하는 모든 작업은 그 코드의 의지에 좌우됩니다. 신뢰 경계를 넘나들며 하나의 샌드박스를 재사용하는 대신, 신뢰할 수 없는 워크로드마다 전용 샌드박스를 하나씩 사용하고 완료 시 kill()로 종료하세요.

도표에 비용이 명확히 드러납니다. 각 샌드박스는 약 6초의 VM 콜드 스타트 비용을 전액 부담하며, 머신 전체에 대한 요금이 청구됩니다. 단일 샌드박스 또는 GPU 워크로드의 경우 이것이 정확히 원하는 동작입니다. 그러나 100~1000개의 짧은 CPU 작업의 경우 이는 낭비이며, 바로 이 문제를 해결하기 위해 풀(pool)이 존재합니다.

Pools: 하나의 Job에 여러 샌드박스 ( SandboxPool )

Pools는 하나의 신뢰 경계(trust boundary) 내부의 워크로드를 위한 것입니다. 풀된 샌드박스는 자체 VM이 아니라 공유 VM 내부의 uid + Landlock ruleset이며, 그 호스트는 모든 샌드박스가 통신하는 특권화된 제어 평면을 실행합니다. 자신의 코드를 저렴하게 분산 실행하려면 Pools를 사용하세요. 상호 불신하는 워크로드, 즉 다른 샌드박스의 파일에 대한 읽기 권한을 부여하지 않을 모든 작업에는 각 워크로드에 자체 VM을 제공하는 Sandbox.create()를 사용하세요. 구체적인 한계 사항은 Known limitations를 참조하세요.

일반적인 RL rollout 또는 도구 실행 샌드박스에는 몇 초 동안 RAM 몇 MB와 코어 1개가 필요합니다. 각 샌드박스에 대해 2-vCPU VM과 6초의 콜드 스타트를 지불하고 1000개 VM의 스케줄링 버스트를 유발하는 것은 잘못된 트레이드오프입니다. 따라서 SandboxPool은 하나의 Job을 호스트로 실행하고 그 안에 여러 샌드박스를 멀티플렉싱합니다.

풀된 샌드박스(pooled sandbox)는 중첩된 VM이나 컨테이너가 아닙니다. 이는 고전적인 Unix 멀티유저 원시 프리미티브(primitive)입니다:

  • a dedicated uid (≥ 20000),
  • 해당 uid가 소유한 0700 권한의 비공개 홈
  • 해당 uid로 exec되며 정화된 환경(env_clear을 사용하여 호스트의 시크릿이 절대 누출되지 않도록 함), NO_NEW_PRIVS, 프로세스별 rlimits, 샌드박스별 Landlock ruleset가 적용됩니다.

따라서 샌드박스 생성은 mkdir + chown + build ruleset ≈ 1ms의 서버 사이드 작업이며, 두 번째 VM 부팅이 필요하지 않습니다. 클라이언트가 인지하는 유일한 지연은 프록시 왕복 시간입니다.

풀된 샌드박스의 공개 id는 <host_job_id>.<local_id>이므로, 전용 샌드박스와 마찬가지로 connect/exec/kill가 상태 없이 작동합니다. 풀된 샌드박스에 kill()을 호출하면 호스트에 DELETE를 보내 슬롯을 해제하며, 호스트는 계속 실행됩니다.

풀 내 격리: uid + Landlock

이것은 풀(pool) 설계의 핵심이므로, 무엇이 격리되고 무엇이 격리되지 않는지 정확히 짚어볼 가치가 있습니다.

기본 Job은 uid 0..65535만 매핑되는 사용자 네임스페이스 내에서 root로 실행되며, seccomp 필터가 활성화되어 있고 CAP_SYS_ADMIN / CAP_NET_ADMIN / CAP_NET_RAW 권한이 없습니다. 이로 인해 일반적인 무거운 격리 도구를 사용할 수 없습니다: 중첩 네임스페이스, 새로운 마운트, cgroup 위임이 모두 불가능합니다(unshare, mount, /sys/fs/cgroup/...에 쓰기 모두 실패합니다). 커널이 제공하는 것은 Landlock(ABI 6)로, 이는 특권이 없는 프로세스가 자신과 자식 프로세스를 제한할 수 있게 하는 Linux Security Module이며, 바로 우리가 필요한 샌드박스별 경계입니다. 각 샌드박스에 대해 서버는 규칙 집합을 구성하며, exec 자식 프로세스는 샌드박스 uid/gid로 전환되고 명령을 실행하기 전에 NO_NEW_PRIVS, landlock_restrict_self, rlimits를 적용합니다.

Landlock과 개별 uid(임의적 액세스 제어)를 결합하는 것은 다음을 제공하도록 설계 및 테스트되었습니다:

  • ✅ A는 다른 프로세스의 environ을 읽을 수 없어 샌드박스 간에 HF 및 샌드박스 토큰에 직접 접근하는 것을 방지합니다.
  • ✅ A는 B의 프로세스에 SIGKILL / ptrace / 메모리 읽기, B로 setuid, 또는 B의 0700 홈 디렉터리 읽기를 수행할 수 없습니다.
  • ✅ /tmp 및 /dev/shm 접근이 거부됩니다 — 각 샌드박스는 Landlock에 의해 자신의 홈 디렉터리로 제한되며(해당 TMPDIR는 $HOME 내부로 지정됨).
  • ✅ A는 bind를 통해 TCP 포트를 바인딩할 수 없으므로 샌드박스 간 로컬호스트 서비스가 없습니다(외부 connect는 허용되므로 인터넷은 정상 작동합니다).
  • ✅ 샌드박스 간 추상 유닉스 소켓은 차단됩니다(LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET; uid 격리만으로는 이러한 소켓을 차단하지 않습니다).

이러한 컨트롤은 워크로드를 제한합니다. 루트 컨트롤 플레인은 여전히 별도의 신뢰 경계입니다. 호스트 모드 파일 작업은 심볼릭 링크를 거부하고, 소켓 프록시는 소켓 인오드를 고정하고 피어 uid를 확인하며, 전용 라우트는 호스트 모드에서 사용할 수 없습니다. 잔여 공유 채널은 알려진 제한 사항에 설명되어 있습니다.

이것이 VM의 대체재가 아닌 이유. Landlock과 uid 격리는 동일한 신뢰 경계 내의 워크로드를 위해 설계되었습니다. 풀링된 샌드박스는 커널, VM 및 제어 평면을 공유하므로 모든 샌드박스 간 공격으로부터의 보호가 보장되지 않으며, 리소스와 일부 프로세스 목록 메타데이터도 여전히 공유됩니다. 상호 신뢰할 수 없는 코드나 GPU를 사용하는 경우, 각 샌드박스에 자체 VM을 제공하는 Sandbox.create()를 사용하세요.

풀의 파일 모델

풀된 샌드박스의 유일한 쓰기 가능 영역은 Landlock으로 제한된 홈(이것은 기본 작업 디렉터리이기도 함)이므로, 파일 API는 모든 경로를 해당 홈을 기준으로 삼습니다: files.write("data/in.txt", ...)은 $HOME/data/in.txt에 쓰며, 앞의 /는 홈 기준으로 해석되고, ..로는 홈 밖으로 나갈 수 없습니다. API를 통해 작성된 파일은 샌드박스의 uid로 chown되어 샌드박스 자체의 코드가 읽을 수 있습니다. 이는 샌드박스 내부의 코드가 실제로 접근할 수 있는 것과 정확히 일치하는 “샌드박스를 루트로 하는 파일시스템” 모델을 제공하며, 컨테이너 파일시스템에서 경로가 절대 경로인 전용 샌드박스와는 다릅니다.

경로는 사전적으로 정규화된 후, 모든 구성 요소에 대해 심볼릭 링크를 거부하는 열린 홈 디렉터리 디스크립터 기준으로 해석됩니다. 파일 API는 동일한 홈 내부의 링크를 포함하여 심볼릭 링크를 따르지 않으며, 샌드박스 코드는 여전히 자신의 Landlock 규칙 하에서 심볼릭 링크를 사용할 수 있습니다.

Pools에는 신뢰할 수 있는 로컬 상태가 없습니다

Pool은 의도적으로 로컬 설정 파일이 아닙니다. Pool은 hf-sandbox-pool=<id> 라벨을 공유하는 실행 중인 호스트 Job들의 집합입니다. 이를 통해 Pool은 샌드박스 API의 나머지 부분과 일관성을 유지하며(모든 것이 라벨에서 발견 가능하고 어떤 기계에서도 다시 연결 가능), 마지막 호스트가 사라지면 Pool은 단순히 존재하지 않게 됩니다.

  • 호스트는 풀의 구성(이미지, flavor, sandboxes_per_host, idle timeout)을 자신의 job 환경 변수에 담고 있으며, 레이블은 필터링에만 사용됩니다. 클라이언트가 중복 호스트를 부팅해야 하는 경우 실행 중인 호스트에서 해당 구성을 다시 읽습니다(inspect_job). 이를 통해 중앙 레코드 없이도 풀 내 모든 호스트가 일관성을 유지합니다.
  • Env와 secrets는 샌드박스별로 관리되며 생성 시점에 전달됩니다. 풀 레벨에서는 전달되지 않습니다. 풀링된 샌드박스의 환경은 샌드박스의 수명 동안 호스트 서버의 메모리에 유지됩니다(실행하는 모든 명령에 다시 적용됩니다). 디스크에 기록되지 않으며 어떤 Job의 메타데이터에도 표시되지 않습니다. 풀링된 샌드박스를 위한 암호화 secrets 채널은 없습니다. env를 사용하고 해당 값을 “디스크에 저장되지는 않지만 암호화되지도 않음”으로 취급하세요. 암호화 저장소가 필요한 값은 전용 샌드박스의 secrets를 사용하세요.
  • Capacity는 서버가 관리합니다. 호스트는 sandboxes_per_host를 초과하는 생성 요청을 거부하며({"rejected": N} 응답), 클라이언트는 초과분을 다른 호스트에 배치하거나 중복 인스턴스를 시작합니다. 이를 통해 여러 프로세스가 동시에 동일한 풀에 생성 작업을 수행하더라도 배치가 정확히 유지됩니다.
  • Idle eviction은 2단계로 구성됩니다. 각 샌드박스는 실행 중인 프로세스가 없는 한, 자체 idle_timeout 기간 동안 비활성 상태가 되면 제거됩니다. 호스트에 샌드박스가 없던 시간이 호스트 idle timeout을 초과하면 호스트는 스스로 종료됩니다. 이는 모든 클라이언트가 사라지더라도 적용되는 과금 방지 장치입니다.

create --pool을 빠르게 유지하기 위한 최선 노력 캐시

권위 있는 로컬 상태가 없으면 정확성 측면에서는 유리하지만 지연 시간이 증가합니다. 콜드 hf sandbox create --pool <id>(새로운 CLI 프로세스)은 샌드박스를 생성하기 전에 네트워크를 통해 모든 것을 다시 발견해야 합니다: 네임스페이스를 스캔하기 위한 list_jobs → 각 호스트의 URL과 nonce를 재구성하기 위한 inspect_job → 각 샌드박스의 사용률을 확인하기 위한 GET /v1/sandboxes → 마지막으로 생성을 위한 POST. 모든 호출마다 순수한 오버헤드인 여러 번의 왕복 통신이 발생합니다.

$HF_HOME/sandbox/pools/<context>/<pool-id>.json에 있는 best-effort 캐시가 이를 제거합니다. create/warm 작업이 실행된 후, 프로세스는 풀 구성과 함께 호스트별 프록시 URL, 인증 nonce, 마지막으로 확인된 빈 슬롯을 기록합니다. 다음 프로세스는 HTTP 요청 없이 파일에서 바로 호스트 전송을 재구성하고 POST로 직접 이동합니다.

<context>는 해당 엔트리가 작성된 엔드포인트, 자격 증명, 네임스페이스의 요약 정보입니다. 자격 증명은 지문 형태로 저장되며, 토큰 자체는 캐시에 기록되지 않습니다. 디렉터리 이름과 파일 모두 이 정보를 포함하며, 세 항목 중 하나라도 일치하지 않는 읽기 요청은 단순한 캐시 미스로 처리됩니다. 따라서 같은 머신의 다른 사용자, 다른 엔드포인트, 또는 connect(pool, namespace="other-org") 호출은 다른 용도로 캐시된 호스트 대신 콜드 패스를 따릅니다. 파일은 0600, 디렉터리는 0700 권한으로 생성됩니다.

캐시가 신뢰할 수 있는 것과 아닌 것:

  • 용량에 대해 권위적이지 않습니다. 작업 내 서버가 계속 권위적으로 유지되므로, 오래된 live 카운트는 잘못된 호스트 패킹을 일으키지 않으며, 단지 불필요한 요청을 발생시킬 뿐입니다.
  • 호스트가 어디에 위치하는지에 대해서는 신뢰하지 않습니다. 캐시된 URL은 해당 엔트리가 지정한 작업의 HTTPS jobs-proxy URL과 일치하는 경우에만 사용되므로, 캐시 파일이 HF bearer 및 sandbox 토큰이 전송될 대상 주소를 선택할 수 없습니다. 다른 주소를 지정한 엔트리는 파일에서 제거됩니다. 15분이 지난 엔트리는 단독으로 신뢰되지 않으며, inspect_job이 해당 작업이 여전히 실행 중인지, 여전히 이 풀에 라벨이 지정되어 있는지, 여전히 해당 URL에 있는지 확인한 후에야 전송이 구성됩니다.
  • 형상이 신뢰할 수 없습니다. 타입과 범위는 읽기 시점에 검사되므로, 수동으로 편집되거나 잘리거나 문자열 기반 타입으로 저장된 파일은 create() 중간에 오류가 발생하는 것이 아니라 캐시 미스로 처리됩니다.
  • 자기 치유. 사라진 캐시된 호스트는 첫 번째 실패한 요청 시 제거되고 파일에서 정리됩니다. create는 라벨 탐색으로 투명하게 폴백합니다.
  • 동시성 안전. 파일 잠금(job_id 기준) 하에서 쓰기 작업이 병합되고 원자적으로 커밋되므로, 병렬 create 프로세스가 서로를 덮어쓰지 않으며 리더는 부분적으로 작성된 파일을 절대 보지 않습니다.
  • 일회용입니다. 삭제하면 캐시 미스가 발생하며, 콜드 패스가 실행되어 모든 기능이 정상적으로 작동합니다. 머신 간에 공유되지 않습니다.

두 가지 사항을 유의하세요. $HF_HOME는 민감한 정보로 취급해야 합니다. 각 항목은 재사용 가능한 호스트 URL과 해당 토큰의 출처인 공개 nonce로 구성되며, 다른 자격 증명으로 작성된 파일은 무시되지만 사용자 자신의 자격 증명으로 작성된 파일은 위에서 언급한 15분 창 내에서 크레딧으로 인정됩니다. 또한 namespace가 키의 일부이므로, 명시적인 namespace=로 캐시된 풀은 동일한 namespace=가 전달될 때만 다시 검색됩니다. hf sandbox create --pool <id>는 해당 풀이 생성될 때 사용된 동일한 --namespace를 요구하며, 그렇지 않으면 콜드 패스를 거쳐 사용자 자신의 namespace에서 검색합니다.

알려진 제한 사항

풀은 하나의 신뢰 경계 내 워크로드에 사용됩니다. 신뢰할 수 없는 코드는 전용 샌드박스를 사용하세요. 아래 검사 항목은 특정 리스크를 줄이지만, 풀링된 샌드박스를 개별 VM으로 변환하지는 않습니다.

Threat model at a glance

Actor전용풀
익명 인터넷 사용자Jobs 프록시는 namespace 읽기 액세스 권한이 있는 HF 토큰을 요구합니다.그렇습니다.
읽기 액세스 권한이 있는 네임스페이스 멤버프록시 및 공개 헬스 체크에 도달할 수 있지만, API 작업을 위해 샌드박스 토큰이 필요합니다.그렇습니다.
Namespace 멤버 중 Job을 생성할 수 있는 멤버nonce에서 다른 사용자의 토큰을 유도할 수 없습니다.Copy pool 라벨을 복사할 수 있지만, 기본 채택은 Jobs API의 initiator를 확인합니다. adopt_hosts="namespace"를 사용하면 다른 생성자를 신뢰하도록 선택할 수 있습니다.
샌드박스에서 실행되는 코드VM 내부에서 서버와 루트를 공유합니다. 해당 VM을 신뢰 경계 사이에 재사용하지 마세요.uid와 Landlock으로 제한되지만 커널, VM, 그리고 권한 있는 제어 평면을 공유합니다.
클라이언트HF 및 전용 샌드박스 자격 증명을 보유합니다.호스트 관리 자격 증명과 샌드박스별 개별 권한을 보유합니다.

격리 및 리소스 부족

  • 호스트 탐색은 인증이 아닙니다. 기본값 adopt_hosts="own"은 시작자, 이미지, flavor, 명령어, 노출된 URL을 확인합니다. 실행 중인 바이너리를 인증하거나 변경 가능한 이미지를 신뢰할 수 있는 것으로 만들지 않습니다. 신뢰할 수 있는 namespace 멤버와 함께만 adopt_hosts="namespace"를 사용하세요.
  • 호스트 자격 증명은 여전히 강력한 권한을 가집니다. 이 자격 증명은 풀을 관리하고 각 샌드박스의 토큰을 복구합니다. 이 클라이언트는 누락된 스코프 지정 토큰을 대체하는 데 이 자격 증명을 절대 사용하지 않습니다. 이 버전으로 부팅된 호스트는 sbx-server 0.7.0을 실행하며, 스코프 지정 루트에서 이 자격 증명을 거부합니다. 이전 버전의 클라이언트로 시작된 호스트는 재사용될 때까지 해당 서버(및 호스트 토큰 호환성)를 유지합니다.
  • 공유 채널은 유지됩니다. 아웃바운드 TCP, 제어 서버에 대한 루프백 액세스, UDP, 커널 IPC, 읽기 가능한 프로세스 목록 메타데이터는 격리되지 않습니다. 시스템 디렉터리와 선택된 디바이스 노드는 여전히 액세스할 수 있습니다. GPU 풀 격리는 테스트되지 않았습니다. GPU의 경우 전용 모드를 사용하세요.
  • 한도는 집계된 할당량이 아닙니다. 프로세스별 rlimits와 제한된 출력 큐는 리소스 증폭을 줄입니다. 하지만 샌드박스 간에 CPU 점유율, 총 메모리, 디스크 공간, inode, 네트워크 용량을 분할하지는 않습니다. 디렉터리 페이지네이션은 응답을 제한하지만 여전히 서버에서 디렉터리를 물리적으로 생성합니다. API 파일 쓰기는 디스크 할당량이 아니며, 전용 모드에서 특수 파일에 대한 쓰기는 블로킹될 수 있습니다.
  • Confinement은 명시적으로 약화할 수 있습니다. Host mode는 기본적으로 Landlock ABI 6를 요구하며, 실패한 ruleset을 거부합니다. SBX_MIN_LANDLOCK_ABI를 낮추거나 서버의 --allow-unconfined 개발 플래그를 사용하면 더 적은 보장을 허용합니다. 유효한 ABI와 기능을 확인하려면 인증된 /health를 검사하세요.
  • 프록시 연결은 한 번만 인증됩니다. 첫 번째 요청 이후 프록시는 EOF까지 해당 백엔드로 바이트를 복사합니다. 해당 연결에 대한 후속 HTTP 요청은 독립적으로 인증되거나 라우팅되지 않습니다. WebSocket/SSE 호환성은 업스트림 연결 재사용에 대한 안전성을 보장하지 않습니다.

라이프사이클 및 자격 증명

  • 분리된 자식 프로세스는 프로세스 종료 또는 타임아웃 이후에도 계속 실행될 수 있습니다. 두 방식 모두 프로세스 그룹에 시그널을 보내며, setsid()을 호출한 자식 프로세스는 해당 그룹에서 벗어납니다. 풀링된 샌드박스를 삭제하거나 전용 Job을 종료하여 전체 워크로드를 끝내야 합니다.
  • max_hosts는 최선 노력(best-effort) 방식입니다. 서로 다른 클라이언트가 동일한 호스트를 중복으로 계산하여 둘 다 더 많은 리소스를 프로비저닝할 수 있습니다. 엄격한 공유 상한을 적용하려면 백엔드 승인(admission) 또는 멱등성(idempotency)이 필요합니다.
  • Teardown이 실패할 수 있습니다. 실패한 host 취소는 예외를 발생시키고 캐시 레코드를 유지합니다. close()는 진행 중인 생성 작업에 대한 대기 시간을 제한합니다. 해당 대기 시간을 초과하여 완료되는 생성 작업은 과금 대상 Job을 남길 수 있습니다. 실행 중인 리소스를 조정하려면 Jobs API 또는 CLI를 사용하세요.
  • Bearer rotation은 샌드박스 기능을 회전하지 않습니다. 프록시 인증은 요청마다 현재 HF 토큰을 해결하지만, 상태 없는 재연결은 원래 bearer 값과 정확히 일치하는 호스트 토큰을 유도합니다. 이 값을 회전하면 기존 호스트에 재연결하는 것을 방지할 수 있습니다.
  • 로컬 캐시는 15분 동안 신뢰할 수 있는 입력으로 유지됩니다. URL과 보안 컨텍스트는 확인되지만 무결성 MAC이 없으므로, 신뢰할 수 없는 코드의 $HF_HOME에 대한 쓰기를 방지해야 합니다.
  • 다이제스트 핀은 서명된 출처가 아닙니다. 클라이언트는 특정 서버 바이너리 하나만 검증합니다. 해당 핀의 게시 및 업데이트는 릴리스 단계로 남아 있으며, 워크플로 매니페스트는 암호학적 증명이 아닙니다. 명시적인 SBX_ALLOW_UNVERIFIED_SERVER=1 예외 처리는 두 해시 도구가 모두 없는 경우에만 검증을 건너뜁니다.

인증되지 않은 /health는 liveness와 프로토콜만 노출합니다. 상세한 메타데이터에는 호스트 자격 증명이 필요합니다. Sandbox ID와 토큰은 커널의 무작위 소스가 필요하며, UID 재사용은 검증된 프로세스 및 홈 정리 후 수행됩니다.

성능

모든 수치는 cpu-basic에서 실행되는 실제 HF Jobs를 기준으로 측정되었으며, 클라이언트는 노트북에서 실행되고 모든 트래픽은 Jobs 프록시를 통해 흐릅니다.

전용 샌드박스:

메트릭value
콜드 스타트(create() 반환, 서버 응답)중위값 약 5.8초
run() 왕복p50 ~110ms (프록시 RTT 하한은 ~105ms이며, 클라이언트 오버헤드 ≈ 0)
파일 전송(병렬 범위, >8 MiB)다운로드 ~340 MiB/s, 업로드 ~441 MiB/s

풀(공유/호스트 모드):

N개의 샌드박스호스트 (50개/호스트)provision + create all모두에서 exec모두 종료합계
10026.1s1.5s0.6s8.2s
1000207.4s4.2s4.2s15.8s

1000개의 샌드박스를 생성하고 실행한 뒤 종료하는 데 약 16초가 소요되며, 이 비용은 전체에 걸쳐 분산된 단일 호스트 콜드 스타트(약 6초)와 거의 동일합니다. 총 비용은 약 $0.0009(cpu-basic 20개)로, 샌드박스당 하나의 Job을 사용하는 경우의 약 $0.06 및 1000개 VM 스케줄링 버스트와 대비됩니다. 서버 측 생성/실행/삭제 각각은 약 1ms이며, 예산은 전적으로 네트워크 왕복 지연에 사용됩니다.

설계 결정, 요약

결정왜
Jobs를 기반으로 구축, 새로운 서비스 없음billing, hardware, permissions를 상속하며, 모든 이미지에서 작동
시작 시 다운로드되는 정적 Rust 바이너리Python/pip 없음; pip 기반 부트스트랩의 30~90초에 비해 약 6초의 콜드 스타트
수동으로 구현한 HTTP/1.1minimal frameworks는 버퍼링된 청크 응답을 처리하여 라이브 스트리밍을 중단시킵니다(확인됨)
Stateless HMAC 인증어디서든 재연결할 수 있으며, HF 토큰 자체 대신 파생된 토큰을 사용합니다(작업별로 범위가 지정됨 — 토큰 범위 참조).
run()은 종료 코드가 0이 아닌 경우 예외를 발생시킵니다(check=False를 사용하면 이 동작을 비활성화할 수 있습니다).“코드 실행, 오류 확인” 루프(E2B 스타일)에 최적화된 DX
클라이언트 측 정리 대신 idle_timeout 워치독지속적 샌드박스는 기능입니다. 누출된 샌드박스도 여전히 종료됩니다
Pools = uid + Landlock, 서버 권한 기반 용량, 로컬 상태 없음빠른 동일 사용자 팬아웃; 동시성 환경에서 정확; 어디든 재연결 가능
Update on GitHub