ข้ามไปยังเนื้อหา

แบ็กเอนด์แซนด์บ็อกซ์

แบ็กเอนด์ ค่า แพลตฟอร์ม ความพร้อมใช้
VM ที่หุ้ม microvm macOS, Linux, Windows (ไฮเปอร์ไวเซอร์ microVM) ถูกกั้นด้วยการตรวจหาบนรันไทม์ของ microVM; ใช้กับเทอร์มินัลแบบโต้ตอบ — เชลล์รันอยู่ภายในริก ไม่ใช่บนโฮสต์
เนทีฟ native macOS (Seatbelt), Linux และ WSL2 (bubblewrap) ตรวจหาตอนบูต; Linux ปฏิเสธการรันที่ต้องมีข้อปฏิเสธการเขียนตามกลอบชื่อไฟล์ ซึ่ง bubblewrap บังคับใช้ไม่ได้
ไม่แยก none ทั้งหมด ค่าเริ่มต้นของการแยกวิเคราะห์ ค่าเริ่มต้นของเทอร์มินัล และตัวถอยกลับเมื่อไม่มีแบ็กเอนด์ให้ใช้ หรือเมื่อตั้ง --sandbox off

SandboxBackend.fromName คืนค่า none สำหรับค่าที่จัดเก็บไว้เป็น null หรือที่ไม่รู้จัก ค่า "docker" ที่จัดเก็บไว้จะถูกแมปไปที่ native

microvm เป็นแบ็กเอนด์เดียวที่มีขอบเขตระดับเคอร์เนล — งานที่รันอยู่ไม่ได้รันบนโฮสต์จริง ๆ มันไม่เคยเป็นตัวถอยกลับแบบเงียบในทิศทางใดเลย: การขอมันอย่างชัดเจนบนโฮสต์ที่บูต VM ไม่ได้จะล้มเหลวอย่างส่งเสียง (“ผมขอ VM แต่ได้แล็ปท็อปของผมเอง” คือการเสื่อมสภาพอย่างเดียวที่เอนโคลเจอร์รับไม่ได้) และมีเพียง ค่าเริ่มต้นที่ไม่ระบุ เท่านั้นที่ถอยลงไปใช้ native พร้อมบรรทัดล็อกที่บอกเช่นนั้น แบ็กเอนด์จริงของเซสชันจะถูกรายงานกลับไปยังไคลเอนต์และติดป้าย เทอร์มินัลจึงอ้างว่าถูกปิดล้อมอยู่ทั้งที่รันบนโฮสต์ไม่ได้ วงจรชีวิต อิมเมจ และการเชื่อมต่อขาออกของ VM เองมีอยู่ในเอกสารอ้างอิงริก

Windows ไม่มีแซนด์บ็อกซ์เนทีฟ NativeSandboxAdapter.probe คืนค่า available: false บนทุกโฮสต์ที่ไม่ใช่ macOS หรือ Linux/WSL2 การสั่งงานเอเจนต์บน Windows จึงใช้ none แบ็กเอนด์ microvm แยกต่างหาก: มันถูกกั้นด้วยการตรวจหาบนรันไทม์ของ microVM และใช้กับเทอร์มินัลแบบโต้ตอบและริก ไม่ใช่กับการห่อ bash บนโฮสต์

การตรวจหาคือสิ่งที่กั้นการบังคับใช้ มันรันครั้งเดียว ตอนบูตเซิร์ฟเวอร์

โฮสต์ available บันทึก / คำแนะนำ
macOS true sandbox-exec (Seatbelt) — การแยกตัวระดับเนมสเปซ ไม่มีขอบเขตระดับเคอร์เนล
Linux หรือ WSL2 ที่มี bwrap และ socat true bubblewrap — การแยกตัวระดับเนมสเปซ ไม่มีขอบเขตระดับเคอร์เนล
Linux หรือ WSL2 ที่ขาดเครื่องมือตัวใดตัวหนึ่ง false requiresInstall: true พร้อมคำแนะนำการติดตั้งที่ระบุชื่อแพ็กเกจที่ขาดไป
แพลตฟอร์มอื่นใด false แซนด์บ็อกซ์เนทีฟไม่ถูกเสนอบนแพลตฟอร์มนี้

