← Blog

Security

AI-keys auditen: checklist voordat je agents live gaan

Een staging key in een publieke GitHub Action verbrandde €11.800 aan andermans prompt-verkeer in twee dagen. Dit is de audit die we nu draaien voor elke agent live gaat.

Jacob Molkenboer· Oprichter · A Brand New Company· 11 jun 2026· 8 min
Messing sleutel op linnen grootboek, envelop met groen lint en lakzegel op ivoorpapier, zijlicht van raam.

Op een dinsdagmiddag stuurde een founder ons een bericht: het Anthropic-dashboard had hem net gemaild over "ongebruikelijk gebruik". Het bedrag op de factuurpreview was €11.800. De maand ervoor had hij €340 uitgegeven. De agent die hij aan het bouwen was stond nog in staging, was nog niet live en had nog geen enkele echte klantprompt gezien.

We hadden het binnen een uur teruggetraced. Een staging key was acht weken eerder in een GitHub Actions workflow geschreven, tijdens een haastige CI-fix. Het workflow-bestand stond in een publieke repo. De key was twee dagen lang stilletjes door iemand anders gebruikt voordat het rate-limit alert afging. Anthropic betaalde het grootste deel terug na een vriendelijk supportticket, maar de founder verloor een week slaap en wij verloren een geplande sprint aan incident response.

Sindsdien draaien we een vaste audit op de keyinventaris van elke sub-€20M SaaS-founder voordat we een agent die we bouwen het productieverkeer laten raken. Het kost ongeveer negentig minuten. Het heeft bij elke klant iets gevonden. Hier is het hele ding.

De blast-radius vraag, eerst gesteld

Voordat we naar een enkele key kijken, stellen we één vraag: als deze key nu zou lekken, wat is het worst case in euro's over achtenveertig uur? Niet "is het mogelijk dat hij lekt". Ga ervan uit dat hij al gelekt is. Wat stopt het bloeden?

Voor de meeste founders is het eerlijke antwoord: de fraudedetectiemail van de provider, uiteindelijk. Dat is geen control. Dat is hoop. Een gelekte key zonder spend cap per key en met volledige org-toegang kan vijf cijfers verbranden voordat een mens het opmerkt. We hebben het inmiddels twee keer zien gebeuren in onze klantenportefeuille. De tweede keer was een vrijdagavondlek dat het hele weekend doorliep.

Als je het dashboard, het budget en het alerting-kanaal niet kunt benoemen die binnen een uur het bloeden zouden stoppen, dan heb je geen key-hygiëne. Dan heb je een key-spreadsheet.

Stap één: inventariseer elke key die bestaat

Je kunt niet auditen wat je niet kunt opnoemen. We beginnen met read-toegang tot de Anthropic en OpenAI consoles, plus elk van de kleinere providers die in gebruik zijn (Mistral, Together, Groq, Fireworks). Voor elk halen we de volledige API key-lijst op en dumpen die in één sheet met vijf kolommen: provider, key prefix, label, aanmaakdatum, laatst gebruikt.

De Anthropic console laat dit direct zien onder Settings → API Keys. Het OpenAI-equivalent staat op platform.openai.com/api-keys. Beide tonen de last-used timestamp, en dat is de meest bruikbare kolom op de sheet.

Daarna sorteren we op last used en stellen we een keiharde vraag over elke key ouder dan dertig dagen zonder verkeer: wie heeft deze aangemaakt, waarvoor was hij, en kunnen we hem nu verwijderen? Ongeveer een derde van de keys in een doorsnee founder-account zakt door die vraag. Het zijn restanten van een Replit-experiment, een Zapier-test, een Vercel preview die nooit is opgeruimd. Elk daarvan is een levende credential.

Waarschuwing

Een key met nul gebruik over zestig dagen is niet "veilig omdat hij ongebruikt is". Het is een credential zonder eigenaar die zou opmerken als hij ineens gebruikt wordt. Verwijder hem.

Stap twee: grep de wereld af op prefixes

Dit is het deel dat niemand leuk vindt. Voor elke key in de inventaris zoeken we op zijn prefix (de eerste tien of twaalf karakters, bijvoorbeeld sk-ant-api03- plus de volgende acht) op:

  • De volledige GitHub-organisatie van de klant, inclusief private repos, gists en Actions-logs.
  • Elke .env, .env.local, .env.staging op elke dev-laptop met toegang.
  • De Vercel, Netlify, Railway, Fly en Render dashboards.
  • Slack, Notion, Linear en elke interne wiki. Vooral Slack-DM's.
  • De browsergeschiedenis en password manager van de founder. Ja, echt.

