Kort samengevat. We draaien veel audits op apps gebouwd met Cursor, Claude Code, Bolt, Lovable en lange ChatGPT-sessies. De codebases verschillen enorm, maar de bevindingen vrijwel nooit. Elf problemen duiken steeds weer op, ruwweg op volgorde van hoe hard ze bijten: hardgecodeerde geheimen en inloggegevens, geen invoervalidatie (het injectieoppervlak), authenticatie die het vakje afvinkt maar niet het verzoek, nul testdekking, geen foutafhandeling op het ongelukkige pad, N+1-query's en prestaties aan het toeval overgelaten, verouderde afhankelijkheden met bekende CVE's die ongepatcht blijven, gedupliceerde variabelen en functies, geen consistente architectuur, complexe asynchrone state ingeklapt tot callbackchaos in plaats van stromen, en geen besef van de uitrolomgeving. Niets hiervan is exotisch. Het is allemaal voorspelbaar — en het is allemaal te herstellen zonder herbouw. Dit is de technische tegenhanger van onze gids voor oprichters over een AI-prototype opleveren; wil je het uit handen geven, dan is dat wat een audit van AI-code doet.


Wat een audit is — en niet is

Een audit is geen herbouw, en het is geen oordeel over of je de app wel met AI had moeten bouwen. Het is een gestructureerde lezing van een werkende codebase die één vraag beantwoordt: wat gebeurt er de eerste keer dat dit een echte aanvaller ontmoet, een echte verkeerspiek, of een echte wijziging over een half jaar? De bevindingen hieronder zijn geen hypothetische categorieën uit een checklist — elk daarvan is iets wat we in een echte opdracht hebben gevonden, geanonimiseerd en hersteld. We verwijzen doorlopend naar ons voorbeeldrapport en naar de dienst audit van AI-code, want daar gebeurt het herstel; dit stuk is het bewijs waarom het nodig is.

Waarom de bevindingen terugkeren

AI-programmeergereedschap optimaliseert op één ding: de kortste weg naar code die draait. Dat is werkelijk nuttig — je krijgt een idee in uren in werkende staat. Maar "draait in de demo" en "onderhoudbaar en veilig in productie" zijn verschillende doelen, en het gat ertussen is over projecten heen opmerkelijk consistent.

De reden is structureel. Een model dat code genereert heeft een smal contextvenster en geen herinnering aan de besluiten die het drie bestanden geleden nam. Het kan niet testen wat het net schreef, het heeft geen idee van je resterende middelen of je beveiligingshouding, en het heeft geen prikkel om de codebase op termijn samenhangend te houden. Dus maakt het elke keer de lokaal optimale keuze — en de som van lokaal optimale keuzes is een codebase die vandaag werkt en zich morgen tegen elke wijziging verzet.

Na genoeg audits vallen de fouten in dezelfde elf bakken. Hier zijn ze.


1. Hardgecodeerde geheimen en inloggegevens

Elke audit vindt dit, en het is precies het geurtje dat echt geld kost: .env-bestanden vastgelegd in de repository, API-sleutels en databasegegevens hard in de broncode, tokens van derden gebakken in clientbundels die naar elke browser gaan die de app laadt. Eén gelekte sleutel van OpenAI of Stripe kan binnen uren duizenden euro's aan ongeautoriseerde kosten opleveren — of een aanvaller je gegevensopslag zo in handen geven.

Waarom AI dit doet. Een sleutel hardcoderen werkt meteen; een geheimenbeheerder of omgevingsinjectie aansluiten niet, en het model heeft geen reden de tragere weg te verkiezen wanneer de snellere ook "draait". De kortste weg naar een werkende functie is vrijwel nooit de veilige.

Hoe we het herstellen. Geheimen gaan uit de broncode en in deugdelijk omgevingsbeheer. Alles wat ooit in git is vastgelegd behandelen we als reeds gecompromitteerd en wisselen we, we verwijderen het niet alleen — een git rm zonder wisselen laat de oude sleutel geldig in elke kloon en elke commitgeschiedenis. Dit is het niet-onderhandelbare deel van elke audit: een gelekte sleutel is een noodgeval, en die herstellen we eerst.

2. Geen invoervalidatie — het injectieoppervlak

