← Blog

Security

OAuth-audit voor AI-agents: de checklist die we eerst draaien

Een vergeten Microsoft Entra app-secret lekte elf maanden lang Outlook-bijlagen van een architectenbureau. De audit die we nu draaien voordat een agent productie raakt.

Jacob Molkenboer· Oprichter · A Brand New Company· 11 jan 2025· 9 min
Messing sleutel op verzegelde envelop, groen label, open leren logboek, geopend hangslot op linnen.

De Entra-app zonder eigenaar

De pre-flight moest een formaliteit zijn. Een Nederlands architectenbureau had de inbox-triage-agent goedgekeurd, de deployment stond gepland voor maandag, en wij liepen op vrijdagmiddag de security-walkthrough door. De ops-lead opende het Entra admin-portaal, sorteerde enterprise apps op aanmaakdatum, en klikte alles open wat onbekend leek.

Halverwege de lijst: Mail Export Test 2024. Geen beschrijving. Het owner-veld leeg. Eén client secret, geldig tot 2027. Eén application permission: Mail.Read, tenant-breed, met admin-consent. Het audit log liet zien dat de app al elf maanden berichten en bijlagen ophaalde uit elke mailbox in de tenant. Stilletjes. In een gestaag tempo. Niemand kreeg een melding, want niemand had alerts ingesteld op een app waarvan niemand het bestaan kende.

De stagiair die hem had geregistreerd, was vorig jaar oktober weg. Het secret dat hij had aangemaakt had een levensduur van twee jaar, want dat was de standaard toen hij op de knop drukte. De exports gingen naar een Cloudflare R2-bucket die het bureau nooit had bezeten.

Die maandag hebben we de agent niet uitgerold.

Hoe agents het OAuth-kerkhof erger maken

De meeste bedrijven die ons vragen een AI-agent te bouwen, hebben een OAuth- en service-account-inventaris waar al jaren niemand naar gekeken heeft. De agent is niet de oorzaak. De agent is de aanleiding die iemand eindelijk dwingt te kijken.

Er veranderen een paar dingen zodra een agent in beeld komt. De agent vraagt doorgaans bredere read-scope dan een menselijke integratie. Een inbox-triage-agent moet threads zien die niet direct naar hem zijn doorgestuurd. Een agenda-agent heeft Calendars.ReadWrite nodig, niet alleen de meeting waarvoor hij is uitgenodigd. De identiteit van de agent staat vervolgens in je audit log naast elke andere OAuth-app die je hebt. Als de vorige vijftig apps in je tenant geen eigenaar hebben, niet geroteerd worden en niet gemonitord worden, erft je nieuwe agent een security-houding die door de slechtste daarvan is bepaald.

Juridisch verschuift het beeld ook. Onder de AVG ben jij de verwerkingsverantwoordelijke voor wat je agent in je tenant doet, ongeacht wie de app oorspronkelijk registreerde of welke stagiair nog een verouderd client secret heeft in een abonnement dat niemand opent. Als jouw agent de verkeerde mailbox leest of de verkeerde klant beantwoordt, houdt "het secret van de stagiair lekte" op als verdedigbaar antwoord.

Waarschuwing

Als je audit-log-retentie korter is dan de tijd die je nodig hebt om een lek op te merken, is je audit log toneel. Check dat getal voordat je iets anders checkt.

De audit, van boven naar beneden

We draaien elf checks over de identity providers van de klant en alle service accounts in hun cloud projects. De lijst hieronder gaat uit van een Microsoft 365-tenant en één Google Cloud-project. De structuur vertaalt zich direct naar AWS IAM, Okta, Google Workspace en self-hosted OAuth-servers.

1. Inventariseer elke app en elke key

Je kunt niet auditen wat je niet kunt opsommen. Het eerste artefact dat we opleveren is één CSV met aan de ene kant elke Entra app-registratie en elke enterprise app, en aan de andere kant elk Google Cloud service account en elke Workspace OAuth-client.

Connect-MgGraph -Scopes 'Application.Read.All','AuditLog.Read.All','Directory.Read.All'

Get-MgApplication -All | ForEach-Object {
  $owners = (Get-MgApplicationOwner -ApplicationId $_.Id).AdditionalProperties.userPrincipalName
  [PSCustomObject]@{
    DisplayName    = $_.DisplayName
    AppId          = $_.AppId
    Owners         = ($owners -join ',')
    SecretCount    = $_.PasswordCredentials.Count
    NextExpiry     = ($_.PasswordCredentials.EndDateTime | Sort-Object | Select-Object -First 1)
    AppPermissions = ($_.RequiredResourceAccess.ResourceAccess.Id -join ',')
  }
} | Export-Csv -Path entra-apps.csv -NoTypeInformation

