رفتن به محتوا

سندباکس و امنیت

یک عامل به‌جای شما فایل‌ها را می‌خواند، فرمان‌های پوسته اجرا می‌کند و APIهای بیرونی را فراخوانی می‌کند. شکستی که برایتان مهم است به‌ندرت بدخواهی است — عاملی است که مسیری را اشتباه می‌خواند و checkout کاری شما را بازنویسی می‌کند، یا عاملی که شاخه‌ای را push می‌کند که هنوز بازبینی‌اش را تمام نکرده‌اید. پس پرسشی که محصول باید پاسخ دهد این نیست که «آیا عامل قابل اعتماد است» بلکه «وقتی اشتباه می‌کند، شعاع اثر چقدر است».

Control Center به این پرسش لایه‌لایه پاسخ می‌دهد و لایه‌ها عمداً مستقل‌اند. اگر یکی نباشد، بقیه هنوز برجایند. این مهم است، چون بیرونی‌ترین لایه — سندباکس سیستم‌عامل — تنها لایهٔ مشروط است: به این بستگی دارد که میزبان بک‌اندی عرضه کند و روی Windows هیچ میزبانی عرضه نمی‌کند.

چه چیزی واقعاً یک عامل را محدود می‌کند

بخش با عنوان “چه چیزی واقعاً یک عامل را محدود می‌کند”

پنج چیز اجرای یک عامل را روی هر میزبان محدود می‌کند، با سندباکس یا بدون آن. هیچ‌کدام‌شان مرز کرنل نیستند؛ همه‌شان واقعی‌اند. جایی که میزبان بک‌اندی عرضه می‌کند، سندباکس سیستم‌عامل بالای آن‌ها می‌نشیند و بخش «سندباکس چه چیزی اضافه می‌کند و کجا متوقف می‌شود» در پایین پوشش می‌دهد که آن چه چیزی را تغییر می‌دهد.

worktree کپی-در-نوشتن خودش. هر گفتگو checkout خودش را از هر مخزن لینک‌شده فراهم می‌کند، که به‌جای کپی بایت‌به‌بایت، از طریق rift بومیِ همراه به‌صورت کپی-در-نوشتن clone می‌شود، پس ایزوله‌سازی به‌قدری ارزان است که پیش‌فرض باشد. مخزن ثبت‌شده‌ای که شما به فضای کاری افزوده‌اید همان دایرکتوری‌ای نیست که عامل در آن کار می‌کند — دو گفتگو که به یک مخزن دست می‌زنند نمی‌توانند ویرایش‌های همدیگر را ببینند و هیچ‌کدام هم checkout شما را ویرایش نمی‌کند.

دانه‌بندی‌اش ارزش دقت دارد: worktree به‌ازای هر گفتگو است، نه به‌ازای هر عامل. دو عامل در یک گفتگو دایرکتوری‌های کاری overlay متمایزی می‌گیرند که repos/ آن گفتگو را به‌اشتراک می‌گذارند — که همین نقطهٔ ماجراست، چون دارند روی یک تغییر همکاری می‌کنند — در حالی که دایرکتوری‌های خصوصی خودِ عامل‌ها جدا می‌مانند. کپی-در-نوشتن هرجا که rift عرضه می‌شود تنها بک‌اند است: فایل‌سیستمی که نمی‌تواند reflink کند، یا دایرکتوری داده‌ای روی والیومی متفاوت از مخزن، فراهم‌آوری را شکست می‌دهد تا اینکه به حالت دیگری برگردد. Windows استثناست — آنجا هیچ بک‌اند کپی-در-نوشتنی وجود ندارد، پس git worktree بک‌اند است، نه یک تنزل خاموش.