เซิร์ฟเวอร์บันทึกผลลัพธ์ไว้ในบรรทัดเริ่มต้นบรรทัดเดียว: agent sandbox ON บวกกับบันทึก, no OS-native agent sandbox on this host บวกกับคำแนะนำ หรือ agent sandbox DISABLED by --sandbox off GET /healthz ไม่มีฟิลด์แซนด์บ็อกซ์

--sandbox / CC_SERVER_SANDBOX รับค่า on (ค่าเริ่มต้น) หรือ off มันเป็นการเลือกปิด ไม่ใช่การเปิดใช้: on หมายถึง “ห่อเมื่อโฮสต์มีแบ็กเอนด์ให้ใช้” ไม่เคยหมายถึง “บังคับว่าต้องมี”

ใช้กลไกแซนด์บ็อกซ์ Seatbelt ของ Apple (ระบบเดียวกับที่ macOS App Sandbox ใช้)

โปรไฟล์ที่สร้างขึ้น ผ่อนปรนโดยค่าเริ่มต้น ((allow default)) แล้วเจาะออกเป็นข้อปฏิเสธ เพราะโปรไฟล์ที่ปฏิเสธโดยค่าเริ่มต้นทั้งหมดจะทำให้เชลล์แบบโต้ตอบใช้ไม่ได้ — มีการเรียก dyld, XPC และ Mach มากเกินกว่าจะแจกแจงได้ ผลในทางปฏิบัติคือเอเจนต์ยังอ่านโฮสต์ส่วนใหญ่ได้; เช็กเอาต์ที่ลงทะเบียนไว้ของคุณได้รับการปกป้องด้วยข้อปฏิเสธการเขียนและการเปลี่ยนชื่อที่ระบุชัดเจน ไม่ใช่ด้วยการมองไม่เห็น

  • การอ่านระบบไฟล์: ถูกปฏิเสธสำหรับเส้นทางลับ (~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh, ~/Library/Keychains และส่วนที่เหลือของชุดปฏิเสธการอ่าน)
  • การเขียนระบบไฟล์: รีเซ็ตเป็นปฏิเสธ แล้วจึงมีข้อยกเว้นแบบอนุญาตสำหรับพาธย่อยที่ระบุชัดเจน โดยเส้นทางที่ห้ามตายตัว (ไฟล์ rc ของเชลล์, .git/hooks, การกำหนดค่า Claude, เช็กเอาต์ที่ได้รับการปกป้อง) ถูกบล็อกแม้จะอยู่ภายในรูทที่เขียนได้ file-write-unlink ถูกปฏิเสธบนทุกเส้นทางที่ถูกปฏิเสธและบรรพบุรุษของมัน การเปลี่ยนชื่อจึงข้ามข้อปฏิเสธการเขียนไม่ได้
  • โปรเซส: process-exec ปฏิเสธไบนารีที่อันตรายเสมอ บวกกับการบล็อกการเรียกใช้ทุกอย่างจากไดเรกทอรีที่เขียนได้
  • เครือข่าย: จำกัดไว้ที่พอร์ตพร็อกซีในโปรเซสบวก DNS — ลูปแบ็กไม่ถูกอนุญาตแบบครอบคลุม และยูนิกซ์ซ็อกเก็ตไม่ถูกอนุญาต พร็อกซีบังคับใช้รายการที่อนุญาตระดับโดเมนต่อเซสชัน (ดูเครือข่ายขาออก)
  • การกำหนดค่า: โปรไฟล์ถูกสร้างต่อการเรียกใช้หนึ่งครั้งจาก SandboxSpec