GitHub's secret scanning vangt de voor de hand liggende gevallen voor de grote providers, maar het is een vangnet, geen control. Push protection triggert alleen op push, niet op al gecommitte history, en het ziet geen Actions-logs of private gists. De grep is handmatig en saai en hij werkt.

#!/usr/bin/env bash
# Find any Anthropic or OpenAI key prefix across the org.
# Requires: gh CLI, jq, the user's org token with repo:read.

ORG="$1"
PATTERNS=("sk-ant-api03" "sk-ant-api04" "sk-proj-" "sk-svcacct-")

for repo in $(gh repo list "$ORG" --limit 500 --json nameWithOwner -q '.[].nameWithOwner'); do
  for pat in "${PATTERNS[@]}"; do
    hits=$(gh api -X GET search/code \
      -f q="$pat repo:$repo" \
      --jq '.items[].path' 2>/dev/null)
    if [ -n "$hits" ]; then
      echo "== $repo : $pat =="
      echo "$hits"
    fi
  done
done

Draai dat tegen elke org die de founder bezit. Draai daarna trufflehog over dezelfde set voor de keys waarvan je nog niet weet, en die zijn er.

Stap drie: scope elke key naar één ding

Zodra de inventaris schoon is, krijgt elke overlevende key één taak. We staan niet toe dat een key met label "prod" ook gebruikt wordt door het lokale script van een developer. We staan niet toe dat dezelfde key in staging en CI staat. De regel is: één key, één omgeving, één workload, één eigenaar.

Op Anthropic betekent dat workspace-scoped keys met een duidelijke naamgevingsconventie: prod-api, staging-ci, dev-jacob-local. Op OpenAI doen project-scoped keys hetzelfde. Beide laten je een maandbudget per key instellen; beide mailen je als je een percentage daarvan raakt. Wij zetten de limiet op 1,5x de hoogste legitieme maand die we hebben gezien. Niet 10x. Niet "ongelimiteerd voor de zekerheid". 1,5x.

Het incident van €11.800 zou op zo'n €450 zijn afgetopt als de staging key zo gescoped was geweest. We weten dat omdat we het achteraf hebben uitgerekend terwijl de founder om 1 uur 's nachts koude pizza zat te eten.

Stap vier: alles via één eigen server

De sterkste control is de control die de meeste founders overslaan omdat het voelt als over-engineering. Je applicatiecode hoort helemaal geen provider API keys vast te houden. Hij hoort je eigen server aan te roepen, die de provider aanroept. De providerkey woont op de server, idealiter in een secrets manager, en de server forceert een spend cap per user en per feature die je auth-systeem toch al begrijpt.

Dat klinkt als veel werk. Voor de meeste stacks is het twee bestanden en een middag. Dit is de minimale vorm die we shippen voor klanten op Supabase, wat ongeveer de helft van onze portefeuille dekt:

// supabase/functions/anthropic-proxy/index.ts
// verifySupabaseJwt, getTodaysSpendEur, recordSpend are the project's own
// auth helper + spend ledger. Wire them to your existing auth and a usage
// table; ten lines each in our reference build.
import Anthropic from "npm:@anthropic-ai/sdk";

const anthropic = new Anthropic({ apiKey: Deno.env.get("ANTHROPIC_KEY")! });
const DAILY_CAP_EUR = 5;

Deno.serve(async (req) => {
  const auth = req.headers.get("authorization");
  const user = await verifySupabaseJwt(auth);
  if (!user) return new Response("unauthorized", { status: 401 });

  const spent = await getTodaysSpendEur(user.id);
  if (spent >= DAILY_CAP_EUR) {
    return new Response("daily cap reached", { status: 429 });
  }

  const { messages, model } = await req.json();
  const reply = await anthropic.messages.create({
    model: model ?? "claude-sonnet-4-5",
    max_tokens: 1024,
    messages,
  });

  await recordSpend(user.id, reply.usage);
  return Response.json(reply);
});

Dat is de hele verdediging. Een gelekt client-token topt nu af op €5 per user per dag, niet €11.800 per weekend. De providerkey verlaat de edge function nooit. Het audit log schrijft zichzelf.

Stap vijf: roteer op een kalender, niet in paniek

Kwartaalrotatie, op een vaste datum in de kalender, met een checklist. Het punt is niet dat negentig dagen een magisch getal is; het punt is dat je de rotatie hebt geoefend bij rustig weer, zodat je een runbook hebt en geen stressreactie wanneer je echt om 2 uur 's nachts moet roteren omdat de laptop van een contractor is gestolen.