SQL-injectie, XSS en komen wijdverbreid voor in met AI gebouwde code, en de oorzaak is elke keer dezelfde: gebruikersinvoer gaat rechtstreeks een query, een sjabloon of een LLM-prompt in zonder validatie of ontsmetting ertussen. Eén kwetsbaar endpoint kan je hele database compromitteren of een aanvaller laten sturen wat je eigen AI-functies doen.

Waarom AI dit doet. Validatie is een tweede verzoek dat het model nooit kreeg. Het genereert de code die aan de gelukkige-padprompt voldoet — "neem het bericht van de gebruiker en sla het op" — en het gelukkige pad noemt nooit wat er geweigerd moet worden.

Hoe we het herstellen. Elke grens waar niet-vertrouwde invoer het systeem binnenkomt krijgt expliciete validatie en geparametriseerde query's of sjablonen, geen tekenreeksen aan elkaar plakken. Voor AI-functies in het bijzonder betekent dat de promptopbouw zelf als injectiegevoelige grens behandelen, niet alleen de databaselaag.

3. Authenticatie die het vakje afvinkt, niet het verzoek

Met AI gebouwde apps bouwen authenticatie vaak alleen aan de oppervlakte — er is een inlogscherm, en het werkt — maar de backend controleert de rechten nooit per verzoek. API-endpoints accepteren alles wat binnenkomt. Beheerroutes zijn bereikbaar zonder rolcontrole. Gebruiker A ziet de gegevens van gebruiker B door een ID in de URL te wijzigen — een onveilige directe objectverwijzing die in productie staat.

Waarom AI dit doet. "Voeg een inlogpagina toe" en "controleer of dit specifieke verzoek dit specifieke record mag aanraken" zijn verschillende problemen, en het model lost het op waarnaar het gevraagd werd. Authenticatie is zichtbaar in een demo; gaten in de autorisatie zijn onzichtbaar tot iemand ze uitbuit.

Hoe we het herstellen. We leggen elk endpoint naast wie het werkelijk mag aanroepen, niet naast wie het inlogscherm suggereert, en voegen de ontbrekende autorisatiecontroles per verzoek toe — eigenaarschapscontroles op records, rolcontroles op beheerroutes, en verzoeklimieten op alles waar een script op kan hameren.

4. Geen tests — elke uitrol is een gok

De meest voorkomende bevinding: er zijn helemaal geen tests. Geen dunne suite, geen wispelturige tests — nul. De app is gevalideerd door erdoorheen te klikken, en dat is het hele vangnet.

Dat is onzichtbaar tot precies het moment waarop het rampzalig is. Zonder tests is er geen manier om te weten of een wijziging iets brak, behalve hem uitbrengen en wachten tot een gebruiker klaagt. Elke uitrol wordt een handmatige regressieronde die niemand werkelijk uitvoert, dus herstructureren wordt eng, updates van afhankelijkheden worden overgeslagen, en de codebase verkalkt — niet omdat de code slecht is, maar omdat niemand hem durft aan te raken.

Waarom AI dit doet. Een functie genereren en tests voor die functie genereren zijn twee aparte verzoeken, en niemand deed het tweede. Het model schrijft graag tests als je erom vraagt, maar aan zichzelf overgelaten levert het het gelukkige pad op en stopt.

Hoe we het herstellen. We mikken niet op 100% dekking op dag één. We voegen een dunne laag toe waar die zich het best terugbetaalt: een rooktest dat de app start, tests rond de geld- en authenticatielogica, en een regressietest voor elke bug die we tijdens de audit oplossen. Dat alleen al maakt van uitrollen een routine in plaats van een gok.

5. Geen foutafhandeling op het ongelukkige pad

De app werkt precies zoals gedemonstreerd — zolang het netwerk nooit wegvalt, de externe API nooit een time-out geeft, en de gebruiker nooit iets onverwachts doet. Zodra een van die dingen gebeurt zijn de symptomen lelijk: een niet-afgevangen promise-afwijzing laat het hele verzoek crashen, een mislukte API-aanroep laat de interface eeuwig op een spinner staan, een exceptie toont de gebruiker een ruwe stacktrace in plaats van een bericht dat hem iets zegt.

Waarom AI dit doet. Het gelukkige pad is wat de prompt beschreef en wat de demo doorliep. Foutafhandeling is verdedigende code voor situaties die het model nooit is gevraagd zich voor te stellen, en ze voegt regels toe zonder de demo indrukwekkender te maken — dus is het het eerste wat onder een impliciet tijdsbudget sneuvelt.

