Ga naar inhoud

Beveiligingen — één beleid voor elke actie die iets wijzigt

Agents in Control Center kunnen bestanden verwijderen, branches pushen, pull requests publiceren en externe API’s aanroepen. Elk daarvan is een ander soort risico, en lange tijd had slechts één ervan — shellopdrachten — een beleid. De beveiligingen bestaan om één vraag uniform te beantwoorden, voor elke actie die een agent kan uitvoeren en die iets wijzigt, ongeacht welke runtime of welk hulpmiddel die voorstelt: is dit toegestaan, is er goedkeuring nodig, of wordt het geweigerd?

Er is precies één beleidsengine. Die wordt geraadpleegd op elk punt waarop een actie het systeem kan verlaten: het toolregister van de ingebouwde harness, de poort van de server-side MCP-dispatcher en repo-opmutaties. Een regel die je één keer schrijft, geldt dus zowel voor de ingebouwde runtime als voor externe agent-CLI’s — er is geen tweede, stillere route eromheen. Het oudere opdrachtbeleid dat alleen op bash werkt, functioneert nog steeds: een regel kan op een opdrachtprefix matchen in plaats van op een actieklasse, en beide vormen bestaan naast elkaar in dezelfde opslag.

De engine is echter geen vervanging van de OS-sandbox, en de sandbox is geen vervanging van de engine. Ze beantwoorden verschillende vragen: de beveiligingen bepalen of een aanroep die Control Center kan zien wel mag plaatsvinden, terwijl de sandbox begrenst waar het resulterende proces mag komen. cc_server past de sandbox toe waar de host een backend biedt — maar Windows biedt er geen en Linux heeft bwrap en socat nodig. Op zulke hosts vormen de beveiligingen de buitenste handhavingslaag in plaats van een beleid boven op een ondergrens, wat je moet weten wanneer je bepaalt hoe ruimhartig je ze instelt.

Elk hulpmiddel dat iets wijzigt, geeft aan tot welke ActionClasses het behoort, uit een gesloten set van dertien effectklassen. Vijf ervan vragen standaard om goedkeuring; de overige staan toe, en alleen een regel brengt daar verandering in.

Klasse Ingebouwde standaardwaarde Wat eronder valt
fileDelete Eerst vragen Een bestand verwijderen (een verwijderingsachtige schrijfactie, rm)
gitPush Eerst vragen Naar een git-remote pushen
prCreate Eerst vragen Een pull request aanmaken
prPublish Eerst vragen Een review publiceren, een PR mergen
vendorSyncWrite Eerst vragen Schrijven naar een externe ticketservice (Linear, Jira, GitHub-synchronisatie)
fileWriteOutsideWorktree Toestaan Een bestand schrijven buiten de geïsoleerde worktree
gitCommit Toestaan Een git-commit aanmaken
networkEgress Toestaan Netwerkuitgaand verkeer (web fetch, willekeurige HTTP)
secretAccess Toestaan Een secret of referentie lezen
packageInstall Toestaan Een pakket installeren (npm, pip, pub, brew)
processSpawn Toestaan Een proces starten (shell en alles wat een shell aanroept)
workspaceMutation Toestaan De werkruimtestructuur wijzigen (repos, ruimtes, agents)
enclosureControl Toestaan Een omhulling (rig) aansturen: een VM opstarten, er invoer naartoe sturen

De acht klassen die standaard toestaan, zijn gekozen in de veronderstelling dat de sandboxondergrens eronder geldt. Dat is het geval waar de host een backend biedt, en niet op Windows of op een Linux-machine zonder bwrap en socat — dus als je wilt dat fileWriteOutsideWorktree of packageInstall daar wordt afgevangen, moet je die regel zelf schrijven.

De set is bewust gesloten. Een nieuw hulpmiddel dat iets wijzigt en geen klasse opgeeft, zakt voor de ratchet-test, dus de taxonomie kan alleen groeien door een expliciet, beargumenteerd besluit — “taxonomievervuiling is de dood van deze functie.” Dertien klassen zijn genoeg om de beleidsregels uit te drukken die mensen daadwerkelijk schrijven (nooit pushen zonder te vragen, nooit buiten een worktree verwijderen, leesacties altijd toestaan) en weinig genoeg om de instellingen-UI begrijpelijk te houden.

Een regel koppelt (bereik, ActionClass | opdrachtprefix) → toestaan | eerst vragen | weigeren. Bereiken nestelen van meest naar minst specifiek:

ruimte > agent > werkruimte > modusvoorinstelling > ingebouwde standaardwaarde

Resolutie gaat eerst op specificiteit: het eerste bereik met een passende regel beslist en daar stopt de resolutie. Meest beperkend is alleen een tiebreak — tussen twee even specifieke regels binnen één bereik en bij het combineren van de klassen die één actie opgeeft. Binnen een bereik matcht een opdrachtregel op het langste opdrachtprefix. Toestaan wint nergens van weigeren.