Die CSV is de ruggengraat van de audit. Elke andere check vult een regel aan.

2. Koppel elke identiteit aan een huidige menselijke eigenaar

Voor elke regel in de CSV: noem een medewerker die nu nog bij het bedrijf werkt en die voor de app kan instaan. Geen team. Geen Slack-kanaal. Een persoon.

Regels zonder eigenaar zijn de regels die Mail Export Test 2024 worden. Regels waarvan de eigenaar weg is, zijn functioneel hetzelfde. Onze ervaring: dat is tussen de twintig en veertig procent van elke tenant ouder dan drie jaar.

3. Krimp de scopes

Per app: lijst de scopes die hij nu heeft en de scopes die hij daadwerkelijk gebruikt. Het Microsoft Graph audit log laat zien welke permissions een app de laatste dertig dagen heeft aangesproken; Google Cloud Audit Logs doen hetzelfde voor service accounts.

De meeste apps gebruiken één of twee van de tien scopes die ze hebben aangevraagd. Vervang de brede scopes (Mail.Read, Files.ReadWrite.All) door de smallere equivalenten (Mail.Read.Shared, Files.SelectedOperations.Selected). De Graph permissions-referentie van Microsoft noemt de granulaire alternatieven voor elke veelvoorkomende scope.

4. Beperk de levensduur van secrets en keys

De Entra-default voor een nieuw client secret is twee jaar. De Google service-account JSON key staat default op oneindig. Allebei fout. Stel een tenant-policy in die de levensduur van secrets capt op negentig dagen voor non-interactive apps en roteer volgens schema, of stap over op managed identities en workload identity federation zodat er geen secret meer te roteren valt. De eigen key-management-richtlijn van Google legt uit waarom langlopende JSON keys vragen om problemen.

gcloud iam service-accounts list --format='value(email)' \
  | while read sa; do
      gcloud iam service-accounts keys list \
        --iam-account="$sa" \
        --filter='keyType=USER_MANAGED' \
        --format='csv[no-heading](name,validAfterTime,validBeforeTime)' \
        | sed "s|^|$sa,|"
    done > gcp-sa-keys.csv

5. Beperk waar credentials vandaan gebruikt mogen worden

Microsoft en Google ondersteunen allebei het binden van een client credential aan een set IP-ranges of aan een workload identity. Als je agent in één Kubernetes-namespace of één Vercel-deployment draait, mogen de credentials die hij gebruikt niet werken vanaf een laptop in een koffietent. Schrijf de conditional access policy. Test of die ook echt blokkeert.

6. Controleer de retentie van je audit log

Zoek vandaag uit hoe lang je tenant OAuth app audit events bewaart. Microsoft houdt het kort op de lagere tiers en langer op E5; Google Cloud Admin Activity logs zijn gratis en worden vierhonderd dagen bewaard, terwijl Data Access logs standaard uit staan. Als de retentie korter is dan je gemiddelde tijd-tot-opmerken, fix dat dan voordat je iets anders fixt.

7. Zet rate limits per app

Een legitieme inbox-agent leest gemiddeld één tot vijf threads per minuut per gebruiker. Een gecompromitteerde probeert de hele mailbox te spiegelen. De meeste providers bieden een rate limit op het app-object. Zet 'm. Een beetje throttling tijdens een piek kost veel minder dan het doorgeefluik zijn voor een exfiltratiepiek.

8. Tag de identiteit van de agent

Bij het aanmaken van de identiteit van de agent: tag 'm zodat hij in het audit log te onderscheiden is van menselijke OAuth-flows. Wij gebruiken een purpose=agent extension attribute op Entra service principals en een purpose: agent label op GCP service accounts. De audit-query die je om 2 uur 's nachts op een zaterdag schrijft, gaat veel sneller als "alle agent-activiteit van het afgelopen uur" één filter is.

9. Schrijf de noodknop

Vóór deployment moet de runbook die de credentials van de agent intrekt en zijn account uitschakelt, bestaan, getest zijn, en uitvoerbaar zijn door iemand die niet de engineer is die de agent gebouwd heeft. Zestig seconden tussen "er gaat iets mis" en "de agent kan niets meer lezen" is de norm. We oefenen dat in de pre-flight.