สิ่งที่ต้องมี:

  • sandbox-exec (/usr/bin/sandbox-exec) ซึ่งมีอยู่ในการติดตั้ง macOS ทุกเครื่อง — ไม่ต้องมีเครื่องมือเพิ่มเติม

ใช้ bubblewrap (bwrap) เพื่อสร้างเมานต์เนมสเปซและเนมสเปซ PID

  • ระบบไฟล์: สร้างเมานต์เนมสเปซที่มองเห็นเฉพาะเส้นทางที่ถูกผูกเข้ามา
  • เครือข่าย: รันในเนมสเปซเครือข่ายที่ไม่ใช้ร่วมกัน; ทราฟฟิกไปถึงพร็อกซีของโฮสต์ผ่านบริดจ์ยูนิกซ์ซ็อกเก็ตของ socat
  • โปรเซส: การแยกตัวด้วยเนมสเปซ PID

สิ่งที่ต้องมี:

  • ติดตั้ง bubblewrap และ socat (sudo apt-get install bubblewrap socat บน Debian/Ubuntu)
  • การรองรับเนมสเปซของเคอร์เนล (มาตรฐานบน Linux สมัยใหม่)

Bubblewrap ซ่อนเส้นทางที่มีอยู่แล้วได้ แต่ห้ามไฟล์ .env, *.key หรือ *.pem ใหม่ ภายในบินด์ที่เขียนได้ไม่ได้ นโยบายการสั่งงานมาตรฐานต้องมีข้อปฏิเสธตามกลอบชื่อไฟล์เหล่านี้ การสั่งงานเอเจนต์แบบเนทีฟบน Linux/WSL2 ตอนนี้จึง ปิดเมื่อล้มเหลวแม้การตรวจหาจะพบ bubblewrap มันไม่รันด้วยนโยบายที่อ่อนกว่าอย่างเงียบ ๆ ต้องมีแบ็กเอนด์ที่มีกฎชื่อไฟล์ตอนสร้างจึงจะคืนการสั่งงานแบบเนทีฟบน Linux ได้ การปิดแซนด์บ็อกซ์เป็นการเลือกปิดโดยเจตนาให้ไม่ถูกกักขอบ ไม่ใช่ทางแก้ที่เทียบเท่ากัน

ไม่มีการแซนด์บ็อกซ์ โปรเซสรันบนโฮสต์โดยตรงด้วยการเข้าถึงระบบไฟล์ เครือข่าย และโปรเซสของโฮสต์ ACP และเครื่องมือ bash ของฮาร์เนสยังได้รับการชำระล้างสภาพแวดล้อมและนโยบายคำสั่ง; Claude Code ผ่าน NoSandboxAdapter สืบทอดสภาพแวดล้อมของโฮสต์ นี่คือสิ่งที่การสั่งงานเอเจนต์ถอยกลับไปใช้บนโฮสต์ที่ไม่มีแบ็กเอนด์ให้ใช้ หรือที่ตั้ง --sandbox off และเป็นค่าเริ่มต้นของเทอร์มินัลเมื่อไคลเอนต์ไม่ระบุแบ็กเอนด์ใด

เทอร์มินัลถูกแซนด์บ็อกซ์ตามเงื่อนไขของตัวเอง ไม่ใช่โดยการตรวจหาของการสั่งงาน defaultBackend ของ TerminalSessionService คือ none และ cc_server ไม่เขียนทับมัน เซสชันเทอร์มินัลจึงรันเชลล์ของโฮสต์ เว้นแต่ไคลเอนต์จะขอ native อย่างชัดเจน — แผงเทอร์มินัลของเดสก์ท็อปไม่ขอ

