Hoe je een AI-coderingsagent een geheim geeft zonder het in de prompt te zetten
Een agent heeft een Stripe-sleutel of een databasewachtwoord nodig om zijn commando te draaien. Plak je die in de chat, dan belandt hij in het transcript op je schijf, bij de modelprovider en soms in een commit. De regel waar we na drie eigen fouten op uitkwamen: een geheim bereikt een agent via een omgevingsvariabele en via niets anders.
De situatie is altijd dezelfde. Een agent zit halverwege een taak, hij moet je Stripe-test-API aanroepen, een migratie draaien tegen de stagingdatabase, of pushen naar een privé-registry. Hij vraagt om de sleutel. Je hebt de sleutel in je wachtwoordmanager, de agent wacht, en het snelste wat er bestaat is hem in de chat plakken.
Dat plakken is het moment waarop de sleutel buiten je controle raakt. Niet omdat het model kwaadwillend is, maar omdat een prompt een opgeslagen en doorgestuurd object is: hij gaat naar een transcriptbestand op je schijf, hij gaat naar de modelprovider, en hij wordt gekopieerd door elke herhaalpoging en elke subagent die het gesprek erft.
Wij bouwen AgentsRoom, een desktop-app die coderingsagenten draait, dus we moesten dit voor onszelf oplossen. In september 2026 hebben we het drie keer fout gedaan. Dit is de regel waar we op uitkwamen, de drie fouten die haar hebben voortgebracht, en hoe je haar toepast, of je onze app nu gebruikt of niet.
Het korte antwoord
- Een geheim bereikt een agent via een omgevingsvariabele, en via niets anders. Geen bericht, geen ticket, geen skill, geen vlag op de commandoregel.
- De agent gebruikt
$NAME, nooit de waarde. De shell vult hem in wanneer het commando draait. - Bewaar de waarde één keer, buiten de repo, versleuteld door de sleutelhanger van je besturingssysteem, en verwijs er overal elders op naam naar.
- Een geheim dat door een gesprek is gegaan, is verbrand. Vervang het, en ruim daarna op.
- Maskering is een vangnet, geen plan. Het vangt wat op een sleutel lijkt, niet een databasewachtwoord dat als een gewoon woord leest.
Waarom dat plakken erger is dan het lijkt
Er gebeuren drie dingen tegelijk, en alleen het eerste is zichtbaar.
Het gaat naar de modelprovider. Dat is het deel dat iedereen ziet. Je sleutel zit nu in een request body op de infrastructuur van iemand anders, in een gesprek dat je niet kunt bewerken.
Het gaat naar je schijf, in platte tekst. Claude Code, Codex, Antigravity CLI en de rest bewaren een transcript van het gesprek zodat je het kunt hervatten. Dat bestand staat onversleuteld in je thuismap, zolang de CLI zijn geschiedenis bewaart. Zoek één keer in je transcripten naar de tekenreeks sk- en kijk wat eruit komt.
Het verspreidt zich zijwaarts. Het gesprek is het geheugen van de agent, dus alles wat het gesprek kopieert, kopieert de sleutel: een hervatte sessie, een geforkte agent, een subagent gestart met de context van de ouder, een samenvatting die in een projectbestand is geschreven. We hebben een sleutel zien reizen van een chatbericht naar een ticketbeschrijving naar het eerste bericht van een tweede agent, zonder dat iemand iets verkeerd deed.
En het gebruikelijke vangnet houdt niet. Maskering van de uitvoer werkt op patroon: sk-…, glpat-…, de vorm van een JWT. Het kan niet weten dat thursday-harbour-71 het wachtwoord van je stagingdatabase is. In onze eigen app maskeren we herkende waarden in de terminaluitvoer, en we beschouwen dat nog steeds als een net onder de trapeze, niet als de trapeze.
De regel van de ene deur
Hier is de regel, in één zin: inloggegevens bereiken een agent via een omgevingsvariabele van het proces waarin de agent draait, en via geen enkel ander kanaal.
Al het andere is letterlijke tekst. Een bericht dat je typt, een ticketbeschrijving, een skillbestand, een systeemprompt, een vlag op de commandoregel: dat wordt allemaal gelezen door een mens of een model, dus een geheim dat daar staat is een gepubliceerd geheim.
De commandoregel verdient een eigen zin, want die voelt veiliger dan een chatbericht en is dat niet. Een waarde in --api-key=… is via ps zichtbaar voor elk proces op de machine, hij belandt in je shellgeschiedenis, hij wordt gelogd door alles wat het gestarte commando vastlegt, en hij duikt op in diagnosetools die de procesregel aan je teruggeven. Een omgevingsvariabele van één proces is leesbaar door dat proces en door root. Kleiner oppervlak, hetzelfde gemak.
De agent heeft de waarde intussen nooit nodig. Hij moet dit schrijven:
curl -H "Authorization: Bearer $STRIPE_SECRET_KEY" https://api.stripe.com/v1/charges
De shell vult $STRIPE_SECRET_KEY in bij het uitvoeren. De agent heeft zeventien tekens en een dollarteken geschreven. Zijn transcript bevat zeventien tekens en een dollarteken.
Hoe het werkt in onze app
Het mechanisme telt minder dan de vorm, maar de vorm is makkelijker over te nemen als je er één uitwerking van ziet.
Eén kluis, één bestand. AgentsRoom bewaart geheimen in één bestand in je thuismap, modus 0600, versleuteld via de sleutelhanger van het besturingssysteem, bij elke schrijfactie opnieuw bevestigd. Er wordt niets naar een server gesynchroniseerd en er gaat niets over naar een andere computer: een geheim hoort bij de machine die het bewaart. Is de sleutelhanger niet beschikbaar, dan mislukt het opslaan in plaats van terug te vallen op platte tekst.
Je bewaart een waarde één keer. Commando's & verbindingen > Secrets, in elk project. Een naam (letters, cijfers, punt, koppelteken, liggend streepje, 64 tekens, zodat hij bruikbaar is als naam van een omgevingsvariabele) en een waarde, getypt in een wachtwoordveld. De regel die verschijnt toont de naam en zijn verwijzing, {{secret:STRIPE_SECRET_KEY}}, en een knop die de verwijzing kopieert. Er is geen knop om te tonen en geen knop om de waarde te kopiëren, want er bestaat geen codepad dat een waarde teruggeeft aan de interface.
Je verwijst ernaar waar de omgeving wordt opgebouwd. Agent bewerken > Geavanceerd > Omgevingsvariabelen, of de projectinstellingen, of de providerinstellingen, of een dev-commandoregel:
STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}}
Bij het starten vult het hoofdproces de waarde in in de omgeving van de terminal die het gaat aanmaken. De renderer ziet hem nooit, de agent ziet hem nooit, de telefoon ziet hem nooit. De pagina over Geheimenbeheer loopt de schermen door.
De agent ontdekt namen, geen waarden. Een agent die aan onze MCP-tools hangt kan secrets_list aanroepen en krijgt de lijst met namen op die machine terug. Dat is genoeg om nuttig te zijn: hij kan zeggen "je hebt een STRIPE_SECRET_KEY opgeslagen, voeg STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}} toe aan de omgeving van deze agent en dan gebruik ik $STRIPE_SECRET_KEY". Hij kan de waarde niet lezen, zelfs niet terwijl hij een commando draait dat die waarde gebruikt.
Drie fouten die we hebben uitgebracht
De regel hierboven klinkt vanzelfsprekend zodra hij op papier staat. Hij is het littekenweefsel van drie bugs, allemaal in september 2026, allemaal gemeld door gebruikers.
Een in een ticket geciteerde verwijzing kwam ingevuld terug
19 september. Robert had een koppeling gedocumenteerd in een ticketbeschrijving, het volkomen redelijke STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}}, zodat wie het ticket oppakte wist wat hij moest instellen. Het ticket via de API teruglezen gaf de verwijzing ongemoeid terug. Maar de agent die vanuit dat ticket werd gestart kreeg de waarde in zijn eerste bericht.
De oorzaak was een detail in de bedrading met een heel reëel gevolg. De briefing van een ticket wordt de eerste prompt van de agent, en die reisde mee in een omgevingsvariabele van het startcommando. Onze resolver vulde elke omgevingswaarde in, dus vulde hij ook de prompt in. De sleutel ging naar de schijf en naar de modelprovider. Vervangen, daar was niet omheen te komen.
De oplossing was om het startcommando van een agent letterlijk te maken: draagt de omgeving een agent-id, dan blijft de variabele met de eerste prompt onaangeroerd. Een dev-commando dat dezelfde variabele gebruikt heeft geen agent-id en wordt ingevuld zoals voorheen. We hebben bewust een neveneffect aanvaard: een verwijzing die in de CLI-opties van een agent is getypt wordt ook niet meer ingevuld, wat juist is, want een commandoregel belandt toch al in ps en in logs.
De les: de deur moet met opzet smal zijn. "Overal invullen waar het van pas zou kunnen komen" is precies hoe een geheim ergens belandt waar het nooit hoort te zijn.
Een in een bericht geplakte verwijzing, die nooit wordt ingevuld
22 september. Cs040618 meldde het omgekeerde probleem: "ik plak de verwijzing in het gesprek en de agent snapt niet wat deze sleutel is."
Geen bug. Een bericht is van opzet letterlijke tekst, wat precies de oplossing van drie dagen eerder is. Maar niets in de app zei dat op het moment van het gebaar, en de agent, die {{secret:…}} in een bericht kreeg, had geen idee wat hij moest antwoorden.
Drie oplossingen, allemaal om mensen te vertellen waar de deur zit: een hint die verschijnt wanneer je een verwijzing in het invoerveld plakt en die naar de omgevingsvariabelen wijst, een helptekst boven de lijst met geheimen die de drie velden noemt waar een verwijzing wordt ingevuld en zegt "nooit in een bericht", en een regel in de instructies van de agent zelf die hem vertelt wat hij antwoordt als er een verwijzing in een bericht binnenkomt.
"Instellingen > Secrets" bestond niet
28 september. De agent van Mohammad diende een melding in: de gebruiker zegt dat hij OTPIQ_API_KEY heeft toegevoegd in Instellingen > Secrets, hem in dertig minuten meerdere keren heeft opgeslagen, en secrets_list geeft nog steeds dezelfde drie oude geheimen terug.
Het opslagpad werkte prima. Het geheim had de kluis nooit bereikt, want er is geen Secrets-pagina in de instellingen. Zes helpteksten in de desktop-app, een pagina op deze website en de instructies van de agent stuurden mensen allemaal naar een scherm dat niet bestond. Wat wel in de instellingen bestaat is het blok met omgevingsvariabelen, dus daar typte hij het token in, tussen accolades, wat geen verwijzing is: nooit ingevuld, ongewijzigd naar de agent gestuurd en gesynchroniseerd met het project, dus het token verliet de machine.
We hebben de teksten gecorrigeerd zodat ze het echte pad noemen, de tools die voor agenten bedoeld zijn laten zeggen waar je een geheim toevoegt, en een controle toegevoegd op het omgevingsblok die ruw ogende inloggegevens markeert, een naam tussen accolades die geen verwijzing is, en een verwijzing naar een geheim dat niet op deze machine bestaat.
De les hier gaat helemaal niet over geheimen: een verkeerd pad in een helptekst is een beveiligingsbug. Het duwt de gebruiker naar het dichtstbijzijnde veld dat er juist uitziet.
Als je AgentsRoom niet gebruikt
De regel reist mee. Kies wat bij jouw opzet past; ze eindigen allemaal op dezelfde plek, bij een omgevingsvariabele in het proces waarin de agent draait.
- De CLI van een wachtwoordmanager.
op run -- claudemet 1Password, of het equivalent bij Bitwarden en Doppler: de tool leest verwijzingen uit een sjabloon, injecteert de echte waarden in de omgeving van het kindproces, en er wordt niets naar schijf geschreven. direnvmet een.envrcin gitignore. Simpel, lokaal, per map. Zet.envrcen.env*in.gitignorevoordat de eerste waarde erin gaat, niet erna.- Een
.env-bestand dat de agent nooit leest. Houd het buiten de repo en laad het in de shell die de agent start. Staat het bestand in de repository, dan opent de agent het vroeg of laat, citeert het in een bericht, en sta je weer op het beginpunt. - Cloud-inloggegevens: neem kortlevende. GitHub OIDC in CI in plaats van langlevende AWS-sleutels, en een workload identity in plaats van een sleutelbestand van een serviceaccount.
Wat je ook kiest, zet de regel in de instructies van de agent, zodat hij ophoudt met vragen. Eén alinea in CLAUDE.md of AGENTS.md volstaat: nooit om de waarde van inloggegevens vragen, er nooit een in een bestand schrijven, $NAME gebruiken en de gebruiker vertellen welke variabele hij moet instellen. Wij publiceren een kleine skill die precies dat zegt, en hij is vijf regels lang.
Als een sleutel al door een gesprek is gegaan
Op volgorde, en de eerste stap is niet optioneel.
- Vervang hem. Een prompt kun je niet terughalen. Ga ervan uit dat de waarde gecompromitteerd is en geef een nieuwe uit.
- Vind de transcripten. De CLI's bewaren gespreksbestanden in je thuismap. Zoek daarin naar de oude waarde en verwijder of kort in wat overeenkomt.
- Controleer de plekken die tekst kopiëren. De geschiedenis van je klembordmanager, het ticket of issue waarin de agent gebriefd is, elke samenvatting die de agent in de repository heeft geschreven, en
git log -pals de agent heeft gecommit terwijl hij hem vasthield. - En pas dan zet je de deur neer. Nieuwe waarde achter een omgevingsvariabele, verwijzing op naam, en de instructies van de agent bijgewerkt zodat de volgende het niet vraagt.
Stap 4 als laatste, want een net mechanisme rond een gelekte sleutel is alleen maar een opgeruimder lek.
Vragen die mensen echt stellen
Waarom hoort een API-sleutel nooit in een prompt aan een AI-agent?
Omdat een prompt wordt opgeslagen en doorgestuurd. Claude Code, Codex en de andere CLI's schrijven het gesprek naar een transcriptbestand op je schijf, en het bericht zelf gaat naar de modelprovider. Vanaf daar staat de sleutel ook in de geschiedenis van je klembord, in elk log dat het bericht herhaalt, en in de herhaalpogingen en subagenten die het gesprek kopiëren. Maskering op patroon vangt dingen in de vorm van sk- of glpat-, niet een databasewachtwoord dat op een gewoon woord lijkt.
Hoe gebruikt een AI-coderingsagent een geheim dat hij niet kan lezen?
Via een omgevingsvariabele. Je zet de waarde in een omgevingsvariabele van het proces waarin de agent draait, en de agent schrijft $STRIPE_SECRET_KEY in het commando in plaats van de waarde. De shell vult hem in wanneer het commando draait. De agent ziet de tekens nooit, dus komen ze nooit in zijn gesprek of in zijn transcript terecht.
Kan ik een geheim in plaats daarvan als argument op de commandoregel meegeven?
Dat kan, en dat moet je niet doen. Een commandoregel is via ps zichtbaar voor elk proces op de machine, hij gaat in de shellgeschiedenis, in proceslogs en in elk diagnosetool dat het gestarte commando uitleest. Een omgevingsvariabele van één proces is leesbaar door dat proces en door root, en dat is een veel kleiner oppervlak.
Waar bewaart AgentsRoom de geheimen die agenten gebruiken?
In één bestand in je thuismap, modus 0600, versleuteld via de sleutelhanger van je besturingssysteem, alleen op die machine. Er wordt niets naar een server gesynchroniseerd en er is geen knop om een waarde opnieuw te tonen. De app toont namen, de agent ziet namen, en de waarde wordt alleen in het hoofdproces ontsleuteld, op het moment dat een terminal of een commando start.
Wat gebeurt er als ik een verwijzing naar een geheim in een bericht aan de agent plak?
Er wordt niets ingevuld, en dat is met opzet. Een bericht, een ticketbeschrijving, een skill en een systeemprompt zijn letterlijke tekst, dus een verwijzing die daar staat komt als tekst bij de agent aan. Alleen de waarde van een omgevingsvariabele wordt ingevuld. Dat hebben we op de harde manier geleerd: een verwijzing die in een ticketbeschrijving stond kwam vroeger ingevuld terug in het eerste bericht van de agent die eruit werd gestart.
Er is al een sleutel door een gesprek gegaan. Wat doe ik?
Je vervangt hem eerst, voor alles, want je kunt hem niet terughalen: hij staat bij de provider en in het transcriptbestand op je schijf. Daarna verwijder of kort je de transcripten in die hem bevatten, controleer je je klembordmanager en doorzoek je de repository voor het geval de agent hem in een bestand heeft geschreven. Pas daarna zet je de nieuwe waarde achter een omgevingsvariabele.
De korte versie
Een agent hoeft jouw sleutel niet te lezen. Hij heeft een proces nodig waarvan de omgeving hem al bevat.
Bewaar de waarde één keer, buiten de repository, versleuteld door het besturingssysteem. Verwijs er op naam naar waar de omgeving wordt opgebouwd. Laat de agent $NAME schrijven. Houd elk ander kanaal letterlijk, ook de kanalen waar invullen handig was geweest, want de handige kanalen zijn precies de kanalen die worden opgeslagen en doorgestuurd.
En wanneer je het fout doet, en dat gebeurt, vervang eerst en ruim daarna op.
Download AgentsRoom
Draai al je AI-agenten, op al je projecten, vanuit één enkel venster.
Companion-app: houd je agents onderweg in de gaten
Breng je eigen: Claude, Codex, Antigravity CLI of andere AI-provider.
Stuur bugs en verzoeken direct naar je openbare backlog.
Verder lezen
Routines in Claude Code: wat in de cloud draait, wat op je machine blijft en hoe je kiest
Routines zijn de manier van Claude Code om een opgeslagen prompt zonder jou uit te voeren: volgens een schema, op een API-aanroep of bij een GitHub-gebeurtenis, als cloud session op een verse kloon van je repository. Ze zijn in research preview. Claude Code heeft daarnaast twee lokale manieren om werk te plannen, de geplande taken van de desktop-app en /loop, en die drie gedragen zich niet hetzelfde: minimuminterval, toegang tot je lokale bestanden, toestemmingsvragen, wat er gebeurt als de laptop slaapt. Deze gids zet de drie op een rij met de limieten uit de documentatie en legt daarna uit waarom onze eigen zeven nachtelijke agents in plaats daarvan op een lokale machine draaien.
Lees het artikelZeven AI-agents draaien onze nachten: geplande agents buiten het coderen, met de prompts
Een gebruiker vroeg ons hoe we AI-agents gebruiken voor iets anders dan code schrijven. Sinds 28 augustus starten elke avond zeven geplande agents op een Mac mini: een CEO van dienst, een SEO-team, een productmanager, een bugfixer, een socialmediateam, een documentalist en een rapporteur die een samenvatting van 25 regels mailt. 33 nachten, 31 ochtendmails, 51 opgeloste bugs met een commitlink, 7 blogartikelen in 20 talen. Wat elk van hen doet, hoe ze werk aan elkaar doorgeven zonder te praten, welk model welk werk doet, de vier regels die hun prompts moesten leren, en de prompts zelf, klaar om te kopiëren.
Lees het artikelEén Claude-account op twee computers, of met z’n tweeën: wat werkt
Met één Claude-account log je in op al je apparaten, en ze putten allemaal uit dezelfde limieten van 5 uur en per week, Claude en Claude Code samen. Met twee mensen op één account ga je in tegen de voorwaarden van Anthropic, en er bestaat geen gezinsabonnement. Wat we op 25 oktober 2026 hebben gecontroleerd, en hoe je ziet welke computer wat heeft verbruikt.
Lees het artikel