דלגו לתוכן

מעקות בטיחות — מדיניות אחת לכל פעולה משנה־מצב

סוכנים ב-Control Center יכולים למחוק קבצים, לדחוף ענפים, לפרסם בקשות משיכה ולקרוא ל-API חיצוניים. כל אחד מאלה הוא סוג שונה של סיכון, ובמשך זמן רב רק לאחד מהם — פקודות מעטפת — הייתה מדיניות. מעקות הבטיחות נועדו להשיב באופן אחיד על שאלה אחת, לגבי כל פעולה משנה־מצב שסוכן יכול לבצע, בלי קשר למנוע ההרצה או לכלי שמציע אותה: האם היא מותרת, האם היא מחייבת אישור, או שהיא חסומה?

מנוע אחד, כל נקודת מעבר

קטע בשם “מנוע אחד, כל נקודת מעבר”

יש מנוע מדיניות אחד בדיוק. הוא נשאל בכל נקודה שבה פעולה יכולה לצאת מהמערכת: רישום הכלים של מנגנון ההרצה המובנה, השער של מפעיל ה-MCP בצד השרת ופעולות שינוי במאגרים. כלל שנכתב פעם אחת חל אפוא גם על מנוע ההרצה המובנה וגם על CLI של סוכנים חיצוניים — אין נתיב שני ושקט יותר לעקוף אותו. מדיניות הפקודות הישנה, שמוגבלת ל-bash, עדיין עובדת: כלל יכול להתאים לקידומת פקודה במקום למחלקת השפעה, ושתי הצורות מתקיימות יחד באותו מאגר.

המנוע הזה, עם זאת, אינו תחליף לארגז החול של מערכת ההפעלה, וארגז החול אינו תחליף לו. הם עונים על שאלות שונות: מעקות הבטיחות מכריעים אם קריאה ש-Control Center רואה בכלל מותרת, ואילו ארגז החול תוחם לאן התהליך שנוצר רשאי להגיע. cc_server מחיל את ארגז החול במקום שבו המארח מציע מנוע — אבל ב-Windows אין כזה, וב-Linux נדרשים bwrap ו-socat. במארחים כאלה מעקות הבטיחות הם שכבת האכיפה החיצונית ביותר ולא מדיניות שיושבת מעל רצפה, וכדאי לדעת זאת כשאתם מחליטים עד כמה להתיר.

ActionClass: טקסונומיה סגורה במכוון

קטע בשם “ActionClass: טקסונומיה סגורה במכוון”

כל כלי משנה־מצב מצהיר לאילו ActionClasses הוא שייך, מתוך קבוצה סגורה של שלושה־עשר סוגי השפעה. חמישה מהם מבקשים אישור כברירת מחדל; השאר מאפשרים, ורק כלל משנה זאת.

מחלקה ברירת מחדל מובנית מה היא מכסה
fileDelete prompt מחיקת קובץ (כתיבה בצורת מחיקה, rm)
gitPush prompt דחיפה למאגר git מרוחק
prCreate prompt יצירת בקשת משיכה
prPublish prompt פרסום סקירה, מיזוג בקשת משיכה
vendorSyncWrite prompt כתיבה לספק כרטיסים חיצוני (Linear, Jira, סנכרון GitHub)
fileWriteOutsideWorktree allow כתיבת קובץ מחוץ לעץ העבודה המבודד
gitCommit allow יצירת קומיט ב-git
networkEgress allow יציאה לרשת (שליפת דפים, HTTP שרירותי)
secretAccess allow קריאת סוד או פרטי גישה
packageInstall allow התקנת חבילה (npm, pip, pub, brew)
processSpawn allow הרצת תהליך (מעטפת וכל דבר שמריץ מעטפת)
workspaceMutation allow שינוי מבנה סביבת העבודה (מאגרים, מרחבים, סוכנים)
enclosureControl allow השליטה במתחם מבודד (תחנה): אתחול VM, שליחת קלט אליו

שמונת הסוגים שמאפשרים כברירת מחדל נבחרו מתוך הנחה שרצפת ארגז החול חלה מתחתם. היא חלה במקום שבו המארח מציע מנוע, ואינה חלה ב-Windows או במכונת Linux שחסרים בה bwrap ו-socat — לכן אם אתם רוצים ש-fileWriteOutsideWorktree או packageInstall יעברו שם דרך שער, זה כלל שעליכם לכתוב.

