Bỏ qua để đến nội dung

Rào chắn — một chính sách cho mọi hành động gây thay đổi

Agent trong Control Center có thể xóa tệp, push nhánh, xuất bản pull request và gọi API bên ngoài. Mỗi việc trong số đó là một loại rủi ro khác nhau, và trong một thời gian dài chỉ một trong số đó — lệnh shell — có chính sách. Rào chắn ra đời để trả lời thống nhất một câu hỏi, cho mọi hành động gây thay đổi mà agent có thể thực hiện, bất kể runtime hay công cụ nào đề xuất nó: việc này có được phép không, có cần phê duyệt không, hay bị từ chối?

Chỉ có đúng một cỗ máy chính sách. Nó được hỏi ý kiến tại mọi điểm mà một hành động có thể rời khỏi hệ thống: sổ đăng ký công cụ của harness tích hợp, cổng điều phối MCP phía máy chủ và các thao tác thay đổi repo. Nhờ vậy, một quy tắc viết một lần sẽ áp dụng cho cả runtime tích hợp lẫn các CLI agent bên ngoài — không có con đường thứ hai nào lặng lẽ vòng qua nó. Chính sách lệnh chỉ dành cho bash trước đây vẫn hoạt động: một quy tắc có thể khớp theo tiền tố lệnh thay vì theo lớp hành động, và hai dạng này cùng tồn tại trong cùng một kho.

Tuy nhiên, cỗ máy này không thay thế sandbox của hệ điều hành, và sandbox cũng không thay thế nó. Chúng trả lời những câu hỏi khác nhau: rào chắn quyết định liệu một lời gọi mà Control Center nhìn thấy có được phép xảy ra hay không, còn sandbox giới hạn phạm vi mà tiến trình sinh ra có thể vươn tới. cc_server áp dụng sandbox khi máy chủ có sẵn một backend — nhưng Windows không có backend nào và Linux cần bwrap cùng socat. Trên những máy chủ đó, rào chắn là lớp thực thi ngoài cùng chứ không phải một chính sách ngồi trên một cái sàn, điều đáng biết khi bạn quyết định nới rộng đến mức nào.

ActionClass: một hệ phân loại đóng có chủ đích

Phần tiêu đề “ActionClass: một hệ phân loại đóng có chủ đích”

Mỗi công cụ gây thay đổi đều khai báo mình thuộc những ActionClass nào, trong một tập đóng gồm mười ba lớp hiệu ứng. Năm lớp trong số đó hỏi trước ngay từ đầu; số còn lại cho phép, và chỉ một quy tắc mới thay đổi được điều đó.

Lớp Mặc định tích hợp Nội dung bao phủ
fileDelete hỏi trước Xóa một tệp (một thao tác ghi mang hình dạng xóa, rm)
gitPush hỏi trước Push lên một remote git
prCreate hỏi trước Tạo một pull request
prPublish hỏi trước Xuất bản một đánh giá, merge một PR
vendorSyncWrite hỏi trước Ghi vào một trình theo dõi phiếu bên ngoài (Linear, Jira, GitHub sync)
fileWriteOutsideWorktree cho phép Ghi một tệp ra ngoài worktree cách ly
gitCommit cho phép Tạo một commit git
networkEgress cho phép Lưu lượng mạng ra ngoài (tải trang web, HTTP tùy ý)
secretAccess cho phép Đọc một secret hoặc thông tin xác thực
packageInstall cho phép Cài một gói (npm, pip, pub, brew)
processSpawn cho phép Khởi tạo một tiến trình (shell và mọi thứ gọi ra shell)
workspaceMutation cho phép Thay đổi cấu trúc không gian làm việc (repo, space, agent)
enclosureControl cho phép Điều khiển một enclosure (rig): khởi động máy ảo, gửi đầu vào cho nó

Tám lớp mặc định cho phép được chọn dựa trên giả định rằng sàn sandbox hiện diện bên dưới chúng. Điều đó đúng khi máy chủ có sẵn backend, và không đúng trên Windows hay trên một máy Linux thiếu bwrap và socat — nên nếu bạn muốn có cổng kiểm soát cho fileWriteOutsideWorktree hay packageInstall ở đó, bạn phải tự viết một quy tắc.