Twee beslissingen komen met harde garanties:

  • Fail-closed vragen. Een prompt zonder verbonden goedkeurder wordt geweigerd. Een onbeantwoorde vraag is nooit een ja.
  • Meest beperkende combinatie. Een actie die meerdere klassen omvat, combineert ze op de meest beperkende manier; wanneer meerdere klassen om goedkeuring vragen, somt één bevestiging ze allemaal op.

Er is bewust geen vergrendeling per beurt. Elke toolaanroep lost het beleid opnieuw vanaf nul op, dus herhaald vragen om goedkeuring voor dezelfde actie wordt begrensd door de “deze beslissing onthouden” van de operator zelf, niet door iets dat aan een beurt gebonden is.

Een regel kan de ARGUMENTEN van een actie beperken, niet alleen het werkwoord. “Mag pushen” en “mag naar feature/* pushen” zijn verschillende beweringen, en alleen de tweede is een beheersmaatregel — een capaciteitspoort die alleen het werkwoord autoriseert, ligt ten grondslag aan de meeste gepubliceerde agentincidenten.

Een beperking kan het volgende benoemen:

  • paden — globpatronen (repos/**, !**/.env)
  • refs — git-refs, met !-negatie (['**', '!main'] leest als “elke branch behalve main”)
  • hosts — netwerkbestemmingen, met subdomeinwildcards (*.internal)
  • opdrachten — opdrachtprefixen, gematcht op een woordgrens
  • plafonds — maxCount / maxCents, zodat “maximaal 50 bestanden verwijderen” uitdrukbaar is

Drie eigenschappen volgen uit het gesloten en lusvrij houden van de grammatica, en die drie zijn precies de bedoeling: elke regel is weer te geven als een zin die je kunt teruglezen, elke weigering kan de beperking benoemen die matchte, en het hele beleid is op te sommen — je kunt zien wat een agent mag doen voordat die draait.

Drie regels die ertoe doen:

  • Een beperkte regel is specifieker dan een onbeperkte regel in hetzelfde bereik, dus “push naar main weigeren” wint van “push toestaan” zonder dat iemand over de volgorde hoeft na te denken.
  • Een beperkende regel (weigeren / eerst vragen) geldt wanneer de aanvraag een willekeurige waarde aanraakt die de regel benoemt; een permissieve regel (toestaan) geldt alleen wanneer de aanvraag er volledig binnen valt. Als je beide op dezelfde manier zou lezen, kon een agent een verboden pad witwassen door er een onschuldig pad naast te zetten.
  • Een regel die een aspect benoemt waarover de aanvraag niets zegt, wordt niet toegepast en ook niet overgeslagen: die escaleert naar een goedkeuring. Stil overslaan zou elke regel voor beschermde branches inactief maken tegenover een hulpmiddel dat zijn ref niet rapporteert; stil weigeren zou elke push afwijzen die de extractor niet kan beschrijven. Zonder verbonden goedkeurder verandert de bestaande fail-closed regel die escalatie in een weigering.

Wat een opdrachtregel wel en niet kan afvangen

Sectie getiteld “Wat een opdrachtregel wel en niet kan afvangen”

Een commandPrefix-regel matcht op een woordgrens: git push dekt git push origin main en niet git pushx. De regel normaliseert de shell niet. git push (twee spaties), cd sub && git push en GIT_DIR=… git push zijn allemaal verschillende strings, en geen ervan matcht een regel over git push.

Dat is een echte beperking, en die wordt benoemd in plaats van verdoezeld: opdrachtregels zijn een gemak voor de operator om gewoon agentgedrag te sturen, geen grens tegen een kwaadwillende. De grenzen zijn de effectklassen (die een hulpmiddel opgeeft en niet anders kan formuleren), het opdrachtnet van de modus onder de beleidsopslag, en de OS-sandbox onder beide. Een opdrachtregel die op allow uitkomt, verruimt geen van beide — het modusnet draait daarna en zijn deny is definitief.