Hoe we het herstellen. We lopen elke externe aanroep langs — API, database, bestandssysteem — en voegen de faaltak toe: opnieuw proberen met uitstel waar dat helpt, een terugval of een helder foutscherm waar dat niet zo is, en logging die vertelt wat er werkelijk gebeurde in plaats van een generiek "er ging iets mis". Het doel is dat een storing bij een derde je app netjes laat degraderen in plaats van hem onderuit te halen.

6. N+1-query's en prestaties aan het toeval overgelaten

Het lijstscherm dat in ontwikkeling met tien rijen direct laadt, komt in productie met tienduizend tot stilstand. De klassieke oorzaak is een N+1-query: één query om een lijst op te halen, en daarna per rij een aparte query voor de bijbehorende gegevens, zodat een scherm dat één rondgang naar de database zou moeten kosten er honderden kost. Ontbrekende indexen, ongebonden resultaatsets zonder paginering, en hele objecten laden terwijl er maar een veld of twee wordt getoond, zijn de gebruikelijke metgezellen. Onze gids over databases en indexen behandelt de mechaniek van waarom dit traag is en hoe een gezond queryplan eruitziet.

Waarom AI dit doet. Het N+1-patroon is de meest voor de hand liggende manier om de lus te schrijven, en het levert correcte uitvoer op — het model heeft geen terugkoppeling die zegt dat het aantal query's telt tot iemand het onder echte datavolumes meet, en dat doet een demo met een handvol rijen nooit.

Hoe we het herstellen. We profileren de werkelijke querypatronen onder realistische datavolumes, klappen de N+1-ketens in tot joins of gebundelde ophaalacties, voegen de ontbrekende indexen toe, en zetten paginering of limieten op alles wat een ongebonden set teruggeeft. Dit is doorgaans de prestatiewinst met de grootste hefboom in een audit, want één slecht lijstscherm kan het grootste deel van de laadtijd van een pagina verklaren.

7. Afdrijvende afhankelijkheden en CVE's

npm audit of het equivalent levert een muur bekende kwetsbaarheden op zodra iemand het draait — want niemand had dat gedaan. Pakketten staan vast op de versie die actueel was toen de AI-tool het project opzette, transitieve afhankelijkheden die niemand rechtstreeks koos dragen eigen CVE's mee, en er is geen proces om erachter te komen wanneer er een patch verschijnt.

Waarom AI dit doet. Het model kiest een pakket dat het directe probleem oplost en gaat verder; het heeft geen doorlopende band met je project die het later tot heroverwegen aanzet. Hygiëne rond afhankelijkheden is onderhoud, en niets aan een functie genereren zet onderhoud in gang.

Hoe we het herstellen. We draaien een scan op kwetsbare afhankelijkheden, patchen of vervangen alles met een bekende uitbuiting, en zetten een proces op — desnoods een eenvoudige geplande scan — zodat dit niet stilletjes opnieuw afdrijft zodra de audit eindigt.

8. Gedupliceerde variabelen en functies

Open een met AI gebouwde codebase en zoek naar dezelfde hulpfunctie voor datumopmaak. Je vindt hem vaak drie of vier keer — elke keer net iets anders, omdat elk apart is gegenereerd voor het scherm dat hem nodig had. Hetzelfde geldt voor validatieregels, API-clients, geldberekeningen en configuratieconstanten.

Duplicatie is niet alleen lelijk; het is een tijdbom voor correctheid. Moet de logica veranderen — een nieuwe belastingregel, een opgeloste afrondfout, een gewijzigd endpoint — dan moet je elke kopie vinden. Je mist er een. Nu zijn twee delen van de app het oneens over iets waarover ze het eens zouden moeten zijn, en dat meningsverschil is het volgende productie-incident.

Waarom AI dit doet. Het model doorzoekt de bestaande codebase zelden op een hulpfunctie die het kon hergebruiken. Het is vanuit zijn perspectief goedkoper de functie ter plekke opnieuw te genereren dan de bestaande te ontdekken en te importeren. Elke generatie is op zichzelf redelijk; het totaal is afdrijving.

