Hub Python Library documentation
Sandboxes bên trong
Sandboxes bên trong
Hướng dẫn này giải thích cách Sandboxes hoạt động bên trong, lý do chúng được xây dựng theo cách đó và những hạn chế của chúng. Nếu bạn chỉ muốn sử dụng sandboxes, Hướng dẫn Sandboxes là đủ; hãy tiếp tục đọc nếu bạn muốn hiểu cơ chế, đánh giá mô hình tin cậy hoặc gỡ lỗi một vấn đề nào đó.
API Sandbox mang tính thử nghiệm. API và hành vi của nó có thể thay đổi mà không cần thông báo trước.
Không có “dịch vụ sandbox”
Điều đầu tiên cần hiểu là không có backend sandbox riêng biệt. Một sandbox đơn giản chỉ là một HF Job (một VM) chạy một binary tĩnh nhỏ duy nhất sbx-server giao tiếp qua HTTP. Client nói chuyện với server đó thông qua Jobs proxy (URL *.hf.jobs mà Job công bố). Mọi thứ khác — xác thực, khám phá, đóng gói nhiều sandbox vào một Job — đều được xây dựng từ các nguyên thủy Jobs hiện có: labels, biến môi trường và secrets.
Thiết kế “không cần hạ tầng mới” này là lý do một sandbox hoạt động trong bất kỳ Docker image nào và kế thừa miễn phí hóa đơn, các loại phần cứng và quyền namespace của Jobs.
Khởi tạo máy chủ
Khi khởi động job, lệnh của Job là một script /bin/sh -c nhỏ, có nhiệm vụ tải binary sbx-server, cấp quyền thực thi cho nó và exec nó. Binary này là bản build tĩnh musl cỡ ~640KB, không có bất kỳ phụ thuộc nào lúc chạy, nên có thể chạy trên bất kỳ image Linux x86_64 nào.
Một vài quyết định đáng lưu ý:
- Tải xuống, với phương án dự phòng là mount. Lối tắt tải binary từ HF CDN bằng
wgethoặccurl(mọi base image phổ biến đều có sẵn một trong hai), nhanh và miễn phí. Để an toàn, Hub repo của server cũng được mount dưới dạng volume trên mọi job: nếu image không có cảwgetlẫncurl, script sẽ copy binary từ mount đó thay thế. Mount là trong suốt — không tốn chi phí trừ khi thực sự đọc — nhưng việc đọc nó làm tăng ~2-3s cho cold start, vì vậy đây không phải là mặc định. Yêu cầu duy nhất bắt buộc đối với image là/bin/sh. - Một máy chủ HTTP/1.1 tự viết, không dùng framework. Việc phát trực tiếp đầu ra yêu cầu phải xả (flush) từng khối (chunk) ngay khi nó được tạo ra. Các máy chủ HTTP Rust tối giản phổ biến (ví dụ
tiny_http) sẽ đệm (buffer) các phản hồi dạng chunked cho đến khi phản hồi hoàn tất, điều này làm hỏng việc phát trực tiếp. Do đó, máy chủ này tự triển khai HTTP/1.1: các luồng sự kiện NDJSON choexec, thân (body) thô cho tệp, và một thao tác xả rõ ràng cho từng khối. - Cổng 49983. Máy chủ lắng nghe trên một cổng ít phổ biến một cách có chủ đích để các cổng phát triển thông dụng (3000, 8000, 8080, …) được giữ trống cho mã của bạn.
Máy chủ có mã nguồn mở tại github.com/huggingface/sandbox-server.
Xác thực không có trạng thái
Hai lớp độc lập bảo vệ một sandbox:
- Cổng proxy. Proxy của Jobs chỉ chuyển tiếp các yêu cầu mang token HF có quyền đọc vào namespace của job. Một thành viên ngẫu nhiên của internet không thể truy cập được URL.
- Cổng ứng dụng.
sbx-serverkiểm tra thêm tiêu đềX-Sandbox-Tokentrên mọi yêu cầu ngoại trừ/health. Đây là phòng thủ nhiều lớp: thành viên namespace chỉ đọc có thể truy cập proxy nhưng vẫn không thể thực thi lệnh.
Token sandbox được suy ra, không được lưu trữ:
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 là công khai và khóa HMAC chính là token HF của bạn, do đó bất kỳ máy nào nắm giữ token đó đều có thể tính lại token sandbox — đó chính là điều khiến việc kết nối lại trở nên không trạng thái. Điều này cũng có nghĩa là token là một quyền năng được suy ra từ thông tin xác thực của bạn, chứ không phải là một bước kiểm tra danh tính của người gọi: máy chủ xác minh rằng người gọi biết token, chứ không xác minh họ là ai.
Mỗi tác vụ sandbox cũng mang hai nhãn ổn định để phục vụ việc tìm kiếm — hf-sandbox=1 (trên tất cả các tác vụ) và hf-sandbox-mode=dedicated hoặc hf-sandbox-mode=pool — để bạn có thể liệt kê hoặc lọc chúng phía máy chủ, ví dụ hf jobs ps --label hf-sandbox=1.
Token được chuyển đến máy chủ thông qua một Job secret. Client sẽ tự suy ra lại token này khi cần từ public nonce trong label. Điều này mang lại một số hệ quả tích cực:
- Kết nối lại không trạng thái.
Sandbox.connect(id)hoạt động từ bất kỳ máy nào giữ cùng một token HF — đọc nonce từ nhãn, tính lại token. Không có tệp cục bộ, không có trạng thái để sao chép. - Token HF không được truyền vào sandbox dưới dạng biến môi trường hoặc bí mật của job (trừ khi bạn chọn tham gia bằng
forward_hf_token=True). Đây không phải là đảm bảo tuyệt đối rằng thông tin xác thực của bạn sẽ không bị truy cập: tiến trình lắng nghe trên cổng của sandbox chính là tiến trình được image khởi chạy đầu tiên, do đó một image không đáng tin cậy có thể quan sát được các yêu cầu mà client gửi đi — bao gồm cả headerAuthorizationcủa chúng. Hãy coi các thông tin xác thực có thể truy cập được từ sandbox là có khả năng bị phơi bày cho sandbox.
Phạm vi token
Một nonce được tạo cho mỗi Job, do đó token dẫn xuất là thông tin xác thực của job. Ở chế độ dedicated, job chính là sandbox, nên hai khái niệm này trùng nhau. Ở chế độ pool, job là host, và một host chứa nhiều sandbox — do đó một sandbox trong pool sẽ có thêm một thông tin xác thực riêng, hẹp hơn:
| credential | được suy ra như thế nào | cấp quyền |
|---|---|---|
| token sandbox chuyên dụng | HMAC(hf_token, nonce) từ nhãn của tác vụ | sandbox đó (đó là toàn bộ công việc) |
| token máy chủ pool | HMAC(hf_token, nonce) từ nhãn của công việc host | quản lý pool: tạo, liệt kê và xóa các sandbox trên host đó, và khôi phục token của chúng |
| token sandbox đã gộp | 256 bit ngẫu nhiên, được tạo bởi máy chủ host cho mỗi sandbox | một sandbox đó — không phải sandbox anh em, không phải pool |
Client tự động sử dụng phiên bản hẹp: pool.create() nhận nó trong phản hồi tạo, Sandbox.connect("<host>.<id>") khôi phục nó bằng token của host, và proxy_headers cấp phát token của sandbox thay vì token của host (giải quyết HF bearer tại thời điểm bạn đọc nó, nên một handle tồn tại lâu dài sẽ không cấp phát token đã hết hạn) — các header này thường xuất hiện trong trình duyệt hoặc client WebSocket, vì vậy chúng chỉ nên cấp quyền truy cập vào một sandbox và không gì khác.
Điều này có nghĩa là gì đối với một vụ rò rỉ: một token sandbox được pooling sẽ làm tổn hại sandbox đó. Một token host sẽ làm tổn hại host — mọi sandbox trên nó, hiện tại và trong tương lai, cùng các tuyến quản lý của nó — vì vậy hãy coi nó như thông tin xác thực quản trị viên của pool. Các thành viên trong namespace của bạn giữ một token HF khác và không thể suy ra token của bạn, nhưng xem Hạn chế đã biết để biết cách một token có thể được gửi đến sai nơi.
Client yêu cầu một token phạm vi sandbox không rỗng cho các thao tác pooled và từ chối các host không cung cấp token này. Client không bao giờ thay thế bằng thông tin xác thực quản lý host. Hãy nâng cấp client và tái tạo các host pool cũ trước khi sử dụng API này.
Các sandbox chuyên dụng ( Sandbox.create )
Mô hình đơn giản: mỗi sandbox một Job. Job là một VM thực, nên cách này mang lại mức cách ly mạnh nhất (cấp độ VM), hỗ trợ mọi loại phần cứng bao gồm cả GPU, và là lựa chọn phù hợp cho các mã không tin cậy lẫn nhau. Các API routes nằm tại /v1/* và đường dẫn file là tuyệt đối trên hệ thống tệp của container. kill() đơn giản là hủy Job.
Ranh giới mà một sandbox chuyên dụng mang lại cho bạn là giữa Job và mọi thứ bên ngoài nó. Bên trong VM, mã của bạn và sbx-server đều chạy với quyền root trong cùng một container: sbx-server không sandbox lệnh mà nó thực thi cho bạn, nó chỉ phơi bày lệnh đó qua HTTP. Vì vậy, một khi mã mà bạn không tin tưởng đã được thực thi trong một sandbox chuyên dụng, hãy coi VM đó — bao gồm cả control plane của nó — là thuộc về mã đó: nó có thể giết hoặc thay thế server, và bất kỳ thứ gì bạn thực hiện trong sandbox đó sau đó đều nằm dưới sự kiểm soát của nó. Hãy sử dụng một sandbox chuyên dụng cho mỗi workload không tin tưởng và kill() nó khi hoàn thành, thay vì tái sử dụng một sandbox xuyên qua các ranh giới tin cậy.
Chi phí thể hiện rõ trong sơ đồ: mỗi sandbox phải chịu thời gian khởi động lạnh VM đầy đủ khoảng ~6s và tính phí cho cả một máy. Đối với một sandbox đơn lẻ hoặc tác vụ GPU, đó chính xác là điều bạn cần. Đối với 100–1000 tác vụ CPU ngắn, điều này gây lãng phí — và đó là mục đích của các pool.
Pools: nhiều sandbox trong một Job ( SandboxPool )
Pools dành cho các tác vụ nằm trong cùng một ranh giới tin cậy. Một sandbox dạng pool là một uid + một bộ quy tắc Landlock bên trong một VM dùng chung, không phải một VM riêng, và host của nó chạy một control plane có đặc quyền mà tất cả các sandbox của nó đều giao tiếp. Sử dụng pools để triển khai mã của bạn một cách tiết kiệm. Đối với các tác vụ không tin tưởng lẫn nhau — bất kỳ thứ gì mà bạn không cho phép đọc tệp của các sandbox khác của bạn — hãy dùng Sandbox.create(), nó cấp cho mỗi tác vụ một VM riêng. Xem Hạn chế đã biết để biết các khoảng trống cụ thể.
Một lượt rollout RL hoặc sandbox thực thi công cụ điển hình chỉ cần vài MB RAM và một lõi CPU trong vài giây. Việc trả tiền cho một VM 2 vCPU và chịu thời gian khởi động lạnh 6 giây cho mỗi lượt — đồng thời kích hoạt một đợt lập lịch 1000 VM — là một sự đánh đổi không hợp lý. Vì vậy, SandboxPool chạy một Job như một host và đa hợp nhiều sandbox bên trong nó.
Một sandbox được chia sẻ không phải là một VM hoặc container lồng nhau. Nó là nguyên thủy đa người dùng cổ điển của Unix:
- một uid riêng (≥ 20000),
- một thư mục home riêng tư
0700thuộc sở hữu của uid đó, - các lệnh
execđược thực thi với uid đó trong một môi trường đã được làm sạch (env_clear, để các bí mật của host không bao giờ bị rò rỉ),NO_NEW_PRIVS, rlimits cho từng tiến trình và một bộ quy tắc Landlock cho từng sandbox.
Do đó, việc tạo một sandbox là mkdir + chown + build ruleset ≈ 1ms phía server — không cần khởi động VM thứ hai. Độ trễ duy nhất mà client nhìn thấy là vòng lặp qua proxy.
Public id của một pooled sandbox là <host_job_id>.<local_id>, do đó connect/exec/kill hoạt động không trạng thái giống như các sandbox dedicated. kill() trên một pooled sandbox gửi DELETE đến host của nó (giải phóng một slot); host vẫn tiếp tục chạy.
Cách ly trong một pool: uid + Landlock
Đây là cốt lõi của thiết kế pool, vì vậy cần chính xác về những gì được và không được cách ly.
Một Job mặc định chạy với quyền root bên trong một user namespace chỉ ánh xạ các uid từ 0 đến 65535, có bật bộ lọc seccomp và không có CAP_SYS_ADMIN / CAP_NET_ADMIN / CAP_NET_RAW. Điều này loại trừ các công cụ cách ly nặng nề thông thường: không có namespace lồng nhau, không có mount mới, không có ủy quyền cgroup (unshare, mount, ghi vào /sys/fs/cgroup/... đều thất bại). Điều mà kernel cung cấp là Landlock (ABI 6), một Linux Security Module cho phép bất kỳ tiến trình không đặc quyền nào tự giới hạn bản thân và các tiến trình con của nó — chính xác là ranh giới cho từng sandbox mà chúng ta cần. Đối với mỗi sandbox, server xây dựng một bộ quy tắc; tiến trình con exec hạ xuống uid/gid của sandbox và áp dụng NO_NEW_PRIVS, landlock_restrict_self, cùng rlimits trước khi chạy lệnh.
Việc kết hợp các uid riêng biệt (kiểm soát truy cập tùy chọn) với Landlock được thiết kế và kiểm thử để cung cấp:
- ✅ A không thể đọc
environcủa một tiến trình khác, ngăn chặn việc truy cập trực tiếp vào các token HF và sandbox giữa các sandbox. - ✅ A không thể
SIGKILL/ptrace/ đọc bộ nhớ của các tiến trình của B,setuidvào B, hoặc đọc thư mục home0700của B. - ✅ Truy cập
/tmpvà/dev/shmbị từ chối — mỗi sandbox được giới hạn bởi Landlock trong thư mục home riêng của nó (TMPDIRcủa nó trỏ vào bên trong$HOME). - ✅ A không thể
bindmột cổng TCP, do đó không có dịch vụ localhost giữa các sandbox (vẫn cho phépconnectra ngoài, nên internet hoạt động bình thường). - ✅ Các socket unix trừu tượng xuyên sandbox bị chặn (
LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET; chỉ cách ly uid không chặn được chúng).
Các điều khiển này ràng buộc khối lượng công việc. Mặt phẳng điều khiển gốc vẫn là một ranh giới tin cậy riêng biệt: các thao tác tệp ở chế độ host của nó từ chối các liên kết mềm, bộ đại diện socket của nó cố định inode của socket và kiểm tra uid của đối tác, và các tuyến đường riêng biệt không khả dụng ở chế độ host. Các kênh chia sẻ còn lại được mô tả trong Hạn chế đã biết.
Tại sao đây không phải là sự thay thế cho VM. Landlock và cách ly uid được thiết kế cho các tác vụ nằm trong cùng một ranh giới tin cậy. Vì các sandbox trong pool chia sẻ một kernel, một VM và một control plane, nên không thể đảm bảo bảo vệ khỏi mọi cuộc tấn công xuyên sandbox; tài nguyên và một số metadata của danh sách tiến trình cũng vẫn được chia sẻ. Đối với các mã không tin cậy lẫn nhau, hoặc cho GPU, hãy sử dụng Sandbox.create(), cho phép mỗi sandbox có VM riêng của nó.
Tệp mô hình trong một pool
Vì vùng ghi duy nhất của sandbox được pool là thư mục home bị giới hạn bởi Landlock (đồng thời cũng là thư mục làm việc mặc định), API tệp gốc mọi đường dẫn tại thư mục home đó: files.write("data/in.txt", ...) ghi vào $HOME/data/in.txt, một / đứng đầu được hiểu là tương đối so với thư mục home, và .. không thể thoát ra ngoài. Các tệp được ghi thông qua API sẽ được chown cho uid của sandbox để chính mã của sandbox có thể đọc chúng. Điều này tạo ra mô hình “hệ thống tệp có gốc tại sandbox” sạch sẽ, khớp chính xác với những gì mã bên trong sandbox có thể chạm tới — và khác với các sandbox chuyên dụng, nơi các đường dẫn là tuyệt đối trên hệ thống tệp của container.
Các đường dẫn được chuẩn hóa theo từ điển, sau đó được phân giải tương đối so với bộ mô tả thư mục home đang mở, với việc từ chối symlink trên mọi thành phần. API tệp không theo dõi symlink, bao gồm cả các liên kết nằm trong cùng một thư mục home; mã sandbox vẫn có thể sử dụng chúng dưới các quy tắc Landlock của nó.
Pools không có trạng thái cục bộ chính thức
Pool được thiết kế để không phải là một tệp cấu hình cục bộ. Một pool chính là tập hợp các Job đang chạy trên các host, tất cả đều chia sẻ nhãn hf-sandbox-pool=<id>. Điều này giúp các pool nhất quán với phần còn lại của API sandbox (mọi thứ đều có thể được phát hiện từ nhãn và gắn lại từ bất kỳ máy nào), đồng nghĩa với việc một pool đơn giản là ngừng tồn tại ngay khi host cuối cùng của nó biến mất.
- Một host mang cấu hình của pool (image, flavor,
sandboxes_per_host, thời gian chờ khi nhàn rỗi) trong các biến môi trường của job — nhãn chỉ được dùng để lọc. Khi một client phải khởi động một host trùng lặp, nó đọc lại cấu hình đó từ một host đang chạy (inspect_job), nhờ đó tất cả các host trong một pool luôn nhất quán mà không cần một bản ghi tập trung. - Env và secrets được cấp theo từng sandbox, được truyền vào tại thời điểm tạo — không bao giờ ở cấp pool. Môi trường của một sandbox trong pool được giữ trong bộ nhớ của máy chủ host trong suốt vòng đời của sandbox (nó được áp dụng lại cho mọi lệnh bạn chạy); nó không bao giờ được ghi xuống đĩa và không bao giờ xuất hiện trong metadata của bất kỳ Job nào. Không có kênh secrets mã hóa cho các sandbox trong pool — hãy dùng
envvà coi các giá trị đó là “không lưu trữ tại chỗ, nhưng cũng không được mã hóa”. Đối với các giá trị cần kho lưu trữ mã hóa, hãy dùngsecretscủa một sandbox chuyên dụng. - Dung lượng do máy chủ quyết định. Máy chủ từ chối các yêu cầu tạo vượt quá
sandboxes_per_host(trả về{"rejected": N}); client sẽ đóng gói phần vượt sang một máy chủ khác hoặc khởi động một bản sao. Điều này giúp việc đóng gói luôn chính xác ngay cả khi nhiều tiến trình cùng tạo vào cùng một pool đồng thời. - Việc loại bỏ khi nhàn rỗi diễn ra theo hai cấp. Mỗi sandbox sẽ bị loại bỏ sau khoảng thời gian
idle_timeoutkhông hoạt động của riêng nó (trừ khi vẫn còn một tiến trình đang chạy); một khi host không còn sandbox nào trong khoảng thời gian nhàn rỗi của host, nó sẽ tự tắt — đây là biện pháp dự phòng về hóa đơn ngay cả khi mọi client biến mất.
Bộ nhớ đệm cố gắng hết sức giúp lệnh create --pool chạy nhanh
Việc không có trạng thái cục bộ chính thức là rất tốt cho tính chính xác nhưng lại gây ra độ trễ. Một lệnh hf sandbox create --pool <id> lạnh (một tiến trình CLI mới) nếu không sẽ phải khám phá lại mọi thứ qua mạng trước khi có thể tạo sandbox: list_jobs để quét namespace → inspect_job từng host để xây dựng lại URL và nonce của nó → GET /v1/sandboxes để xem mức độ đầy của từng sandbox → cuối cùng là POST để tạo. Nhiều vòng lặp chỉ là chi phí thừa, trong mỗi lần gọi.
Một bộ nhớ đệm theo kiểu nỗ lực tối đa tại $HF_HOME/sandbox/pools/<context>/<pool-id>.json sẽ loại bỏ điều đó. Sau bất kỳ thao tác tạo/nóng nào, một tiến trình sẽ ghi lại cấu hình pool cùng, cho từng host, URL proxy, mã xác thực ngẫu nhiên (auth nonce) và các slot trống được nhìn thấy gần nhất. Tiến trình tiếp theo sẽ xây dựng lại kết nối host trực tiếp từ tệp (không qua HTTP) và chuyển thẳng đến POST.
<context> là bản tóm tắt của endpoint, credential và namespace mà entry được ghi cho — credential dưới dạng fingerprint; chính token của bạn không bao giờ được ghi vào cache. Cả tên thư mục và tệp đều chứa thông tin này, và một thao tác đọc không khớp trên cả ba yếu tố sẽ là một cache miss thông thường, do đó một người dùng khác của máy, một endpoint khác, hoặc một connect(pool, namespace="other-org") sẽ nhận đường dẫn cold thay vì các host đã được cache cho mục đích khác. Tệp được tạo với quyền 0600, thư mục với quyền 0700.
Cache được tin cậy và không được tin cậy cho những gì:
- Không có thẩm quyền về sức chứa. Máy chủ trong job vẫn giữ thẩm quyền, nên một số lượng
livecũ chỉ gây ra một yêu cầu bị lãng phí, chứ không bao giờ dẫn đến việc đóng gói sai host. - Không đáng tin cậy về vị trí của host. Một URL được cache chỉ được sử dụng nếu nó là URL jobs-proxy HTTPS của job mà cùng entry đó chỉ định, do đó một tệp cache không thể chọn đích mà token bearer HF và token sandbox của bạn được gửi đến; một entry chỉ định bất kỳ thứ gì khác sẽ bị loại khỏi tệp. Sau 15 phút, một entry cũng không còn được tính dựa trên chính nó nữa:
inspect_jobphải xác nhận rằng job vẫn đang chạy, vẫn được gắn nhãn cho pool này và vẫn ở trên URL đó trước khi transport được xây dựng. - Không đáng tin cậy về hình dạng. Kiểu và phạm vi được kiểm tra khi đọc, nên một tệp được chỉnh sửa thủ công, bị cắt cụt hoặc có kiểu chuỗi sẽ là một lần bỏ lỡ bộ nhớ đệm thay vì một lỗi xảy ra giữa chừng
create(). - Tự phục hồi. Một host được cache đã biến mất sẽ bị loại bỏ ở yêu cầu thất bại đầu tiên và được cắt bỏ khỏi tệp; thao tác tạo sẽ tự động chuyển sang khám phá nhãn.
- An toàn với đa luồng. Các thao tác ghi được hợp nhất dưới một khóa tệp (dựa trên
job_id) và cam kết một cách nguyên tử, do đó các tiến trìnhcreatechạy song song sẽ không ghi đè lên nhau và người đọc sẽ không bao giờ thấy tệp bị ghi dở. - Dùng một lần. Xóa nó và bạn sẽ gặp lỗi cache: đường dẫn lạnh (cold path) sẽ chạy và mọi thứ vẫn hoạt động bình thường. Nó không bao giờ được chia sẻ giữa các máy.
Có hai điều cần lưu ý. Hãy coi $HF_HOME là nhạy cảm: một mục nhập vẫn là một URL host có thể tái sử dụng cùng với nonce công khai mà token của nó được suy ra từ đó, và trong khi một tệp được ghi dưới một thông tin xác thực khác sẽ bị bỏ qua, một tệp được ghi dưới thông tin xác thực của bạn sẽ được ghi nhận cho cửa sổ 15 phút nêu trên. Và vì namespace là một phần của khóa, một pool được cache dưới một namespace= tường minh chỉ được tìm thấy lại khi cùng một namespace= được truyền vào — hf sandbox create --pool <id> yêu cầu cùng một --namespace mà pool đã được tạo với, nếu không nó sẽ đi theo đường dẫn lạnh và tìm trong namespace của riêng bạn.
Hạn chế đã biết
Pools dành cho các tác vụ nằm trong cùng một ranh giới tin cậy. Hãy sử dụng các sandbox riêng biệt cho mã mà bạn không tin tưởng. Các kiểm tra bên dưới giúp giảm thiểu các rủi ro cụ thể; chúng không biến các sandbox được pool thành các VM riêng biệt.
Tổng quan về mô hình mối đe dọa
| Actor | Dành riêng | Pool |
|---|---|---|
| Người dùng internet ẩn danh | Proxy Jobs yêu cầu một token HF có quyền đọc namespace. | Tương tự. |
| Thành viên namespace có quyền đọc | Có thể truy cập proxy và kiểm tra sức khỏe công khai, nhưng cần token sandbox cho các thao tác API. | Tương tự. |
| Thành viên của Namespace có thể tạo Jobs | Không thể suy ra token của người dùng khác từ nonce của nó. | Có thể sao chép nhãn pool, nhưng mặc định việc nhận sẽ kiểm tra người khởi tạo của API Jobs. adopt_hosts="namespace" chọn cách tin tưởng các người tạo khác. |
| Mã chạy trong sandbox | Chia sẻ thư mục gốc với máy chủ bên trong VM; không tái sử dụng VM đó giữa các ranh giới tin cậy. | Bị giới hạn bởi uid và Landlock, nhưng chia sẻ kernel, VM và mặt phẳng điều khiển đặc quyền. |
| Client của bạn | Chứa thông tin xác thực của HF và sandbox chuyên dụng. | Chứa thông tin xác thực quản lý host và các khả năng riêng biệt cho từng sandbox. |
Khoảng cách về cách ly và tài nguyên
- Phát hiện host không phải là xác thực. Mặc định
adopt_hosts="own"kiểm tra initiator, image, flavor, command và URL được phơi bày. Nó không xác thực binary đang chạy cũng không làm cho image có thể thay đổi trở nên đáng tin cậy. Chỉ sử dụngadopt_hosts="namespace"với các thành viên namespace mà bạn tin tưởng. - Thông tin xác thực của host vẫn rất mạnh. Chúng quản lý pool và khôi phục token của từng sandbox. Chúng không bao giờ được thay thế cho các scoped token bị thiếu bởi client này. Các host được khởi động bởi phiên bản này chạy sbx-server 0.7.0, từ chối chúng trên các scoped routes; các host được khởi động bởi các client cũ hơn giữ lại server của chúng (và khả năng tương thích host-token của nó) cho đến khi chúng được tái chế.
- Các kênh dùng chung vẫn được giữ lại. Kết nối TCP ra ngoài, truy cập loopback đến máy chủ điều khiển, UDP, IPC của kernel và metadata của danh sách tiến trình có thể đọc được không bị cách ly. Các thư mục hệ thống và một số nút thiết bị được chọn vẫn có thể truy cập được. Việc cách ly pool GPU chưa được kiểm tra; hãy sử dụng chế độ dành riêng cho GPU.
- Giới hạn không phải là hạn mức tổng hợp. rlimits theo tiến trình và hàng đợi đầu ra có giới hạn giúp giảm khuếch đại tài nguyên. Chúng không phân chia phần CPU, bộ nhớ tổng, dung lượng đĩa, inode hoặc băng thông mạng giữa các sandbox. Phân trang thư mục giới hạn phản hồi nhưng vẫn tạo ra thư mục trên máy chủ. Ghi tệp qua API không phải là hạn mức đĩa, và ghi ở chế độ dành riêng vào các tệp đặc biệt có thể bị chặn.
- Confinement có thể được làm suy yếu một cách rõ ràng. Chế độ Host yêu cầu Landlock ABI 6 theo mặc định và từ chối ruleset thất bại. Việc hạ thấp
SBX_MIN_LANDLOCK_ABIhoặc sử dụng cờ phát triển--allow-unconfinedcủa server sẽ chấp nhận ít bảo đảm hơn; hãy kiểm tra/healthđã xác thực để biết ABI và các tính năng hiệu lực. - Kết nối proxy chỉ được xác thực một lần. Sau yêu cầu đầu tiên, proxy sẽ sao chép các byte đến backend đó cho đến khi kết thúc (EOF). Các yêu cầu HTTP tiếp theo trên kết nối đó sẽ không được xác thực hay định tuyến độc lập. Khả năng tương thích WebSocket/SSE không đảm bảo an toàn trước việc tái sử dụng kết nối phía upstream.
Chu kỳ sống và thông tin xác thực
- Các tiến trình con bị tách ra có thể tồn tại sau khi tiến trình cha kết thúc hoặc hết thời gian chờ. Cả hai đều gửi tín hiệu đến nhóm tiến trình; một tiến trình con gọi
setsid()sẽ thoát khỏi nhóm đó. Hãy xóa sandbox trong pool hoặc kết thúc Job chuyên dụng để chấm dứt toàn bộ khối lượng công việc. max_hostschỉ là nỗ lực tốt nhất. Các client riêng biệt có thể đếm cùng một nhóm host và cả hai đều provision thêm. Cần có cơ chế chấp nhận phía backend hoặc tính idempotency để đặt giới hạn chung cứng.- Teardown có thể thất bại. Các lệnh hủy host thất bại sẽ raise và giữ lại các bản ghi cache.
close()giới hạn thời gian chờ cho các thao tác tạo đang chạy; một thao tác tạo kéo dài vượt quá thời gian chờ đó có thể để lại một Job tính phí. Hãy sử dụng Jobs API hoặc CLI để đối soát các tài nguyên đang chạy. - Việc xoay vòng Bearer không xoay vòng các khả năng của sandbox. Xác thực proxy giải quyết token HF hiện tại cho mỗi yêu cầu, nhưng việc kết nối lại không trạng thái suy ra token host từ giá trị bearer gốc chính xác. Việc xoay vòng giá trị đó có thể ngăn cản việc kết nối lại với các host hiện có.
- Bộ nhớ đệm cục bộ vẫn được coi là đầu vào đáng tin cậy trong 15 phút. URL và ngữ cảnh bảo mật của nó được kiểm tra, nhưng nó không có MAC toàn vẹn; hãy bảo vệ
$HF_HOMEkhỏi việc ghi bởi mã không đáng tin cậy. - Một digest pin không phải là nguồn gốc đã ký. Client xác minh một binary server cụ thể. Việc công bố và cập nhật pin đó vẫn là các bước phát hành; manifest của workflow không phải là một xác nhận mật mã. Lối thoát rõ ràng
SBX_ALLOW_UNVERIFIED_SERVER=1bỏ qua xác minh chỉ khi cả hai công cụ hash đều thiếu.
Chưa xác thực /health chỉ hiển thị trạng thái hoạt động và giao thức. Metadata chi tiết yêu cầu thông tin xác thực của host. Id và token của sandbox yêu cầu nguồn ngẫu nhiên từ kernel; việc tái sử dụng UID tuân theo quy trình xác minh và dọn dẹp thư mục home.
Hiệu năng
Tất cả các con số được đo lường trên các HF Jobs thực tế chạy trên cpu-basic, với client đặt trên laptop và toàn bộ lưu lượng đi qua proxy của Jobs.
Môi trường sandbox riêng:
| metric | value |
|---|---|
khởi động lạnh (create() trả về, máy chủ bắt đầu phản hồi) | ~5,8s trung vị |
round-trip run() | p50 ~110ms (mức sàn RTT của proxy là ~105ms; chi phí phát sinh phía client ≈ 0) |
| chuyển tệp (song song theo khoảng, >8 MiB) | ~340 MiB/s xuống, ~441 MiB/s lên |
Pool (chế độ shared/host):
| N sandbox | máy chủ (50/máy chủ) | cấp phát + tạo tất cả | chạy trong tất cả | kill all | tổng |
|---|---|---|---|---|---|
| 100 | 2 | 6.1s | 1.5s | 0.6s | 8.2s |
| 1000 | 20 | 7.4s | 4.2s | 4.2s | 15.8s |
1000 sandbox được tạo, thực thi và hủy trong ~16s có chi phí xấp xỉ một lần khởi động lạnh của host (~6s) được phân bổ cho tất cả chúng — tổng cộng khoảng $0.0009 (20 × cpu-basic), so với ~$0.06 và một đợt lập lịch 1000-VM cho mỗi Job trên một sandbox. Các thao tác tạo/thực thi/xóa phía server đều mất ~1ms; ngân sách thời gian hoàn toàn nằm ở vòng lặp mạng.
Quyết định thiết kế, tóm tắt
| quyết định | tại sao |
|---|---|
| Xây dựng trên Jobs, không có dịch vụ mới | kế thừa hóa đơn, phần cứng, quyền hạn; hoạt động trong bất kỳ image nào |
| Binary Rust tĩnh, được tải xuống khi khởi động | không có Python/pip; khởi động lạnh ~6 giây so với 30–90 giây cho một bootstrap dựa trên pip |
| HTTP/1.1 tự viết | các framework tối giản đệm các phản hồi dạng chunk và làm hỏng việc phát trực tiếp (đã xác minh) |
| Xác thực HMAC không trạng thái | kết nối lại từ bất kỳ đâu; một token phái sinh thay vì chính token HF (được giới hạn phạm vi theo từng job — xem Phạm vi token) |
run() sẽ gây ra lỗi nếu mã thoát khác không (check=False để tắt tính năng này) | DX tốt nhất cho các vòng lặp “chạy mã, xem lỗi” (kiểu E2B) |
idle_timeout watchdog thay vì dọn dẹp phía client | Các sandbox tồn tại lâu dài là một tính năng; những sandbox bị rò rỉ vẫn sẽ bị hủy |
| Pools = uid + Landlock, sức chứa do máy chủ kiểm soát, không có trạng thái cục bộ | fan-out cho cùng một người dùng nhanh; chính xác dưới điều kiện đồng thời; có thể gắn lại ở bất kỳ đâu |