Elke run in CodeLynq gaat door een security-controle voordat de pull request opent. We waren er blij mee, en dat zijn we nog steeds. Maar toen we keken wat hij echt dekt, was het antwoord ongemakkelijk: hij beschermt de code die verandert, en het grootste deel van een codebase verandert niet.
Wat een security-controle per PR ziet
Een run heeft twee controles, beschreven in vier checks voor een AI-run. De self-review leest de diff. De security scan draait gitleaks, semgrep en de dependency-audits op de repository die de run wijzigde, en geeft wat hij vindt terug aan de agent om op te lossen.
Beide zijn goed in waar ze voor zijn: voorkomen dat een nieuwe wijziging een probleem toevoegt. Uit dat ontwerp volgen drie gaten:
- Code waar niemand aan komt, wordt nooit bekeken. Het beheerdersendpoint van twee jaar geleden, de exportjob, de webhookhandler van een externe developer. Komt er geen taak in de buurt, dan draait er geen controle overheen.
- De AI-review leest alleen de wijziging. Een ontbrekende autorisatiecheck zegt iets over hoe een endpoint zich verhoudt tot de rest van de applicatie. Een reviewer die veertig gewijzigde regels ziet, ziet niet dat de route ernaast er geen heeft.
- Scanners zoeken patronen. Ze zijn heel goed in een key in een configbestand of een bekende kwetsbare dependency. Ze weten niet dat je facturen alleen zichtbaar mogen zijn voor de eigen klant.
En de wereld verandert ook als je code dat niet doet. Een dependency die vorige maand in orde was, heeft deze maand een advisory, en een controle op basis van de diff merkt dat pas als iemand toevallig aan die repository werkt.
Een security audit van het hele project
Dus hebben we een security check toegevoegd die alles leest. Hij kloont elke repository in het project, draait dezelfde scanners over de volledige checkout, en geeft daarna de hele codebase aan een AI-agent die alleen kan lezen, met de uitvoer van de scanners erbij. De agent kan bestanden lezen en doorzoeken, maar niet wijzigen.
De opdracht is concreet: ontbrekende authenticatie of autorisatie op endpoints, injectie in alle vormen inclusief path traversal, SSRF, secrets in de repository, zwakke cryptografie of tokenafhandeling, onveilige deserialisatie, fouten in CORS en CSRF, persoonsgegevens die gelogd of te lang bewaard worden, en kwetsbare dependencies. Hij moet echte, bereikbare problemen boven theoretische zetten, per probleem zeggen hoe het misbruikt kan worden, en bevestigen of weerleggen wat de scanners meldden in plaats van het te herhalen.
Elke bevinding komt terug met een ernst, een locatie en een aanbeveling. Geeft de agent zijn bevindingen niet terug in een vorm die we kunnen lezen, dan mislukt de check en zegt hij dat. Hij legt nooit een leeg resultaat vast, want "niets gevonden" terwijl er niets gecontroleerd is, is het slechtste antwoord dat een securitytool kan geven.
Elke week, zonder dat iemand erom vraagt
Je kunt op elk moment een security check starten vanuit het tabblad Checks van een project. Het punt is juist dat niemand eraan hoeft te denken. Met security monitoring aan krijgt een project elke week een volledige security check. Het is een optionele add-on per project, maandelijks gefactureerd via het bestaande abonnement van de organisatie, en alleen een beheerder van de organisatie kan hem aan- of uitzetten. Zolang hij aan staat, gaan de wekelijkse checks niet van je tegoed af. Loopt het abonnement zelf af, dan pauzeren de ingeplande checks en gaan ze verder zodra het weer actief is.
Op een ingeplande check wacht niemand, dus het resultaat gaat als melding naar de beheerders van de organisatie, met het oordeel en het aantal nieuwe taken.
Waarom alleen kritiek en hoog taken worden
Onze go-live check maakt ook van gemiddelde bevindingen taken. De wekelijkse audit niet, en dat verschil is bewust. Een go-live check start iemand omdat die alles wil weten wat voor de lancering opgelost moet worden. Een wekelijkse scan draait zonder toezicht, en als hij elke gemiddelde bevinding als taak aanmaakte, liep de backlog vol met tickets waar niemand om vroeg. Mensen leren een bron van ruis te negeren, en dan negeren ze de kritieke bevinding ook.
Dus worden alleen kritieke en hoge bevindingen taken, als concept met het probleem, de locatie en de oplossing al beschreven, klaar voor een run. Gemiddeld en lager blijft op het auditrapport, waar je het leest wanneer jij dat wilt. Eén audit maakt hooguit vijftien taken aan, wat hij ook vindt.
Wekelijks betekent ook dat dezelfde bevinding elke week terugkomt tot hij is opgelost. Elke bevinding krijgt een vingerafdruk uit categorie, soort probleem en bestand, zonder regelnummer of formulering, want die verschuiven allebei. Een herhaling wordt gekoppeld aan de taak die al openstaat in plaats van opnieuw aangemaakt. Is die taak afgesloten en komt het probleem terug, dan krijgt het een nieuwe.
Wat het niet is
- Hij leest code. Hij test je draaiende applicatie niet, dus het is geen pentest en hij vindt geen verkeerd ingestelde load balancer.
- Hij draait je testsuite niet; dat doet de go-live check.
- De scanners draaien alleen als ze beschikbaar zijn voor je stack. Is er geen van toepassing, dan meldt de check dat en vertrouwt de review alleen op de AI.
- Zoals elke AI-review kan hij dingen missen en zich vergissen. Bevindingen zijn taken voor je team om te beoordelen, geen oordelen om te mergen.
Allebei, niet een van de twee
De controle per PR en de wekelijkse audit beantwoorden verschillende vragen. De ene vraagt of deze wijziging het erger maakt, de andere hoe het ervoor staat. Heb je alleen de eerste, start dan eens één security check met de hand op je oudste project en kijk wat er in de code zit waar al een jaar niemand aan kwam. Meer over hoe we met code en data omgaan staat op onze securitypagina.