Als je een goedkeuringsvraag beantwoordt met “dit onthouden”, wordt er een echte beleidsregel geschreven: met een bereik (ruimte / agent / werkruimte), beperkt op argumenten (het goedkeuren van een push naar feature/login verleent feature/**, niet main) en zelfintrekkend na een expliciete TTL. Ja zeggen voor de komende uren is iets anders dan stilletjes je permanente beleid herschrijven, en de regels die het schrijft, belanden in dezelfde opslag met herkomst remembered en een vervaldatum, zodat ze vroegtijdig kunnen worden ingetrokken.

Elke regel heeft er één:

  • adviserend — toestaan, maar vastleggen dat de regel matchte. Dit is het invoerpad: zet een strikte regel uit in adviserende modus, lees een week lang het auditspoor om precies te zien wat die zou hebben geblokkeerd, en promoveer hem daarna. Zonder dit niveau worden strikte beleidsregels nooit ingeschakeld.
  • soft — weigeren, te overschrijven door iemand met de overschrijvingsmachtiging, met een vastgelegde rechtvaardiging. De overschrijving komt zelf ook in de audit terecht.
  • hard (de standaard) — weigeren. Niets overschrijft dit.

Een installatie-operator kan beleid vastzetten dat geen enkele werkruimtebeheerder kan versoepelen. Beheerde regels staan aan de serverkant en worden meest beperkend samengevoegd met de eigen keten van elke werkruimte — ze zijn geen bereik aan het hoofd ervan, want een beheerde allow aan het hoofd van de keten zou de installatie een deny van een werkruimte laten overschrijven, het tegenovergestelde van waar een klem voor dient. Een beheerde regel kan dus alleen maar strenger maken.

Als je CC_SERVER_MANAGED_POLICY op een JSON-bestand zet, gaat dat volledig boven de opgeslagen regels, en daardoor kan een operator een houding vastzetten die geen enkele beheerders-UI kan omzetten — het antwoord op “kan ik voorkomen dat mijn ontwikkelaars de veiligheidscontroles uitschakelen?”.

De autonomieschakelaar is een profiel, geen parallel systeem

Sectie getiteld “De autonomieschakelaar is een profiel, geen parallel systeem”

De autonomieschakelaar per ruimte is een benoemd profiel boven op dezelfde beleidsopslag, geen tweede mechanisme. De drie standen worden opgeslagen en verzonden als proposeOnly, actWithApproval en actFreely (de vormen met koppelstreepjes zijn alleen UI-tekst, en autonomy.setForSpace weigert al het andere). Laat je hem oningesteld, dan gedraagt hij zich als actWithApproval.

Wat elk van de standen doet met een opgeloste beslissing:

  • proposeOnly weigert elk afgeschermd hulpmiddel onmiddellijk. De agent kan nog steeds redeneren en antwoorden; hij kan alleen niet handelen, en zijn bericht zegt waarom.
  • actWithApproval (en oningesteld) is de fail-closed goedkeuringspoort die hierboven is beschreven.
  • actFreely keurt alles vooraf goed wat niet op een harde deny is uitgekomen.

Die laatste verdient een onomwonden formulering in plaats van een verzachting: onder actFreely wordt een prompt-beslissing niet naar een goedkeurder geëscaleerd — ze wordt toegestaan en er wordt niemand gevraagd. Alleen een expliciete deny-regel overleeft de schakelaar. actFreely is dus een bewuste toekenning van autonomie, geen gemaksinstelling, en de fail-closed regel beschermt precies het geval waarin dat ertoe doet (actWithApproval, de standaard).

Delegatie tussen agents wordt op het controlepunt delegate_task bewaakt door alle vier de bewakers: een dieptelimiet (standaard 3), cyclusdetectie, een autonomieplafond (een gedelegeerde kan nooit met meer autonomie handelen dan zijn delegator — privileges kunnen niet worden witgewassen door werk aan een vrijere agent te geven) en een budgetenvelop (delegatie schrijft af op het resterende budget van de delegator en kan geen nieuw budget aanmaken). Elk wordt luidruchtig geweigerd, met de reden van de bewaker woordelijk aan de agent teruggegeven.

Niet elke runtime laat Control Center elke actie onderscheppen. Externe agent-CLI’s voeren sommige hulpmiddelen native uit, in-process, waar geen poort kan zitten. In plaats van iets anders voor te wenden, draagt elke adapter een eerlijkheidsmatrix: per adapter of Control Center het toolaanbod filtert, toolaanroepen onderschept, het voltooiingscontract waarneemt, de native hulpmiddelen van de runner kan zien, en of hulpmiddelen in-process door een sandboxprofiel worden gedekt.

Twee vermeldingen zijn het lezen waard voordat je een configuratie vertrouwt. De ingebouwde harness verklaart dat zijn bestandshulpmiddelen in-process niet in een sandbox draaien — het toolaanbod en deze beleidsengine zijn de enige bestandssysteemgrens die hij heeft. Claude Code verklaart dat zijn eigen toolaanroepen niet te onderscheppen zijn, omdat het wordt gestart met --dangerously-skip-permissions; Control Center ziet zijn mcp__*-aanroepen en, via een hook, zijn Bash-opdrachtregels, terwijl zijn eigen lees-, schrijf- en bewerkingshulpmiddelen ongezien draaien.

Het instellingenscherm toont deze matrix, want de enige onvergeeflijke fout voor een beveiligingssysteem is dekking claimen die niet bestaat.

De beveiligingen vind je onder Instellingen → Werkruimte → Agentmachtigingen (/workspaces/<id>/settings/workspace/permissions). Het scherm bevat de beleidsmatrix waarin regels worden geschreven, beleidssjablonen, een Wat als?-test, het auditspoor van autorisaties en de eerlijkheidsmatrix van de adapters.

De test neemt een actieklasse (of een opdracht) en een bereik, en toont welke regel heeft beslist en waarom. Omdat resolutie puur en deterministisch is — dezelfde actie, hetzelfde bereik, dezelfde beslissing — is het antwoord van de test het antwoord dat de agent krijgt.