הקבוצה סגורה במכוון. כלי משנה־מצב חדש שמצהיר על אף מחלקה נכשל בבדיקת הראצ’ט, כך שהטקסונומיה יכולה לגדול רק בהחלטה מפורשת ומנומקת — “התפשטות טקסונומיה היא מותה של התכונה הזו.” שלושה־עשר סוגים מספיקים כדי לבטא את המדיניות שאנשים באמת כותבים (לעולם לא לדחוף בלי לשאול, לעולם לא למחוק מחוץ לעץ עבודה, תמיד לאפשר קריאות) ומעטים מספיק כדי שממשק ההגדרות יישאר מובן.

כלל ממפה (scope, ActionClass | command prefix) → allow | prompt | deny. הטווחים מקוננים מהספציפי ביותר לכללי ביותר:

מרחב > סוכן > סביבת עבודה > ערכת מצב > ברירת מחדל מובנית

ההכרעה היא קודם כול לפי ספציפיות: הטווח הראשון שיש בו כלל מתאים מכריע, ושם ההכרעה נעצרת. “המחמיר ביותר” הוא רק שובר שוויון — בין שני כללים ספציפיים באותה מידה באותו טווח, וכאשר משלבים את מספר המחלקות שפעולה אחת מצהירה עליהן. בתוך טווח, כלל פקודה מתאים לפי קידומת הפקודה הארוכה ביותר. allow אינו גובר על deny בשום מקום.

שתי החלטות נושאות הבטחות קשיחות:

  • בקשות אישור שנכשלות לחסימה. prompt בלי מאשר מחובר מוכרע כ-denied. שאלה שלא נענתה היא לעולם לא “כן”.
  • שילוב מחמיר. פעולה שפורשת על פני כמה מחלקות משלבת אותן באופן המחמיר ביותר; כאשר כמה מחלקות מבקשות אישור, אישור אחד מפרט את כולן.

במכוון אין נעילה שתלויה בסבב. כל קריאת כלי מכריעה את המדיניות מחדש מאפס, ולכן חזרה על בקשת אישור לאותה פעולה מוגבלת רק בכך שהמפעיל עצמו בחר “לזכור את ההחלטה הזו”, ולא בשום דבר שתלוי בסבב.

כללים ברמת הארגומנטים

קטע בשם “כללים ברמת הארגומנטים”

כלל יכול להגביל את הארגומנטים של פעולה, ולא רק את הפועל שלה. “רשאי לדחוף” ו“רשאי לדחוף אל feature/*” הן שתי טענות שונות, ורק השנייה היא בקרה — שער הרשאה שמאשר את הפועל בלבד הוא הצורה שעומדת מאחורי רוב התקריות המפורסמות עם סוכנים.

אילוץ יכול לציין:

  • נתיבים — תבניות glob (repos/**, !**/.env)
  • מפנים — מפנים של git, עם שלילה ב-! (['**', '!main'] נקרא כ“כל ענף מלבד main”)
  • מארחים — יעדי רשת, עם תווים כלליים לתת־תחומים (*.internal)
  • פקודות — קידומות של פקודות, בהתאמה על גבול מילה
  • תקרות — maxCount / maxCents, כך שאפשר לבטא “מחיקה של עד 50 קבצים”

שלוש תכונות נובעות מכך שהדקדוק נשאר סגור וחסר לולאות, ושלושתן הן העיקר: כל כלל מתורגם למשפט שאפשר לקרוא בחזרה, כל דחייה יכולה לנקוב בשם האילוץ שהתאים, ואת כל המדיניות אפשר למנות — אתם יכולים לראות מה סוכן רשאי לעשות לפני שהוא רץ.

שלושה כללים שחשוב להכיר:

  • כלל עם אילוץ ספציפי יותר מכלל בלי אילוץ באותו טווח, ולכן “כלל שחוסם דחיפה אל main” גובר על “כלל שמתיר דחיפה” בלי שאיש יצטרך לחשוב על סדר.
  • כלל מגביל (חסום / שאל קודם) חל כאשר הבקשה נוגעת בכלשהו מהערכים שהוא מציין; כלל מתיר (אפשר) חל רק כאשר הבקשה כולה בתוך הערכים האלה. קריאה זהה של שניהם הייתה מאפשרת לסוכן “להלבין” נתיב אסור על ידי צירוף נתיב תמים לצידו.
  • כלל שמציין היבט שהבקשה אינה אומרת עליו דבר אינו מיושם ואינו מדולג: הוא מסלים לבקשת אישור. דילוג שקט היה הופך כל כלל על ענף מוגן לחסר תוקף מול כלי שאינו מדווח על ה-ref שלו; חסימה שקטה הייתה דוחה כל דחיפה שהמחלץ אינו מצליח לתאר. כשמאשר אינו מחובר, כלל הכשל־סגור הקיים הופך את ההסלמה הזו לדחייה.