Hoe we het herstellen. We zoeken de clusters bijna identieke code op, trekken één bron van waarheid eruit, en leiden elke aanroepplek daarlangs. Dit is een van de opruimacties met de grootste hefboom in de meeste audits: het krimpt de codebase en haalt hele categorieën "hier opgelost maar daar niet"-bugs weg.

9. Geen consistente architectuur

Deze is schokkend om voor het eerst te zien. Twee schermen in hetzelfde project zijn geschreven alsof twee verschillende teams eraan werkten: het ene haalt data op in de component, het andere via een servicelaag; het ene houdt state op de ene manier vast, het volgende doet het compleet anders; naamgeving, mapindeling en foutafhandeling wisselen per functie. Er is geen ruggengraat.

Een codebase zonder consistente architectuur is er een waarin elk bestand dat je opent een verrassing is. Een developer inwerken kost weken omdat er geen patroon te leren valt — alleen honderd bijzondere gevallen om te onthouden. Erger nog: waar patronen botsen, zitten precies de naden waar bugs broeden.

Waarom AI dit doet. Het model heeft geen blijvend beeld van "hoe deze app is gebouwd". Elke prompt is een verse start, dus grijpt het naar het patroon dat bij dat ene verzoek past. Over de levensduur van een project levert dat een lappendeken op — elk stuk op zichzelf verstandig, het geheel onsamenhangend.

Hoe we het herstellen. We kiezen één architectuur die bij het project past — geen dogmatische, een passende — en laten de codebase daar stapsgewijs naartoe convergeren, zodat een developer die één functie leert kan voorspellen hoe de volgende werkt.

10. Stroomgebaseerde state vermeden — recht de callbackchaos in

Dit is de technisch interessantste fout, en degene die stilletjes de lastigste functies breekt. Door AI gegenereerde code neigt ertoe stroom- en reactieve statemodellen te vermijden ten gunste van imperatieve callbacks. In plaats van "deze waarde verandert in de tijd en de interface reageert" te modelleren, sluit het een callback aan, die een andere callback aanroept, die een vlag zet, die een derde afvuurt — en het resultaat is callbackchaos.

Bij eenvoudige schermen merk je het nauwelijks. Maar zodra de state werkelijk complex is — een formulier in meerdere stappen met validatie over velden heen, een dashboard dat live bijwerkt, alles met vertragen, opnieuw proberen, annuleren of optimistisch bijwerken — valt de callbackaanpak uit elkaar. Het klassieke symptoom is het formulier dat bijna werkt: het valideert, maar de fout verdwijnt op het verkeerde moment; het verstuurt, maar een dubbele tik vuurt hem twee keer af.

Waarom AI dit doet. Imperatieve callbacks zijn het meest voorkomende patroon in de trainingsdata en het makkelijkst stuk voor stuk te genereren. Reactieve en stroomgebaseerde modellen vragen dat je de hele toestandsmachine tegelijk in gedachten houdt — precies waar een generator met beperkte context het slechtst in is.

Hoe we het herstellen. We wijzen de functies met complexe state aan en herbouwen hun statelaag deugdelijk — als stromen of als een reactief statemodel dat bij de stack past — zodat de interface een functie van de state is in plaats van een stapel callbacks die met elkaar racen.

11. Geen besef van de uitrolomgeving

Het model schrijft code alsof die als één proces op één machine draait — want van binnenuit de prompt is dat de enige omgeving die het kan zien. Het heeft geen idee hoeveel instanties er zullen draaien, welke beheerde diensten er al zijn, of hoe verkeer wordt gerouteerd. Dus grijpt het naar de eenvoudigst mogelijke topologie, en die standaardkeuze breekt stilletjes zodra de app werkelijk wordt uitgerold.

De symptomen zijn altijd dezelfde. State die in het geheugen van het proces leeft — een cache, sessies, tellers voor verzoeklimieten — werkt perfect op één instantie en loopt stilletjes uiteen zodra er een tweede replica achter de loadbalancer opkomt. Achtergrondtaken vuren op elke instantie in plaats van één keer, zodat de e-mail drie keer uitgaat.

Waarom AI dit doet. Het heeft geen beeld van je infrastructuur. Het weet niet dat je al Redis, een berichtenwachtrij en objectopslag hebt — dus bouwt het ze in het geheugen opnieuw. De uitroltopologie is precies de context die een prompt niet kan bevatten.