10. Zet de anomalie-alerts aan

Microsoft Defender for Cloud Apps en het Alert Center van Google Workspace leveren allebei regels voor afwijkend OAuth-gebruik. Zet ze aan. Stuur de alerts naar een kanaal dat in het weekend gelezen wordt. De reden dat Mail Export Test 2024 elf maanden draaide, is dat niemand de alert had aangesloten.

11. Ontmantel de doden

Elke audit produceert een lijst apps die niet zouden moeten bestaan. Apps gekoppeld aan ex-medewerkers. Secrets die twee jaar niet zijn gebruikt. Service accounts die geen enkele workload claimt. Verwijder ze. Geen "uitschakelen", geen "later bekijken". Verwijderen. Als iemand schreeuwt, heb je net de daadwerkelijke eigenaar gevonden, en die informatie had je toch al nodig.

Hoe het rapport er echt uitziet

De output van de audit is één Markdown-document dat de klant aftekent. Drie kolommen triage: keep (met de wijzigingen hierboven), rotate (gebruikt maar verkeerd scoped), kill (geen eigenaar, geen verkeer, of beide).

Voor het architectenbureau leverde de eerste audit 134 identiteiten op. Kill: 41. Rotate: 67. Behouden: 26. Het Outlook-lek was de ergste vondst, maar niet de enige. Twee service accounts in hun billing GCP-project hadden de Owner-rol en JSON keys aangemaakt in 2021. Vier Power Automate-flows draaiden onder het persoonlijke Microsoft-account van een developer die twee jaar eerder was vertrokken.

De agent ging drie weken later in productie, in een tenant die voor het eerst in lange tijd in kaart was gebracht.

Eén ding voor vandaag

Open je Entra-portaal (of je Google Cloud IAM-pagina, of je Okta admin console) en sorteer op aanmaakdatum, oudste eerst. Klik de tien oudste non-Microsoft-entries open. Beantwoord voor elk twee vragen: wie binnen het bedrijf is hier de eigenaar van, en wanneer is het secret voor het laatst geroteerd. Als je dat voor meer dan twee van de tien niet kunt beantwoorden, heb je dezelfde audit in handen als het architectenbureau, en moet je 'm draaien voordat de volgende agent ook maar in de buurt van productie komt.

Toen wij de inbox-agent voor dat bureau bouwden, was de les dat de agent zelf het makkelijke deel was. Hun tenant klaarmaken voor nieuwe automatisering kostte meer tijd dan het bouwen van de AI-agents erbovenop, en dat was het werk dat de uitrol echt veilig maakte.

Kern

Voordat een AI-agent productie raakt, audit elke OAuth-app en elk service account dat er al was. De agent is zelden de zwakke schakel; het kerkhof eromheen meestal wel.

FAQ

Welke vinding heeft de hoogste prioriteit om als eerste op te lossen?

Application-level Mail.Read (of het Google-equivalent) dat tenant-breed is verleend aan een app waarvan de eigenaar weg is. Dat is een jaar aan bijlagen dat klaarstaat om gelekt te worden.

Hoe lang duurt de audit in de praktijk?

Een halve dag voor een tenant met minder dan vijftig registraties en één cloud project. Een week voor een enterprise met tien jaar OAuth-wildgroei. De export-queries draaien in minuten; de menselijke triage neemt de tijd.

Kunnen we niet gewoon vertrouwen op de opgegeven permissielijst van de AI-agent-leverancier?

Hun opgegeven scopes kun je vertrouwen. De rest van de tenant niet. De agent voegt zich bij een stapel bestaande identiteiten, en die stapel is wat gecompromitteerd raakt.

Werkt dezelfde checklist voor Google Workspace en AWS?

Ja. Lijst service accounts en OAuth-clients, check de leeftijd van keys, koppel aan eigenaren, cap de levensduren, beperk per workload. Het Microsoft-voorbeeld is alleen waar het ergste lek dat we hebben gezien plaatsvond.

En als de audit onze agent-launch met weken vertraagt?

Dan deed de audit zijn werk. Een agent lanceren bovenop een onbeheerde tenant is hoe je een productiviteitsproject in een incident-response-project verandert. Drie weken vertraging is beter dan drie jaar bijlagen in iemand anders' bucket.

securityai agentsoperationsintegrationsarchitectureworkflow

Iets bouwen?

Start een project