เมื่อมีการขอ native เชลล์จะถูกห่อผ่าน SandboxManager ภายใต้โปรไฟล์เทอร์มินัลแบบตายตัว: $HOME และไดเรกทอรีทำงานของเซสชันเขียนได้ เครือข่ายเปิด และ ~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh และ ~/Library/Keychains ถูกปฏิเสธการอ่าน โทเคนอ่านอย่างเดียวและเกตเวย์การพุชไม่ใช้ที่นี่ — นั่นเป็นเรื่องของการสั่งงาน หากการห่อไม่รองรับ เซสชันจะล้มเหลว แทนที่จะแสดงป้ายเนทีฟบน PTY ของเชลล์โฮสต์

เมื่อแซนด์บ็อกซ์ถูกเรียกใช้ การเข้าถึงเครือข่ายขาออกถูกบังคับใช้ด้วยพร็อกซีในโปรเซส ไม่ใช่ด้วยกฎไฟร์วอลล์ของเคอร์เนล บนโฮสต์ที่ไม่มีแบ็กเอนด์ให้ใช้ ทั้งหมดนี้ใช้ไม่ได้ และโปรเซสที่สปอว์นขึ้นจะเข้าถึงเครือข่ายได้เหมือนที่ผู้ใช้เซิร์ฟเวอร์จะทำ

  • รันไทม์แซนด์บ็อกซ์รันพร็อกซี HTTP/HTTPS และพร็อกซี SOCKS5 บน 127.0.0.1 ภายในโปรเซส cc_server, หนึ่งคู่ต่อเซสชันที่ถูกจำกัด (http_proxy.dart, socks_proxy.dart) การรันพร้อมกันไม่ใช้คู่ร่วมกัน
  • โปรเซสที่อยู่ในแซนด์บ็อกซ์ได้รับตัวแปรสภาพแวดล้อมของพร็อกซี (HTTP_PROXY/HTTPS_PROXY → พร็อกซี HTTP, ALL_PROXY → พร็อกซี SOCKS; GIT_SSH_COMMAND ทำให้ git-over-SSH ผ่านพร็อกซี SOCKS) บน Linux เนมสเปซเครือข่ายไม่ถูกใช้ร่วมกัน พร็อกซีจึงเป็นทางออกเดียว; บน macOS แซนด์บ็อกซ์ใช้ลูปแบ็กของโฮสต์ร่วมกันและคุยกับพร็อกซีโดยตรง
  • โฮสต์เป้าหมายของการเชื่อมต่อแต่ละครั้งถูกจับคู่กับรายการที่อนุญาตระดับโดเมนของเซสชัน; อะไรนอกนั้นถูกปฏิเสธ มีไวลด์การ์ดสองรูปแบบ แต่ละรูปแบบมี * ตัวเดียวพอดี: รูปแบบที่ขึ้นต้นด้วย *.example.com จับคู่ซับโดเมนใดก็ได้ (รวม a.b.example.com) แต่ไม่ใช่โดเมนหลักเอง; รูปแบบป้ายกลางอย่าง bedrock.*.amazonaws.com จับคู่พอดีหนึ่งเลเบล (bedrock.us-east-1.amazonaws.com ไม่ใช่ bedrock.evil.attacker.amazonaws.com)
  • รายการที่อนุญาตถูกแปลงค่าต่อเซสชันจากฐานที่คัดสรรไว้ (kBaselineAllowedDomains: ผู้ให้บริการ LLM, โฮสติ้ง Git, รีจิสทรีแพ็กเกจ) บวกโดเมน GitHub (kGithubDomains) บวก SandboxSpec.egressAllowlist เส้นทางการสั่งงานไม่เคยส่งรายการ egressAllowlist เพิ่มเติมมา ดังนั้นในทางปฏิบัติทุกการรันจะได้ฐานบวก GitHub
  • รายการที่ถูกปฏิเสธแบบตายตัว (kBaselineDeniedDomains: ปลายทางเมทาดาทาของคลาวด์, จุดรับส่งเทเลเมตรี) มาก่อนการอนุญาตทั้งหมด
  • networkEnabled: false ยังเริ่มพร็อกซีต่อเซสชัน แต่รายการที่อนุญาตว่างเปล่า ทุกการเชื่อมต่อขาออกจึงถูกปฏิเสธ การสั่งงานตั้งค่านี้ตอนเริ่มรันจากกฎ เข้าถึงเครือข่าย: ปฏิเสธจะปิดเครือข่าย อนุญาตและถามก่อนคงไว้ให้เปิด