Hoe we het herstellen. We brengen de werkelijke uitrol in kaart en verplaatsen gedeelde state naar waar ze hoort: cache, sessies en locks naar Redis of de database, bestanden naar objectopslag, terugkerend werk naar een echte planner of wachtrij. Het resultaat is code die horizontaal schaalt.


Hoe we ze vinden

Elke bevinding hierboven begint met een geautomatiseerde ronde en eindigt met een mens die de code leest. De automatisering — statische analyse, scannen op afhankelijkheden en geheimen, profileren van API-kosten — is wat een AI-versnelde audit snel maakt: ze ruimt de categorieën op die mechanisch te vinden zijn (bevinding 1, 4, 6 en 7 hierboven duiken hier vrijwel meteen op), zodat de tijd van onze engineers zich concentreert op de categorieën die oordeelsvermogen vragen — autorisatielogica, architectuur, en of een bepaald foutpad er voor jouw product werkelijk toe doet. Geen van beide helften werkt alleen: automatisering alleen mist alles wat begrip vraagt van waar de code voor is, en handmatig beoordelen alleen schaalt niet naar een echte codebase in een week. De volledige uitsplitsing van het proces staat op de dienstpagina audit van AI-code.

Wat er in het rapport staat

Bevindingen komen niet als ruwe lijst. Elke is op ernst gerangschikt — kritiek, hoog, gemiddeld, laag — met een uitleg van het risico in gewone taal, een demonstratie waar dat past, en een concrete oplossing, precies de vorm die elke bevinding hierboven volgde. Wil je de opzet zien voordat je je ergens aan verbindt, vraag dan een geanonimiseerd voorbeeldrapport aan — dezelfde rapportstructuur die een echte opdracht oplevert, met identificerende gegevens van de klant verwijderd.

Het patroon achter het patroon

Doe een stap terug en de elf bevindingen delen één oorzaak: AI optimaliseert elke generatie lokaal, en niemand optimaliseert de codebase als geheel. Geheimenbeheer, invoervalidatie, autorisatie, tests, foutafhandeling, queryprestaties, hygiëne rond afhankelijkheden, ontdubbeling, architectuur, statemodellering en besef van de uitrolomgeving zijn allemaal eigenschappen van het hele systeem. Ze kunnen niet prompt voor prompt ontstaan, want geen enkele prompt kan het geheel zien. Precies dat gat dicht een menselijke beoordeling.

Het geruststellende deel is dat niets hiervan betekent dat het met AI gebouwde fundament verspild is. De functies werken; het product is echt. Wat ontbreekt is het bindweefsel — en dat toevoegen gaat veel sneller dan vanaf nul herbouwen.


Veelgestelde vragen