De checklist is klein: nieuwe key genereren in de console, schrijven naar secrets manager, redeployen, verkeer op de nieuwe key verifiëren, oude key revoken, inventaris-sheet bijwerken. Zes stappen. We hebben een template waar ons team mee werkt; we publiceren hem als gist als iemand erom vraagt.

Stap zes: alert op de tweede afgeleide

Provider-dashboards alerten op absolute spend. Dat is te traag. Tegen de tijd dat je €500-alert afgaat, draait een aanvaller al uren. Alert in plaats daarvan op de rate of change: elk uur waarin de spend meer dan 5x de mediaan over de afgelopen 24 uur is, pieper iemand op. Dat vangt de €11.800-case in zo'n veertig minuten in plaats van veertig uur.

Als je geen metrics-pipeline hebt, is de goedkope versie een cron die elk kwartier het usage-endpoint van de provider raakt en naar een Slack-webhook post als de delta een drempel overschrijdt. De dure versie is hetzelfde ding in Grafana. Het principe is identiek.

Takeaway

De fraudedetectie van een provider is niet jouw incident response. Als je enige alarm de mail van billing is, betaal je voor andermans verkeer tot zij het opmerken.

En de nieuwe retentieregels?

De recente wijziging in Anthropic's consumer privacy policy over data-retentie staat los van key-hygiëne, maar komt op dezelfde tafel terecht. Als je gebonden bent aan een klant-DPA die nul retentie belooft, audit dan welke model-ID's je vandaag daadwerkelijk vanuit productie aanroept en check elke ID tegen het huidige beleid van de provider. Zet de model-ID in je inventaris-sheet naast de key. We hebben founders per ongeluk hun default model zien upgraden in een dependency bump en de retentiewijziging pas drie maanden later horen in een security review van een klant.

Het kleinste ding dat je vandaag kunt doen

Toen we vorig kwartaal de email-responder agent bouwden voor een Nederlandse logistieke klant, liepen we precies hiertegenaan: drie vergeten staging keys, twee daarvan in een publiek Actions-log. We hebben het opgelost door de bovenstaande audit te draaien en elke call via één Supabase edge function te routeren met een dagelijkse cap per tenant. Wil je hetzelfde op jouw stack, dan is dat het soort werk dat we doen onder AI-agents.

Vandaag, voordat je dit tabblad sluit: open je Anthropic-console, sorteer je keys op last-used datum en verwijder elke key zonder verkeer in de laatste zestig dagen. Het is een vijfminutenklus en het is de hefboomrijkste actie op deze lijst.

Kern

Als het enige vangnet voor een gelekte key de fraudedetectiemail van de provider is, dan heb je geen key-hygiëne. Dan heb je hoop, en die hoop heeft een vijfcijferige failure mode.

FAQ

Hoe is dat lek van €11.800 nu eigenlijk gebeurd?

Een staging API key was hardcoded in een GitHub Actions workflow in een publieke repository, tijdens een CI-fix. Iemand die publieke Actions-configs aan het scrapen was vond hem en gebruikte hem twee dagen voordat de rate-limit detectie van de provider de eigenaar mailde.

Heb ik echt een proxy-server voor de provider nodig?

Als je app een backend service is die de key al server-side bewaart, nee, scoping en budgets zijn genoeg. Als client-side code, een mobiele app of een browserextensie een provider aanraakt: ja. Anders is de key één devtools-tabblad verwijderd van publiek zijn.

Kan GitHub secret scanning deze audit vervangen?

Nee. Het vangt voor de hand liggende commits op de default branch bij ondersteunde providers, maar het dekt geen Actions-logs, private gists, Slack-berichten of developer-laptops. Behandel het als vangnet, niet als control.

Wat is de juiste spend cap op een productie-key?

1,5x de hoogste legitieme maand die je in de laatste zes maanden hebt gezien. Niet 10x, niet ongelimiteerd. Als je daar echt overheen groeit, krijg je een nette 429 en verhoog je de cap bewust, in plaats van het te ontdekken via een factuur.

Hoe vaak moet ik keys roteren?

Per kwartaal op een vaste kalenderdatum is de ondergrens. De frequentie maakt minder uit dan dat je team de rotatie heeft geoefend toen er niets in brand stond, zodat de noodrotatie om 2 uur 's nachts een runbook heeft.

securityai agentsoperationstoolingarchitecture

Iets bouwen?

Start een project