Process automation
n8n praktijkverhaal: een Drive-scope verstopte 287 PDF's
Een Haarlems mediabureau draaide n8n elke zondag om klantrapporten in Drive-mappen te zetten. Het dashboard bleef groen, terwijl 287 PDF's op de verkeerde plek belandden.

Op een maandag om 09:11 liep de oprichter van een mediabureau van 21 mensen aan de Spaarne haar kantoor in en zag drie van haar accountmanagers naar hetzelfde scherm staren. Hun grootste vaste klant had gemaild: "Waarom staan de rapporten van vorige week in de Drive-map van iemand anders? En waarom staat er een mediaplan van een Duits dierenvoermerk in die van ons?"
Haar n8n-dashboard stond op groen. Dertien dagen lang. In dat venster had de wekelijkse klantrapportage-workflow van het bureau 287 PDF's naar de verkeerde shared-drive-mappen verplaatst, voornamelijk naar de eigen My Drive van het service account, waar niemand aan gedacht had te kijken. De Google Drive API gaf nooit een 4xx terug. n8n gooide geen enkele error. De workflow draaide 41 keer, allemaal gemarkeerd als geslaagd.
Wij kwamen op dag veertien aan tafel. Dit is wat we vonden.
Hoe de workflow in elkaar zat
Het bureau had een keurige wekelijkse automatisering gebouwd in n8n, self-hosted op een Hetzner-bak. Elke zondag om 22:00 startte een Cron-node een fan-out die per klant zes dingen deed:
- De metrics van vorige week ophalen uit Meta, Google Ads en GA4.
- Een PDF renderen met een Carbone-template.
- De Drive-map van de klant op naam zoeken in hun Shared Drive.
- De PDF uploaden via de Google Drive-node, met die map als parent.
- Een Slack-bericht plaatsen in #client-reports met een permalink.
- Een rij toevoegen aan een tracking sheet.
De auth was een service account met domain-wide delegation, twee jaar eerder opgezet door een freelancer die zijn mail niet meer beantwoordde. Het werkte perfect. Tot het niet meer werkte.
De scope die onder hun voeten veranderde
Dertien dagen voor de inbox ontplofte, had de IT-verantwoordelijke van het bureau gedaan wat elke Workspace-admin verteld wordt te doen. Hij had third-party OAuth-apps in de Admin Console nagelopen en scopes aangescherpt op alles wat ruim leek. De client ID van het n8n-service account gebruikte https://www.googleapis.com/auth/drive, de volledige scope. Hij wijzigde dat naar https://www.googleapis.com/auth/drive.file, wat Google expliciet aanbeveelt als least-privilege optie.
De drive.file scope laat de app alleen bestanden zien en aanpassen die het zelf heeft aangemaakt, of bestanden die expliciet via een Google Picker zijn gedeeld. Al het andere, inclusief de klantmappen in de Shared Drive van het bureau, wordt onzichtbaar voor het service account. Niet verboden. Onzichtbaar.
De IT'er sloeg zijn wijziging op. Er ging niets af. De Drive-credential van n8n bleef werken omdat de OAuth-refresh nog steeds slaagde. De token kwam alleen terug met een smallere scope-claim. Geen 4xx, want er werd niets afgewezen. De spelregels waren veranderd en het spel speelde door.
Waarom de API nooit nee zei
Dit is het stuk waar operators op stuklopen, dus ik zeg het maar gewoon. Een OAuth-scope verkleinen breekt bestaande tokens niet met terugwerkende kracht. Het verandert wat nieuwe tokens mogen doen. Met drive.file kon het service account nog steeds:
- Authenticeren.
- Nieuwe bestanden aanmaken, die dan "door de app aangemaakte bestanden" worden.
- Die bestanden later lezen en aanpassen.
'root'als parent oplossen, oftewel zijn eigen My Drive-root.
Wat het niet meer kon, was mappen zien die het niet zelf had aangemaakt. Dus de stap "zoek map op naam" op positie 3 gaf een leeg resultaat terug. Geen error. Gewoon een lege lijst.
Hier schoot de workflow zichzelf in de voet. De map-lookup voedde een Set-node die de parent-ID bepaalde, met een defensieve fallback:
// In the Set node, "Folder ID" field
{{ $json["files"][0]["id"] || "root" }}
Die fallback was maanden eerder geschreven voor het scenario "wat als een nieuwe klant nog geen map heeft, laat het dan in root landen en we sorteren het later wel handmatig". Hij was bedoeld om nooit te vuren. Toen de scope veranderde, vuurde hij voor elke klant, elke week.
De Drive-upload-node liep daarna met parents: ["root"] en supportsAllDrives: true. De API schreef het bestand braaf weg. Naar de eigen My Drive van het service account, waar nog nooit iemand op had ingelogd. 200 OK. Slack kreeg een permalink die, als je erop klikte, een 404 gaf omdat geen mens toegang had tot dat bestand. Maar niemand klikte op die permalinks. De workflow stond al twee jaar op groen en het team had het kijken opgegeven.
Elke expressie in je automatisering die eindigt op || 'root', ?? defaultFolderId of || process.env.FALLBACK_ID is een wachtlijst voor stille fouten. Defensieve defaults maken van ontbrekende data foute data.
Wat de run-historie eigenlijk liet zien
Het execution log van n8n is eerlijk. Het legt vast wat elke node teruggaf. De Drive-search-node gaf [] terug. De Set-node gaf { folderId: "root" } terug. De Drive-upload-node gaf een geldige file resource terug. Elke stap slaagde. Het workflow-statusveld zei "success" omdat geen enkele node iets gooide.
Als je de logs had gelezen, had je het op dag één gezien. Niemand leest de logs van een workflow die al twee jaar op groen staat. Dat is de echte les, en die heeft niets met Google of n8n te maken. Het systeem deed precies wat het opgedragen kreeg. Niemand had bedacht om te vragen wát het deed.
Wat we hebben veranderd
Stille default eruit. De || "root" fallback werd een harde error. Geeft de map-lookup leeg terug, dan gooit de workflow nu een fout en gaat de Slack-melding naar #ops, niet naar #client-reports.
Contract-check aan de top. Voor er ook maar één klant verwerkt wordt, lijst de workflow de verwachte klantmappen op naam op en checkt of het aantal klopt met de tracking sheet. Is een map onzichtbaar geworden, dan stopt de workflow bij de eerste klant en gilt.
Scope expliciet vastgezet. De n8n-credential vraagt nu drive aan, en we hebben in een comment bij de Workspace Admin Console-entry vastgelegd waarom. Least-privilege klopt in principe, maar dan moet er ook een echte access-map naast liggen. We hebben een driemaandelijkse review toegevoegd waarin IT en de automation-eigenaar samen de boel tegen elkaar leggen.
Canary aangezet. Elke run schrijft nu één extra bestand, een health-PDF van één regel, in een bekende canary-map en leest die terug. Geeft de read een 404, dan faalt de workflow hardop. Dit vangt drift precies op de plek waar drift ontstaat.
De 287 PDF's terughalen was het makkelijke stuk. Het service account was eigenaar. We hebben via domain-wide delegation impersonation een human user toegevoegd, de bestanden in batch naar hun juiste bestemming verplaatst, en het bureau stuurde de betreffende klanten één alinea uitleg over wat er gebeurd was.
Hoe deze bug eruitziet, in het algemeen
Draai je een automatisering die een third-party API met scoped auth aanroept, dan is dit het patroon om in de gaten te houden. Ergens buiten je code verandert een config: admin console, OAuth consent screen, IAM-rol, billing tier. Je workflow draait door omdat er niks wordt afgewezen. De 4xx waar je op rekende komt nooit, want de wijziging heeft je in een kleinere permissie-set gezet waar de nieuwe, kleinere operaties nog steeds geldig zijn.
Je kunt de config-wijziging niet voorkomen. Je kunt alleen je workflow laten merken dat de wereld die hij verwacht niet meer bestaat. Dáár zijn contract-checks en canary-writes voor. Een automatisering die al twee jaar op groen staat is geen werkende automatisering. Het is een automatisering die al twee jaar door niemand getest is.
Wat we voor ze hebben gebouwd
Toen we de rapportage-workflow voor het Haarlemse bureau opnieuw bouwden, liepen we ertegenaan dat de Drive-node van n8n niet laat zien welke OAuth-scope hij op de token kreeg. We hebben uiteindelijk een kleine pre-flight node toegevoegd die tokeninfo aanroept en de scope-string assert voor er ook maar één andere stap draait. Die check van twee seconden zit nu in elke n8n process automation die we opleveren met een Workspace-afhankelijkheid.
Dit is het kleinste wat je vandaag kunt doen. Open de run-historie van je belangrijkste automatisering. Pak een geslaagde run van vorige week. Lees de output van elke node. Kun je niet alleen aan de JSON zien of de workflow het juiste deed, dan is je groene dashboard decoratie.
Kern
Een automatisering die al twee jaar op groen staat is geen werkende automatisering. Het is een automatisering die al twee jaar door niemand getest is.
FAQ
Wat is een Google Drive OAuth-scope downgrade?
Een wijziging in de scopes die een app of service account mag aanvragen, waardoor nieuwe tokens minder rechten krijgen. Bestaande tokens blijven werken tot ze verlopen; nieuwe komen smaller terug.
Waarom gaf de Drive API geen 4xx-error terug?
Het service account had nog een geldige token en kon nog uploaden. Het kon alleen de doelmappen niet zien. Een upload naar fallback-parent 'root' slaagde, dus antwoordde de API met 200.
Hoe vang ik dit soort stille fouten af in n8n?
Voeg een pre-flight node toe die het tokeninfo-endpoint aanroept en de OAuth-scope-string assert. Combineer dat met een canary-write naar een bekende map en lees het bestand terug. Geeft de read een 404, laat de workflow dan luid falen.
Is drive.file altijd de verkeerde scope?
Nee. drive.file is goed voor apps die alleen aan bestanden komen die ze zelf hebben aangemaakt. Het is fout voor automatiseringen die moeten schrijven in mappen die op een andere manier met het service account gedeeld zijn, zoals via Shared Drive-membership.
Kan ik bestanden terughalen die in de My Drive van een service account zijn beland?
Ja. Gebruik domain-wide delegation om een Workspace-gebruiker te impersoneren, lijst de bestanden van het service account op en verplaats of herdeel ze. De bestanden zijn niet weg, alleen wees in een account dat niemand bezit.