محدودسازی مسیر در ابزارهای فایلی هارنس داخلی. ابزارهای read، write، edit، apply_patch، search، find و search_files هارنس هر مسیری را نسبت به دایرکتوری کاری اجرا به‌علاوهٔ ریشه‌های مشترک حل می‌کنند — دایرکتوری repos/ گفتگو، جایی که worktreeها زندگی می‌کنند، و دایرکتوری پیوست‌های فضا وقتی وجود دارد — و هر چیزی را که بیرون بیفتد رد می‌کنند. مسیرهای مطلق فقط اگر از قبل داخل بوده باشند پذیرفته می‌شوند. یک symlink داخل worktree که به بیرونش اشاره کند هم حل می‌شود و رد. به همین دلیل است که یک عامل حتی بدون هیچ سندباکسی در تصویر هم نمی‌تواند در ~/.ssh بنویسد.

سطح ابزارِ حالت. حالت یک گفتگو تعیین می‌کند اصلاً کدام ابزارها برای اجرا مادی شده‌اند. در حالت‌های فقط‌خواندنی (plan، review، orchestrate) ابزارهای تغییردهنده به‌سادگی در رجیستری نیستند و سیاست فرمان پوسته برای آن حالت‌ها هر فرمان تغییردهنده‌ای را بی‌تأمل رد می‌کند — git commit، git push، npm install، docker و بقیه — به‌جای اینکه برایشان پرسش کند. ابزاری که حاضر نیست نمی‌تواند فراخوانی شود، که تضمینی قوی‌تر از ابزاری است که حاضر است و دروازه‌بندی می‌شود.

گاردریل‌های یکپارچهٔ کنش. هر ابزار تغییردهنده کلاس‌های اثری را که می‌تواند تولید کند اعلام می‌کند و موتور سیاست هر یک را پیش از اجرای فراخوانی به اجازه، پرسش، یا رد حل می‌کند. یک prompt بدون هیچ تأییدکنندهٔ متصل به رد حل می‌شود — پرسشی که هیچ‌کس نمی‌تواند پاسخ دهد هرگز بله نمی‌شود. برای مدل حل‌وفصل به گاردریل‌ها نگاه کنید.

اعتبارنامه‌های کارگزاری‌شده و محیط پاک‌سازی‌شده. اعتبارنامه‌ها به‌ازای هر راه‌اندازی صادر می‌شوند و در پایان اجرا باطل می‌شوند؛ توکن GitHub که یک اجرا در دست دارد می‌تواند بخواند اما نمی‌تواند پوش کند — «پوش‌ها از Control Center می‌گذرند» را در ادامه ببینید. ACP، bash هارنس و Claude Code زیر پوشش بومی محیطی می‌گیرند که پیش از شروع فرایند از بردارهای تزریق پیونددهندهٔ دینامیک و مفسر پاک شده است (LD_PRELOAD، DYLD_INSERT_LIBRARIES، NODE_OPTIONS، GIT_SSH_COMMAND و همتایانشان). Claude Code از مسیر NoSandboxAdapter محیط میزبان را به ارث می‌برد.

ابزارهای فایلی هارنس درون فرایند سرور اجرا می‌شوند، نه در یک فرایند فرزند، پس هیچ پروفایل سندباکسی نمی‌تواند آن‌ها را محدود کند، حتی اگر یکی به آن‌ها وصل شود — فقط bash از مسیر اجراکنندهٔ فرمان سندباکس‌شده می‌گذرد. محدودسازی آن‌ها همان بررسی مسیر توصیف‌شده در بالاست و بنابراین آن بررسی بارِ اصلی را حمل می‌کند نه اینکه خط دفاعی دومی باشد.

ابزار bash جای دیگری است که باید دقیق بود. فرمان‌هایش در برابر سیاست فرمان حالت ارزیابی و سپس با محیطی پاک‌سازی‌شده و worktree به‌عنوان دایرکتوری کاری‌اش spawn می‌شوند. جایی که بک‌اندی وجود دارد آن spawn پیچیده می‌شود، پس سندباکس همان چیزی است که جلوی یک فرمان پوسته را برای نوشتن بیرون از worktree می‌گیرد؛ جایی که هیچ بک‌اندی نیست — Windows، یا Linux بدون bwrap و socat — سیاست فرمان تنها چیزی است که میان فرمان و بقیهٔ دیسک قرار دارد. در حالت chat سیاست یک فهرست ثابت را سخت رد می‌کند (ارتقای امتیاز، تخریب سیستم و دیسک، فرار از کانتینر و namespace) و برای تغییرهای وضعیت راه دور (git push، gh pr create، انتشار و نصب پکیج‌ها) پرسش می‌کند. هر چیزی که در هیچ‌کدام از دو فهرست نباشد اجرا می‌شود. پایپ‌لاین‌ها، زنجیره‌ها و زیرپوسته‌ها تجزیه می‌شوند و هر زیرفرمان جداگانه ارزیابی می‌شود، و محدودکننده‌ترین تصمیم برنده است.