Tập hợp này được đóng lại có chủ đích. Một công cụ gây thay đổi mới không khai báo lớp nào sẽ trượt bài kiểm thử ratchet, nên hệ phân loại chỉ có thể lớn lên bằng một quyết định tường minh và có lập luận — “sự phình to của hệ phân loại chính là cái chết của tính năng này.” Mười ba lớp là đủ để diễn đạt những chính sách mà người ta thực sự viết (không bao giờ push mà không hỏi, không bao giờ xóa ra ngoài worktree, luôn cho phép đọc) và đủ ít để giao diện cài đặt vẫn dễ hiểu.

Một quy tắc ánh xạ (scope, ActionClass | command prefix) → allow | prompt | deny. Các phạm vi lồng nhau từ cụ thể nhất đến ít cụ thể nhất:

space > agent > không gian làm việc > preset chế độ > mặc định tích hợp

Việc phân giải theo nguyên tắc cụ thể trước: phạm vi đầu tiên có quy tắc khớp sẽ quyết định và quá trình phân giải dừng ngay tại đó. Mức hạn chế nhất chỉ dùng để phân định khi hòa — giữa hai quy tắc cùng độ cụ thể trong một phạm vi, và khi kết hợp nhiều lớp mà một hành động khai báo. Trong một phạm vi, quy tắc lệnh khớp theo tiền tố lệnh dài nhất. Cho phép không bao giờ thắng từ chối ở bất kỳ đâu.

Hai quyết định mang bảo đảm cứng:

  • Phê duyệt theo hướng đóng khi lỗi. Một quyết định prompt mà không có người phê duyệt nào được kết nối sẽ được phân giải thành từ chối. Một câu hỏi không được trả lời không bao giờ là một câu đồng ý.
  • Kết hợp theo hướng hạn chế nhất. Một hành động trải qua nhiều lớp sẽ được kết hợp theo hướng hạn chế nhất; khi nhiều lớp cùng hỏi trước, một lần xác nhận sẽ liệt kê tất cả.

Cố ý không có chốt ghi nhớ theo từng lượt. Mỗi lời gọi công cụ đều phân giải lại chính sách từ đầu, nên việc hỏi lặp lại cho cùng một hành động chỉ bị giới hạn bởi chính lựa chọn “ghi nhớ quyết định này” của người vận hành, chứ không bởi bất cứ thứ gì thuộc phạm vi theo lượt.

Một quy tắc có thể ràng buộc cả ĐỐI SỐ của một hành động, chứ không chỉ động từ của nó. “Được push” và “được push tới feature/*” là hai khẳng định khác nhau, và chỉ khẳng định thứ hai mới là một cơ chế kiểm soát — một cổng năng lực chỉ cho phép mỗi động từ chính là hình dạng đằng sau phần lớn các sự cố agent được công bố.

Một ràng buộc có thể nêu tên:

  • đường dẫn — các mẫu glob (repos/**, !**/.env)
  • ref — các git ref, với phép phủ định ! (['**', '!main'] đọc là “mọi nhánh trừ main”)
  • host — các đích mạng, với ký tự đại diện tên miền con (*.internal)
  • lệnh — các tiền tố lệnh, khớp trên ranh giới từ
  • mức trần — maxCount / maxCents, nhờ đó có thể diễn đạt “xóa tối đa 50 tệp”

Ba tính chất nảy sinh từ việc giữ ngữ pháp đóng và không có vòng lặp, và cả ba chính là mục đích: mọi quy tắc đều hiển thị thành một câu bạn đọc lại được, mọi lần từ chối đều nêu được ràng buộc đã khớp, và toàn bộ chính sách có thể liệt kê được — bạn thấy được agent được phép làm gì trước khi nó chạy.

Ba quy tắc đáng lưu tâm:

  • Một quy tắc có ràng buộc thì cụ thể hơn một quy tắc không ràng buộc ở cùng phạm vi, nên “từ chối push tới main” thắng “cho phép push” mà không ai phải nghĩ về thứ tự.
  • Một quy tắc hạn chế (từ chối / hỏi trước) được áp dụng khi yêu cầu chạm tới bất kỳ giá trị nào mà nó nêu tên; một quy tắc nới lỏng (cho phép) chỉ được áp dụng khi yêu cầu nằm hoàn toàn bên trong nó. Đọc cả hai theo cùng một cách sẽ cho phép agent “rửa” một đường dẫn bị cấm bằng cách xếp một đường dẫn vô hại bên cạnh nó.
  • Một quy tắc nêu tên một khía cạnh mà yêu cầu không đề cập thì không được áp dụng cũng không bị bỏ qua: nó được đẩy lên thành một phê duyệt. Bỏ qua trong im lặng sẽ khiến mọi quy tắc bảo vệ nhánh trở nên vô hiệu trước một công cụ không báo cáo ref của nó; từ chối trong im lặng sẽ chặn mọi lần push mà bộ trích xuất không mô tả được. Khi không có người phê duyệt nào được kết nối, quy tắc đóng-khi-lỗi hiện có biến việc đẩy lên đó thành một lần từ chối.