מה כלל פקודה תופס ומה לא

קטע בשם “מה כלל פקודה תופס ומה לא”

כלל commandPrefix מתאים על גבול מילה: git push מכסה את git push origin main ולא את git pushx. הוא אינו מנרמל את המעטפת. git push (שני רווחים), cd sub && git push ו-GIT_DIR=… git push הם כולם מחרוזות שונות, ואף אחד מהם אינו מתאים לכלל על git push.

זו מגבלה אמיתית, והיא נאמרת בגלוי ולא מטואטאת: כללי פקודה הם נוחות למפעיל לצורך עיצוב ההתנהגות הרגילה של סוכן, לא גבול מול סוכן עוין. הגבולות הם מחלקות ההשפעה (שכלי מצהיר עליהן ואינו יכול לאיית אותן מחדש), רשת הפקודות של המצב שמתחת למאגר המדיניות, וארגז החול של מערכת ההפעלה שמתחת לשניהם. כלל פקודה שמוכרע כ-allow לעולם אינו מרפה את אחד משני אלה — רשת המצב רצה אחריו, וה-deny שלה הוא סופי.

מענה לבקשת אישור עם “לזכור את זה” כותב כלל מדיניות אמיתי: בעל טווח (מרחב / סוכן / סביבת עבודה), מוגבל בארגומנטים (אישור דחיפה אל feature/login מקנה feature/**, לא את main) ומבטל את עצמו בתום TTL מפורש. אמירת “כן” לשעות הקרובות אינה אותו דבר כמו שכתוב שקט מחדש של המדיניות הקבועה שלכם, והכללים שהוא כותב נוחתים באותו מאגר עם מקור remembered ותפוגה, כך שאפשר לבטל אותם מוקדם.

לכל כלל יש אחת:

  • advisory — לאפשר, אבל לתעד שהכלל התאים. זהו נתיב האימוץ: פורסים כלל מחמיר במצב advisory, קוראים את יומן הביקורת למשך שבוע כדי לראות בדיוק מה הוא היה חוסם, ואז מקדמים אותו. בלי הרמה הזו, מדיניות מחמירה לעולם לא נדלקת.
  • soft — לחסום, עם אפשרות עקיפה למי שמחזיק בהרשאת העקיפה, בתיעוד נימוק. העקיפה עצמה היא אירוע שנרשם ביומן הביקורת.
  • hard (ברירת המחדל) — לחסום. שום דבר אינו עוקף זאת.

מפעיל של התקנה יכול לנעול מדיניות שאיש ממנהלי סביבות העבודה לא יוכל להרפות. כללים מנוהלים יושבים בצד השרת וממוזגים באופן המחמיר ביותר עם השרשרת של כל סביבת עבודה — הם אינם טווח בראש השרשרת, מפני ש-allow מנוהל בראש השרשרת היה מאפשר להתקנה לעקוף חסימה של סביבת עבודה, ההיפך ממה שמנגנון הגבלה מלמעלה נועד לעשות. כלל מנוהל יכול אפוא רק להחמיר.

הגדרת CC_SERVER_MANAGED_POLICY לקובץ JSON גוברת על הכללים השמורים לחלוטין, וזה מה שמאפשר למפעיל לנעול עמדה ששום ממשק ניהול לא יכול להפוך — התשובה לשאלה “אפשר למנוע מהמפתחים שלי לכבות את בקרות הבטיחות?”.

חוגת האוטונומיה היא פרופיל, לא מערכת מקבילה

קטע בשם “חוגת האוטונומיה היא פרופיל, לא מערכת מקבילה”

חוגת האוטונומיה לכל מרחב היא פרופיל בעל שם מעל אותו מאגר מדיניות, ולא מנגנון שני. שלוש ההגדרות שלה נשמרות ונשלחות כ-proposeOnly, actWithApproval ו-actFreely (הצורות הכתובות במקפים הן טקסט ממשק בלבד, ו-autonomy.setForSpace דוחה כל דבר אחר). השארתה ללא הגדרה מתנהגת כ-actWithApproval.

מה כל אחת מהן עושה להחלטה שהוכרעה:

  • proposeOnly חוסם לחלוטין כל כלי שעובר דרך שער. הסוכן עדיין יכול לחשוב ולהשיב; הוא פשוט אינו יכול לפעול, וההודעה שלו מסבירה למה.
  • actWithApproval (וגם מצב ללא הגדרה) הוא שער האישור שנכשל לחסימה שתואר למעלה.
  • actFreely מאשר מראש כל דבר שלא הוכרע כ-deny קשיח.

את האחרונה ראוי לנסח בפשטות בלי לרכך: תחת actFreely החלטת prompt אינה מוסלמת למאשר — היא מאושרת, ואיש לא נשאל. רק כלל deny מפורש שורד את החוגה. כלומר actFreely היא הענקה מכוונת של אוטונומיה, לא הגדרת נוחות, וכלל הכשל־סגור מגן בדיוק על המקרה שבו הוא חשוב (actWithApproval, ברירת המחדל).

האצלה בין סוכנים נשמרת בנקודת המעבר של delegate_task על ידי כל ארבעת השומרים: תקרת עומק (ברירת מחדל 3), זיהוי מעגלים, תקרת אוטונומיה (סוכן מואצל אינו יכול לפעול לעולם באוטונומיה גדולה מזו של המאציל — אי אפשר “להלבין” הרשאה על ידי העברת עבודה לסוכן חופשי יותר) ומעטפת תקציב (האצלה מחייבת את התקציב שנותר למאציל ואינה יכולה להנפיק עוד). כל אחת נדחית בקול רם, כשהנימוק של השומר מוחזר לסוכן כפי שהוא.

מטריצת היושר של המתאמים

קטע בשם “מטריצת היושר של המתאמים”

לא כל מנוע הרצה מאפשר ל-Control Center ליירט כל פעולה. CLI של סוכנים חיצוניים מריצים חלק מהכלים באופן מקורי, בתוך התהליך, במקום שאין בו שער. במקום להעמיד פנים אחרת, כל מתאם נושא מטריצת יושר: לפי מתאם, אם Control Center מסנן את משטח הכלים, מיירט קריאות כלים, רואה את חוזה ההשלמה, מצליח לראות את הכלים המובנים של מנוע ההרצה, ואם כלים שבתוך התהליך מכוסים בפרופיל ארגז חול.

שתי רשומות כדאי לקרוא לפני שאתם סומכים על תצורה. מנגנון ההרצה המובנה מצהיר שכלי הקבצים שלו שבתוך התהליך אינם בארגז חול — משטח הכלים ומנוע המדיניות הזה הם גבול מערכת הקבצים היחיד שיש לו. Claude Code מצהיר שקריאות הכלים שלו אינן ניתנות ליירוט, מפני שהוא מופעל עם --dangerously-skip-permissions; Control Center רואה את קריאות ה-mcp__* שלו ואת שורות הפקודה של Bash דרך hook, וכלי הקריאה, הכתיבה והעריכה שלו עצמו רצים בלי שנראים.

מסך ההגדרות מציג את המטריצה הזו, מפני שהכישלון היחיד שאין לסלוח לו במערכת מעקות בטיחות הוא להצהיר על כיסוי שאינו קיים.

לבדוק את המדיניות לפני שהיא נושכת

קטע בשם “לבדוק את המדיניות לפני שהיא נושכת”

מעקות הבטיחות נמצאים בהגדרות → סביבת עבודה → הרשאות סוכנים (/workspaces/<id>/settings/workspace/permissions). במסך יש את מטריצת המדיניות שבה נכתבים הכללים, תבניות מדיניות, בדיקת “מה אם?”, יומן ביקורת ההרשאות ומטריצת היושר של המתאמים.

הבדיקה מקבלת מחלקת השפעה (או פקודה) וטווח, ומראה איזה כלל הכריע ולמה. מכיוון שההכרעה טהורה ודטרמיניסטית — אותה פעולה, אותו טווח, אותה החלטה — התשובה של הבדיקה היא התשובה שהסוכן יקבל.