สเปกที่ส่งให้ SandboxPort.launch:

ฟิลด์ ชนิด คำอธิบาย
sessionId String ตัวระบุเซสชันที่คงที่ (โดยทั่วไปคือไอดีสเปซ)
workspaceId String ไอดีเวิร์กสเปซเจ้าของ
agentId String? เอเจนต์ที่ผูกกับแซนด์บ็อกซ์อยู่ในขณะนี้ หากมี
bindMounts List<SandboxBindMount> การเมานต์โฮสต์→เกสต์; แซนด์บ็อกซ์มองเห็นเฉพาะเส้นทางโฮสต์ที่ระบุไว้ที่นี่
networkEnabled bool (ค่าเริ่มต้น true) ตั้งตอนสั่งงานจากกฎการส่งออกเครือข่าย (ปิดเฉพาะเมื่อตัดสินเป็นปฏิเสธ) เมื่อปิด พร็อกซีต่อเซสชันยังเริ่มทำงานแต่รายการที่อนุญาตว่างเปล่า ทุกการเชื่อมต่อขาออกจึงถูกปฏิเสธ
egressAllowlist List<String> (ค่าเริ่มต้น []) โดเมนขาออกที่อนุญาตเพิ่มเติม; รองรับไวลด์การ์ด (*.example.com บวกแบบป้ายกลางหนึ่งเลเบล) การสั่งงานไม่เคยใส่ค่านี้ การรันที่ถูกห่อจึงได้ฐานที่คัดสรรไว้บวกโดเมน GitHub
guestWorkdir String? ไดเรกทอรีทำงานเริ่มต้นภายในแซนด์บ็อกซ์; การเรียก exec แต่ละครั้งเขียนทับได้
mode Mode (ค่าเริ่มต้น chat) กำหนดกฎการเขียนระบบไฟล์: chat ทำให้การเมานต์แบบผูกเขียนได้; review, plan และ orchestrate เป็นอ่านอย่างเดียว
protectedPaths List<String> (ค่าเริ่มต้น []) เส้นทางโฮสต์ที่ต้องไม่เขียนได้ภายในแซนด์บ็อกซ์ในโหมดใดเลย
runnerStateDirs List<String> (ค่าเริ่มต้น []) ไดเรกทอรีโฮสต์ที่ตัวรันเก็บสถานะของตัวเองไว้ เขียนได้ในทุกโหมด (CLAUDE_CONFIG_DIR ที่ Control Center จัดการคือตัวปัจจุบัน)
execGrantRoots List<String> (ค่าเริ่มต้น []) โครงไดเรกทอรีที่ผู้ปฏิบัติงานอนุมัติ ซึ่งเปิดการบล็อก exec ในไดเรกทอรีที่เขียนได้กลับคืนบน macOS Linux ไม่สนใจค่าเหล่านี้ — bwrap ไม่มีการบล็อก exec ของ $HOME ให้เปิดกลับคืน

protectedPaths เก็บเช็กเอาต์รีโปที่ลงทะเบียนต้นฉบับ ซึ่งแปลงค่าต่อการสั่งงานหนึ่งครั้งจากรีโปของเวิร์กสเปซ เอเจนต์ทำงานใน worktree แบบ copy-on-write ต่อการสนทนาเท่านั้น ต้นฉบับจึงต้องไม่เขียนได้จากภายในแซนด์บ็อกซ์ — บน macOS การอนุญาตเขียน $HOME แบบครอบคลุมของ Seatbelt จะทำให้มันยังเขียนได้ผ่าน bash พวกมันเข้าร่วมชุดปฏิเสธการเขียนข้อมูลลับในทุกโหมด รวมถึง chat