Claude Code، هنگامی که به‌عنوان آداپتور استفاده می‌شود، همان‌جایی است که سندباکس بیشترین وزن را حمل می‌کند. Control Center آن را با --dangerously-skip-permissions راه‌اندازی می‌کند، چون یک claude -p غیرتعاملی در غیر این صورت برای همیشه پشت پرامپت تأیید خودش گیر می‌کند. فراخوانی‌های خواندن، نوشتن و ویرایش آن درون فرایند خودش رخ می‌دهند و Control Center هرگز آن‌ها را نمی‌بیند. فراخوانی‌های Bash آن پیش از اجرا به هوکی روی سرور می‌رسند که خط فرمان را دسته‌بندی می‌کند و از قاعدهٔ متناظر می‌پرسد؛ ابزارهای mcp__*ی که خود Control Center سرو می‌کند طبق معمول از دروازه می‌گذرند. جایی که بک‌اندی وجود دارد، آن فراخوانی‌های نادیده هنوز درون پوشش سندباکس رخ می‌دهند، پس سیستم‌عامل مرز پیرامون آن‌هاست. جایی که هیچ بک‌اندی نیست، هیچ‌چیز نیست. هر آداپتور این را در ماتریس اجرای خودش بیان می‌کند — ماتریس صداقت آداپتور را ببینید.

هیچ پرچم مجوز به‌ازای هر عامل وجود ندارد. اینکه یک عامل بتواند کامیت بسازد، پوش کند، pull request باز کند، در یک ردیاب بنویسد یا به شبکه دسترسی داشته باشد را گاردریل‌ها تعیین می‌کنند — اجازه، ابتدا بپرس یا رد — و همین قواعد صرف‌نظر از اینکه کدام آداپتور عامل را اجرا کند اعمال می‌شوند.