Over tientallen door AI gegenereerde codebases keren vrijwel elke keer elf problemen terug: hardgecodeerde geheimen en inloggegevens, geen invoervalidatie, authenticatie die het vakje afvinkt maar niet het verzoek, nul geautomatiseerde tests, geen foutafhandeling op het ongelukkige pad, N plus een-problemen en prestaties aan het toeval overgelaten, verouderde afhankelijkheden met bekende kwetsbaarheden, zwaar gedupliceerde variabelen en functies, geen consistente architectuur door het project heen, complexe asynchrone state gebouwd als callbackchaos in plaats van stromen, en code geschreven zonder besef van de uitrolomgeving. De concrete code verschilt per project, maar deze elf categorieën duiken steeds weer op.
Een sleutel hardcoderen werkt meteen, terwijl een geheimenbeheerder of omgevingsinjectie aansluiten dat niet doet, en het model heeft geen reden de tragere, juiste weg te verkiezen wanneer de snellere ook draait. Het resultaat zijn .env-bestanden in de repository, sleutels hard in de broncode, en tokens gebakken in clientbundels. Alles wat ooit in git is vastgelegd moet als reeds gecompromitteerd worden behandeld en gewisseld, niet alleen verwijderd, want de oude waarde blijft geldig in elke kloon en elke commitgeschiedenis.
Validatie is een tweede verzoek dat het model nooit uitdrukkelijk kreeg. Het genereert code die aan de gelukkige-padprompt voldoet, en het gelukkige pad noemt nooit wat er geweigerd moet worden, dus gaat gebruikersinvoer vaak rechtstreeks een query, een sjabloon of een LLM-prompt in zonder ontsmetting ertussen. Dat is de directe oorzaak van de SQL-injectie, XSS en prompt injection die in vrijwel elke met AI gebouwde codebase opduiken die we beoordelen.
Omdat een functie schrijven en er tests voor schrijven twee aparte verzoeken zijn, en het tweede meestal nooit gebeurt. AI-gereedschap optimaliseert op de kortste weg naar code die draait, en dat is het gelukkige pad zonder testsuite erachter. Het model schrijft tests als je erom vraagt, maar uit zichzelf levert het de functie op en stopt, waardoor elke toekomstige uitrol zonder vangnet zit.
Het model doorzoekt de bestaande codebase zelden op een hulpfunctie die het kon hergebruiken. Een functie ter plekke opnieuw genereren voor het scherm dat hem nodig heeft is vanuit zijn perspectief goedkoper dan een bestaande ontdekken en importeren. Elke generatie is op zichzelf redelijk, maar het resultaat is dezelfde logica meerdere keren gekopieerd met kleine verschillen, en dat wordt een correctheidsprobleem zodra die logica moet veranderen.
Door AI gegenereerde code neigt ertoe stroomgebaseerde en reactieve statemodellen te vermijden ten gunste van imperatieve callbacks. Bij eenvoudige schermen is dat prima, maar complexe state zoals formulieren in meerdere stappen met validatie over velden heen, vertragen, opnieuw proberen of optimistisch bijwerken klapt in tot callbackchaos. Het klassieke symptoom is een formulier dat bijna werkt: fouten verdwijnen op het verkeerde moment, of een dubbele tik verstuurt twee keer. De state als stroom modelleren lost het op.
De meest voorkomende oorzaak is een N plus een-querypatroon: één query om een lijst op te halen, daarna per rij een aparte query voor de bijbehorende gegevens, zodat een scherm dat één rondgang naar de database zou moeten kosten er honderden kost. Het ziet er in ontwikkeling met een handvol rijen correct uit en duikt pas op zodra echt datavolume het raakt, want niets in het generatieproces meet het aantal databaseopvragingen. Ontbrekende indexen en ongebonden resultaatsets zonder paginering zijn de gebruikelijke metgezellen.
Omdat het model geen beeld heeft van hoe de app wordt uitgerold. Het schrijft code voor één proces op één machine, dus houdt het state in het geheugen en schrijft het bestanden naar de lokale schijf. Dat werkt op één instantie maar breekt zodra de app achter een loadbalancer wordt geparallelliseerd: caches, sessies en tellers voor verzoeklimieten in het geheugen lopen tussen replicas uiteen, en achtergrondtaken vuren op elke instantie in plaats van één keer. De oplossing is gedeelde state naar de juiste ondersteunende diensten verplaatsen zodat de app horizontaal schaalt.
Nee. Alle elf bevindingen zijn ter plekke te herstellen. Geheimen worden verwijderd en gewisseld, invoer wordt op elke grens gevalideerd, autorisatiecontroles komen op elk endpoint dat ze nodig heeft, tests worden toegevoegd waar ze zich het best terugbetalen, faalpaden krijgen deugdelijke foutafhandeling, N plus een-problemen worden ingeklapt tot gebundelde ophaalacties, afhankelijkheden worden gepatcht, gedupliceerde logica wordt tot één bron van waarheid teruggebracht, de codebase convergeert op één consistente architectuur, functies met complexe state krijgen een deugdelijke reactieve statelaag, en gedeelde state verhuist naar de juiste ondersteunende diensten. Zo behoud je het werkende fundament dat de AI opleverde en herstel je alleen het ontbrekende bindweefsel, wat veel sneller en goedkoper is dan een herbouw.

Krijg de bevindingen voor jouw codebase

Heb je een met AI gebouwde app en herken je er een paar van deze elf, dan loop je niet achter — je zit precies waar vrijwel elke door AI gegenereerde codebase belandt. De oplossing is geen herbouw; het is een gerichte audit die het bindweefsel toevoegt dat de AI niet kon.

Haal je codebase door een audit van AI-code, vraag een voorbeeldrapport aan om eerst de opzet te zien, of plan een gratis analyse en we vertellen je welke van de elf je grootste risico is, wat het kost om die te herstellen, en geven je een offerte met vaste scope — geen gok.