เส้นทางที่ถูกปฏิเสธการอ่านและปฏิเสธการเขียนทุกเส้นทางถูกปล่อยออกมาทั้งในรูปตามตัวอักษรและรูปที่แปลงซิมลิงก์แล้ว เคอร์เนลจับคู่กฎปฏิเสธกับเส้นทางที่แปลงค่าแล้ว กฎที่เขียนกับเส้นทางที่เป็นซิมลิงก์จึงไม่เคยทำงาน — และบน macOS นั่นคือกรณีปกติมากกว่ากรณีพิเศษ เพราะ /tmp แปลงค่าเป็น /private/tmp และ /var/folders/… เป็น /private/var/folders/… รายการที่มี * หรือ ? ถูกถือเป็นกลอบและปล่อยไว้ตามเดิม; เส้นทางที่แปลงค่าไม่ได้จะถูกเก็บตามที่เขียนไว้

ฟิลด์ ชนิด คำอธิบาย
hostPath String เส้นทางแบบสัมบูรณ์บนโฮสต์
guestPath String เส้นทางภายในแซนด์บ็อกซ์ (กับแซนด์บ็อกซ์เนทีฟ มันเท่ากับ hostPath; เก็บไว้เพื่อความสมมาตรของอะแดปเตอร์)
readOnly bool (ค่าเริ่มต้น false) เมานต์แบบอ่านอย่างเดียวเมื่อเป็น true

คืนค่าจาก SandboxPort.launch:

ฟิลด์ ชนิด คำอธิบาย
sessionId String ตัวระบุเซสชัน
backend SandboxBackend แบ็กเอนด์ใดกำลังทำงานอยู่
state SandboxState สถานะวงจรชีวิต
error Object? ถูกตั้งค่าเมื่อ state เป็น error
details Map<String, Object?> การบันทึกภายในของอะแดปเตอร์ (ไดเรกทอรีทำงาน, PID, เส้นทางโปรไฟล์ ฯลฯ)
สถานะ คำอธิบาย
created สร้างสเปกแล้วแต่ VM ยังไม่เริ่ม
warm กำลังทำงานและพร้อมรับคำสั่ง exec
active กำลังรันคำสั่งอยู่
suspended หยุดชั่วคราว / เช็กพอยต์ลงดิสก์เพื่อประหยัด RAM
destroyed รื้อถอนแล้ว; แฮนเดิลใช้ไม่ได้อีกต่อไป
error การเรียกใช้ล้มเหลว — ดู SandboxHandle.error
ชนิดเหตุการณ์ คำอธิบาย
stdout เอาต์พุตมาตรฐานจากแซนด์บ็อกซ์
stderr ข้อผิดพลาดมาตรฐานจากแซนด์บ็อกซ์
exit โปรเซสแซนด์บ็อกซ์ออกแล้ว (พก exitCode มาด้วย)
starting แซนด์บ็อกซ์กำลังเริ่มทำงาน
ready แซนด์บ็อกซ์พร้อมรับการเรียก exec
killed โปรเซสถูกลงมือฆ่าโดยโฮสต์ (OOM, หมดเวลา, หยุดด้วยมือ)
violation นโยบายแซนด์บ็อกซ์ถูกละเมิด (พก SandboxViolation มาด้วย)
ฟิลด์ ชนิด คำอธิบาย
action String คำกริยาที่ถูกปฏิเสธ (เช่น file-read*, network-outbound)
target String เส้นทางหรือโฮสต์ที่เป็นเป้าหมายของการดำเนินการที่ถูกปฏิเสธ
raw String? บรรทัดล็อกดิบ มีประโยชน์สำหรับดีบัก