Quy tắc lệnh bắt được và không bắt được gì

Phần tiêu đề “Quy tắc lệnh bắt được và không bắt được gì”

Một quy tắc commandPrefix khớp trên ranh giới từ: git push bao trùm git push origin main và không bao gồm git pushx. Nó không chuẩn hóa shell. git push (hai dấu cách), cd sub && git push và GIT_DIR=… git push đều là những chuỗi khác nhau và không chuỗi nào khớp một quy tắc về git push.

Đó là một giới hạn có thật và nó được nói thẳng ra thay vì che đậy: quy tắc lệnh là tiện ích cho người vận hành để định hình hành vi thông thường của agent, chứ không phải một ranh giới chống lại một agent đối kháng. Các ranh giới thật sự là các lớp hiệu ứng (thứ mà một công cụ khai báo và không thể viết lại theo cách khác), lưới lệnh của chế độ nằm dưới kho chính sách, và sandbox của hệ điều hành nằm dưới cả hai. Một quy tắc lệnh phân giải thành allow không bao giờ nới lỏng bất kỳ thứ nào trong hai thứ đó — lưới chế độ chạy sau đó và lệnh deny của nó là quyết định cuối cùng.

Trả lời một yêu cầu phê duyệt bằng “ghi nhớ điều này” sẽ ghi ra một quy tắc chính sách thật: có phạm vi (space / agent / không gian làm việc), có ràng buộc đối số (phê duyệt một lần push tới feature/login cấp quyền cho feature/**, không phải main) và tự thu hồi theo một TTL tường minh. Đồng ý trong vài giờ tới không giống với việc âm thầm viết lại chính sách vĩnh viễn của bạn, và những quy tắc nó ghi ra nằm trong cùng một kho với nguồn gốc remembered và một thời điểm hết hạn, nên chúng có thể bị thu hồi sớm.

Mỗi quy tắc mang một mức:

  • tư vấn — cho phép, nhưng ghi lại rằng quy tắc đã khớp. Đây là con đường đưa vào sử dụng: triển khai một quy tắc nghiêm ngặt ở chế độ tư vấn, đọc dấu vết kiểm toán trong một tuần để xem chính xác nó sẽ chặn những gì, rồi nâng cấp nó. Không có tầng này, các chính sách nghiêm ngặt sẽ không bao giờ được bật.
  • mềm — từ chối, có thể bị ghi đè bởi người nắm quyền ghi đè, kèm một lý do được ghi lại. Bản thân việc ghi đè cũng là một sự kiện được kiểm toán.
  • cứng (mặc định) — từ chối. Không gì ghi đè được nó.

Người vận hành bản cài đặt có thể ghim chính sách mà không quản trị viên không gian làm việc nào nới lỏng được. Các quy tắc được quản lý nằm phía máy chủ và được hợp nhất theo hướng hạn chế nhất với chuỗi quy tắc riêng của từng không gian làm việc — chúng không phải một phạm vi đứng đầu chuỗi, bởi vì một allow được quản lý đứng đầu chuỗi sẽ cho phép bản cài đặt ghi đè lên lệnh từ chối của không gian làm việc, ngược hẳn với mục đích của một cơ chế siết. Vì vậy một quy tắc được quản lý chỉ có thể siết chặt hơn mà thôi.

Đặt CC_SERVER_MANAGED_POLICY trỏ tới một tệp JSON sẽ có hiệu lực cao hơn hẳn các quy tắc đã lưu, nhờ đó người vận hành có thể ghim một cấu hình mà không giao diện quản trị nào đảo ngược được — câu trả lời cho “liệu tôi có thể ngăn các lập trình viên của mình vô hiệu hóa các kiểm soát an toàn không?”.

Núm tự chủ là một hồ sơ, không phải một hệ thống song song

Phần tiêu đề “Núm tự chủ là một hồ sơ, không phải một hệ thống song song”

Núm tự chủ theo từng space là một hồ sơ có tên đặt trên chính kho chính sách này, chứ không phải một cơ chế thứ hai. Ba thiết lập của nó được lưu và truyền dưới dạng proposeOnly, actWithApproval và actFreely (các dạng có gạch nối chỉ là văn bản giao diện, và autonomy.setForSpace từ chối mọi giá trị khác). Để trống thì hành xử như actWithApproval.

Mỗi thiết lập tác động thế nào tới một quyết định đã phân giải:

  • proposeOnly từ chối thẳng mọi công cụ được kiểm soát bằng cổng. Agent vẫn có thể suy luận và trả lời; nó chỉ không thể hành động và thông điệp của nó nói rõ lý do.
  • actWithApproval (và trạng thái để trống) là cổng phê duyệt đóng-khi-lỗi đã mô tả ở trên.
  • actFreely phê duyệt trước mọi thứ không phân giải thành một deny cứng.

Điều cuối cùng đó cần được nói thẳng thay vì làm nhẹ đi: dưới actFreely, một quyết định prompt không được đẩy lên cho người phê duyệt — nó được cho phép và không ai bị hỏi. Chỉ một quy tắc deny tường minh mới sống sót qua núm tự chủ. Vậy nên actFreely là một sự trao quyền tự chủ có chủ đích, không phải một thiết lập tiện lợi, và quy tắc đóng-khi-lỗi bảo vệ đúng trường hợp mà nó quan trọng (actWithApproval, mặc định).

Việc ủy thác giữa các agent được canh giữ tại cửa ngõ delegate_task bởi cả bốn rào cản: một mức trần độ sâu (mặc định 3), phát hiện chu trình, một mức trần tự chủ (bên được ủy thác không bao giờ được hành động với quyền tự chủ cao hơn người ủy thác nó — đặc quyền không thể bị rửa bằng cách giao việc cho một agent tự do hơn) và một hạn mức ngân sách (việc ủy thác tính vào ngân sách còn lại của người ủy thác và không thể tạo thêm). Mỗi trường hợp đều bị từ chối dứt khoát, kèm lý do của rào cản được trả lại nguyên văn cho agent.

Không phải runtime nào cũng cho phép Control Center chặn mọi hành động. Các CLI agent bên ngoài chạy một số công cụ theo cách gốc, trong tiến trình, nơi không cổng nào có thể chen vào. Thay vì giả vờ điều ngược lại, mỗi adapter mang một ma trận trung thực: theo từng adapter, liệu Control Center có lọc bề mặt công cụ, chặn các lời gọi công cụ, quan sát hợp đồng hoàn tất, có thấy được các công cụ gốc của runner hay không, và liệu các công cụ trong tiến trình có được một hồ sơ sandbox bao phủ hay không.

Có hai mục đáng đọc trước khi bạn tin vào một cấu hình. Harness tích hợp khai báo rằng các công cụ tệp trong tiến trình của nó không được chạy trong sandbox — bề mặt công cụ và cỗ máy chính sách này là ranh giới hệ thống tệp duy nhất mà nó có. Claude Code khai báo rằng các lời gọi công cụ của chính nó không chặn được, vì nó được khởi chạy với --dangerously-skip-permissions; Control Center thấy các lời gọi mcp__* của nó và, qua một hook, các dòng lệnh Bash của nó, còn các công cụ đọc, ghi và sửa của chính nó chạy mà không bị nhìn thấy.

Màn hình cài đặt hiển thị ma trận này, bởi vì thất bại không thể tha thứ duy nhất của một hệ thống rào chắn là tuyên bố có một phạm vi bao phủ không hề tồn tại.

Rào chắn nằm tại Cài đặt → Không gian làm việc → Quyền của agent (/workspaces/<id>/settings/workspace/permissions). Màn hình này có ma trận chính sách nơi các quy tắc được viết, các mẫu chính sách, một phép thử “nếu như”, dấu vết kiểm toán ủy quyền và ma trận trung thực của adapter.

Phép thử nhận một lớp hành động (hoặc một lệnh) và một phạm vi, rồi cho biết quy tắc nào đã quyết định và vì sao. Vì việc phân giải thuần túy và mang tính tất định — cùng hành động, cùng phạm vi, cùng quyết định — nên câu trả lời của phép thử chính là câu trả lời mà agent sẽ nhận được.