پوش کنشی است که باید حتی در برابر عاملی که می‌کوشد قاعده را دور بزند پابرجا بماند، پس با آنچه عامل در دست دارد اعمال می‌شود، نه با آنچه اجرا می‌کند. با یک GitHub App پیکربندی‌شده، اجرایی که در یک worktree گیت‌هاب است یک توکن نصب محدود به یک مخزن می‌گیرد که می‌تواند محتوا را بخواند و با pull requestها کار کند، اما نمی‌تواند پوش کند. پیکربندی git اجرا هر نشانی پوش GitHub (https://github.com/، git@github.com:، ssh://git@github.com/) را به دروازهٔ اجرای عامل بازنویسی می‌کند — یک نقطهٔ پایانی loopback روی cc_server. دروازه می‌خواند که پوش کدام refها را به‌روز می‌کند، از قاعدهٔ پوش به یک ریموت می‌پرسد — پس قواعد محدود به شاخه مانند «رد پوش به main» هم اعمال می‌شوند — و تنها پس از آن پوش را با یک توکن نوشتنی که برای همین کار صادر می‌کند و هرگز به عامل نمی‌دهد به GitHub می‌فرستد. پوش ردشده در خروجی خود git ظاهر می‌شود:

! [remote rejected] main -> main (Control Center policy: …)

دروازه پوش‌ها را فقط به مخزن خود worktree یا به یک مخزن GitHub پیوندخورده با فضای کاری می‌فرستد. fetchها مستقیماً با توکن فقط‌خواندنی به GitHub می‌رسند.

دیگر فرمان‌های پوسته با دروازه‌ای نرم‌تر روبه‌رو می‌شوند. ابزار bash هارنس و ابزار Bash در Claude Code از طریق هوک، هر خط فرمان را دسته‌بندی می‌کنند و از قاعده‌ای که به آن تعلق دارد می‌پرسند: git commit، merge، rebase، cherry-pick، revert و am به ایجاد یک کامیت؛ gh pr create به باز کردن یک pull request؛ gh pr merge، review، comment و close به انتشار یک بازبینی یا ادغام؛ gh issue create، edit، comment و close به نوشتن در یک ردیاب خارجی؛ نصب بسته‌ها به نصب یک بسته؛ و curl و wget به دسترسی به شبکه. این شکل‌های معمول یک کنش را تشخیص می‌دهد، و عاملی که مصمم به پنهان کردن آن باشد (eval، یک فایل اسکریپت، فراخوانی مستقیم API) می‌تواند چنین کند. پوش به این تکیه ندارد.

هیچ کلید API تیکتینگی در سندباکس گذاشته نمی‌شود. عامل‌ها تیکت‌ها را از طریق ابزارهای MCP در Control Center می‌نویسند که از قاعدهٔ نوشتن در یک ردیاب خارجی می‌پرسند.

قاعدهٔ دسترسی به شبکه یک‌بار، در شروع اجرا، خوانده می‌شود. شبکهٔ سندباکس فقط روشن یا خاموش است، پس رد آن را خاموش می‌کند و اجازه و ابتدا بپرس آن را روشن نگه می‌دارند؛ ابزارهای web_fetch و web_search هارنس همچنان در هر فراخوانی می‌پرسند. اعتبارنامه‌ها هنگامی که اجرا برچیده می‌شود باطل می‌شوند، همراه با هر توکن نوشتنی که دروازه صادر کرده، پس یک دستگیره از عمر اجرا بیرون نمی‌ماند.

سندباکس چه چیزی اضافه می‌کند و کجا متوقف می‌شود

بخش با عنوان “سندباکس چه چیزی اضافه می‌کند و کجا متوقف می‌شود”

enum SandboxBackend سه مقدار دارد — native (Seatbelt روی macOS، bubblewrap روی Linux و WSL2)، none و microvm. پوشش فرمان عامل فقط از دو مورد اول استفاده می‌کند: microvm بک‌اند VM محفظه‌ای برای ترمینال‌های تعاملی و ریگ‌هاست، نه ارتقای خاموش یک پوشش bash. فهمیدن شکل بومی توضیح می‌دهد که کجا یکی وصل است چه چیزی به‌دست می‌آورید و چه چیزی را هرگز قرار نبود به شما بدهد.

cc_server میزبان را یک‌بار در بوت بررسی می‌کند و جایی که بک‌اندی پاسخ دهد هر دو درز مسیر اعزام را تغذیه می‌کند، چون ترنسپورت‌ها یکی را به‌اشتراک نمی‌گذارند. اولی (SandboxPort) CLI مربوط به Claude Code را می‌پیچد. دومی (SandboxManager) آداپتورهای ACP و ابزار bash هارنس داخلی را می‌پیچد. تغذیهٔ فقط یکی بقیه را بدون پوشش spawn می‌کرد، که همان چیزی است که قبلاً رخ می‌داد. روی میزبانی بدون بک‌اند، یا زیر --sandbox off، همه‌شان بدون پوشش spawn می‌شوند و سرور روی خط راه‌اندازی‌اش می‌گوید در کدام حالت هستید. ACP و bash هارنس هنوز محیط را پاک‌سازی می‌کنند؛ Claude Code از مسیر NoSandboxAdapter محیط میزبان را به ارث می‌برد.

یک سطح به‌عمد کنار می‌کشد: ترمینال میزبانی‌شده روی سرور یک آرگومان بک‌اند به‌ازای هر نشست می‌پذیرد و cc_server آن را با پیش‌فرض پوستهٔ میزبان می‌سازد، پس ترمینالی که از برنامه باز می‌کنید بدون سندباکس اجرا می‌شود مگر اینکه کلاینتی صراحتاً native را نام ببرد. آن یک پوستهٔ واقعی روی میزبان سرور است، نه یک اجرای عامل.

روی Linux، bubblewrap یک mount namespace می‌سازد، پس عامل واقعاً فقط چیزی را می‌بیند که به آن bind شده است. روی macOS، sandbox-exec پروفایل Seatbelt اعمال می‌کند که به‌طور پیش‌فرض مجاز است — یک پروفایل کاملاً رد-به‌طور-پیش‌فرض پوستهٔ تعاملی را غیرقابل‌استفاده می‌کند، چون تعداد فراخوانی‌های dyld، XPC و Mach برای برشمردن عملی نیست. پس پروفایل macOS از (allow default) شروع می‌کند و ردها را می‌تراشد: خواندن مسیرهای محرمانه، سیاست نوشتن که به رد بازنشانی شده با اجازه‌های صریح رویش، مسدودسازی unlink روی هر مسیر ردشده و اجدادش تا یک rename نتواند از یک رد-نوشتن بگذرد، ردهای exec برای باینری‌های همیشه‌خطرناک و برای هر چیزی که به دایرکتوری قابل‌نوشتن کپی شده و شبکهٔ محدود به پورت‌های پروکسی loopback به‌علاوهٔ DNS.

نتیجهٔ عملی: روی Linux دیدِ عامل از فایل‌سیستم باریک می‌شود؛ روی macOS عامل هنوز می‌تواند بیشتر میزبان را بخواند و checkoutهای ثبت‌شدهٔ شما با ردهای صریح نوشتن و rename محافظت می‌شوند، نه با نامرئی‌بودن. هر دو ایزوله‌سازی در سطح namespaceاند، نه یک مرز امنیتی کرنل — خود بک‌اندها این را در یادداشت‌های توانایی خودشان می‌گویند.

مجموعهٔ bind-mount همان‌جایی است که مرز میان‌عامل‌ها کشیده می‌شود. اعزام overlay (Claude Code و ACP) حداکثر چهار مسیر را mount می‌کند که به‌ازای هر اجرا حل می‌شوند: دایرکتوری کاری overlay عامل به‌صورت خواندن-نوشتن، دایرکتوری پیکربندی عامل به‌صورت فقط‌خواندنی، دایرکتوری repos/ مشترک گفتگو به‌صورت خواندن-نوشتن وقتی وجود دارد، و دایرکتوری پیوست‌های فضا به‌صورت فقط‌خواندنی وقتی وجود دارد. دایرکتوری یک عامل همتا هرگز mount نمی‌شود. پوشش bash هارنس باریک‌تر است: فقط دایرکتوری کاری اجرا را bind می‌کند. این همان شکلی است که بررسی مسیر درون‌پردازشی هارنس در نرم‌افزار اعمال می‌کند، پس آن دو هم‌سازند — اما فقط بررسی مسیر ابزارهای فایلی خود هارنس را پوشش می‌دهد، که هرگز به یک mount namespace نمی‌رسند.

checkoutهای ثبت‌شدهٔ شما بالای آن به مجموعهٔ رد-نوشتن می‌پیوندند، در هر حالت و روی هر سه مسیر پوشش‌داده‌شده (Claude Code، ACP، bash هارنس)، و هر قاعده در هر دو صورت نوشتاری‌اش منتشر می‌شود: صورت تحت‌اللفظی و صورت حل‌شدهٔ symlink. صورت دوم یک تجمل نیست: کرنل یک قاعدهٔ رد را در برابر مسیر حل‌شده تطبیق می‌دهد و macOS حل‌کردن را حالت رایج می‌کند نه حالت نامتعارف — /tmp در واقع /private/tmp است، /var/folders/… در واقع /private/var/folders/… است — پس checkoutی که از طریق هر کدام از این‌ها رسیده باشد قابل‌نوشتن می‌بود در حالی که پروفایل ظاهراً آن را ممنوع کرده بود.

شبکه، زیر سندباکس، یک فهرست مجاز دامنه به‌ازای هر نشست است که توسط پروکسی‌های loopback درون‌فرایندی اعمال می‌شود. خروج به‌طور پیش‌فرض رد است: با خاموش بودن شبکه، پروکسی‌ها هنوز شروع می‌شوند اما فهرست مجازشان خالی است، پس هر اتصال خروجی رد می‌شود. با روشن بودنش، حل‌وفصل‌کننده فهرست مجاز را از یک خط پایهٔ گزینش‌شده می‌سازد — ارائه‌دهندگان API مدل زبانی، میزبانی git، رجیستری‌های پکیج، فهرست مدل‌ها — به‌علاوهٔ دامنه‌های GitHub، و میزبان هدف هر اتصال را با آن تطبیق می‌دهد. وایلدکاردها کار می‌کنند (*.example.com با زیردامنه‌ها تطبیق می‌یابد، نه با دامنهٔ ریشه). یک فهرست همیشه-ردشدهٔ جداگانه بر فهرست مجاز پیروز می‌شود و نقاط پایانی فرادادهٔ ابری و مقصدهای تلمتری را پوشش می‌دهد. چیزی که قابل پیکربندی نیست خودِ فهرست است: spec یک فیلد egressAllowlist برای دامنه‌های اضافی حمل می‌کند، اما هیچ‌چیز در مسیر اعزام آن را پر نمی‌کند، پس در عمل هر اجرا خط پایه به‌علاوهٔ GitHub را می‌گیرد و نه چیزی بیشتر.

ردها، روی macOS، قابل مشاهده‌اند: یک پایشگر لاگ سیستم را برای ردهای Sandbox می‌خواند، نویز شناخته‌شده را فیلتر می‌کند، طوفان‌های تلاش مجدد را یکتاسازی می‌کند و هر یک را به‌صورت یک SandboxViolation که نام می‌برد چه چیزی تلاش شده و چه چیزی مسدود شده، نمایان می‌سازد. روی Linux مسیر پوشش stderr را برای EPERM، Operation not permitted و Permission denied تجزیه می‌کند و همان نوع رویداد را منتشر می‌کند — هیچ خوانشی از لاگ سیستم وجود ندارد. آن‌ها روی جریان رویداد خودِ اجرا می‌رسند، پس یک syscall ردشده در رونوشت ظاهر می‌شود نه فقط در یک لاگ. این تلمتری فقط تا زمانی وجود دارد که یک سندباکس بومی واقعاً فرایند را می‌پیچد — روی میزبانی بدون بک‌اند چیزی برای مشاهده نیست. همچنین به گاردریل‌ها ربطی ندارد: یک تخلف از یک syscall ردشده می‌آید، هرگز از جستجوی یک قاعده.

تأییدها از گاردریل‌ها می‌آیند، نه از سندباکس

بخش با عنوان “تأییدها از گاردریل‌ها می‌آیند، نه از سندباکس”

سندباکس هیچ مسیر تأییدی ندارد. راه‌اندازی می‌کند، اجرا می‌کند، رویداد منتشر می‌کند و برچیده می‌شود؛ جایی در آن نیست که یک انسان به پرسشی پاسخ دهد. وقتی در یک فضا تأییدی می‌بینید — این push را تأیید کن، این حذف را تأیید کن — آن را موتور گاردریل (یا سیاست فرمان هارنس) پیش از آنکه کنش اصلاً به زمان اجرا برسد مطرح کرده است.

این ترتیب عامدانه است. تأییدی که پس از syscall رخ دهد یک رکورد حسابرسی می‌بود، نه یک دروازه. همچنین به همین دلیل است که قاعدهٔ شکستِ بسته در گاردریل‌ها زندگی می‌کند: با نبود تأییدکنندهٔ متصل، تصمیم پیش از آنکه چیزی اجرا شود گرفته می‌شود و رد است.

عیب‌یابی یک عامل تنها جایی است که می‌گوید چه چیزی را می‌توان دستی بررسی کرد.