Bouw een Claude Code-beveiligingsscanner voor OWASP
De scan was om 23:47 klaar op een donderdagavond, en ik zat daar naar de terminal te staren alsof die net mijn hele codebase had beledigd. Zevenendertig kwetsbaarheden. Negen kritiek. Een risicoscore van 245 knipperend bovenaan het rapport, precies in die tint rood die betekent: "voer dit niet uit." De grap? Dit was geen code van een klant. Het was een opzettelijk kwetsbare notitie-app die ik als doelwit had gebouwd om de Claude Code security scanner agent te testen, waar ik die middag aan had gewerkt.
En de agent vond alles. MD5-wachtwoordhashing. Rauwe SQL-stringconcatenatie. Een kapotte toegangscontrole waar elke geauthenticeerde gebruiker alle notities van anderen kon wissen door simpelweg een ID te raden. Een sink voor willekeurige code-executie die ik drie bestanden diep had verstopt, omdat ik écht wilde weten of hij het zou vinden. Dat deed hij. In dertig seconden.
Toen stopte ik met het zien van dit project als een experimentje voor het weekend, en begon ik te denken dat dit precies was wat ik zes maanden geleden had moeten bouwen.
Ik run een cybersecurity-merk — xCyberSecurity — en een groot deel van mijn werk bestaat uit het uitvoeren van OWASP-stijl audits op webapplicaties. Die audits kosten veel menselijke tijd. Code lezen, datastromen nalopen, afhankelijkheidsversies checken, CVE's cross-refereren, bevindingen uitschrijven met ernst en oplossingen. Dat zijn taken voor een senior engineer. Traag. Nauwkeurig. Per uur factureerbaar.
Een Claude Code-agent kan die engineer niet vervangen. Maar hij kan wel de eerste selectie doen. Hij kan de voor de hand liggende kwesties opsporen, nog voordat er een mens naar het bestand kijkt. En omdat Claude Code het Skill + sub-agent patroon heeft geïntroduceerd, is alles modulair — een herbruikbaar onderdeel dat ik over projecten kan delen, vanuit andere agents kan aanroepen, en op een dinsdagmiddag simpelweg in een pre-commit hook kan plaatsen.
Dit is hoe ik hem heb gebouwd. En nog belangrijker: waarom de architectuur ertoe doet.
Waarom Eén Grote Agent Niet Werkt Hiervoor
Mijn eerste instinct — en waarschijnlijk ook dat van jou — is om gewoon één grote systeemopdracht te schrijven. "Je bent een security auditor. Controleer op SQL-injectie, gebroken toegangscontrole, zwakke crypto, de hele OWASP Top 10, schrijf een rapport." Versturen. Klaar.
Ik heb die versie als eerste geprobeerd. Het werkte. Min of meer. De rapporten waren inconsistent. Sommige scans markeerden elke md5()-aanroep als kritiek; andere negeerden die juist. De "ernst"-score verschoof tussen de scans — de ene keer werd een hardcoded API-sleutel als "hoog" beoordeeld, de volgende keer als "kritiek". Erger nog, de agent had geen geheugen van wat goed output was. Elke keer was het een compleet nieuwe improvisatie.
Het echte probleem was structureel. Ik vroeg één contextvenster om vier verschillende zaken tegelijk vast te houden: het OWASP Top 10-referentiemateriaal, de scanmethodologie, het rapportformaat en de daadwerkelijke code die geaudit moest worden. Dat is erg veel cognitieve belasting om in één prompt te duwen, en Claude — zelfs Opus 4.7 — reageert op die druk hetzelfde als een menselijke auditor na het achtste energiedrankje. Het wordt slordig.
Skills lossen dit op. Sub-agents ook. Gebruik ze samen en alles valt op zijn plaats.
Wil je meer context over hoe skills zich verhouden tot Claude Code, dan heb ik dat uiteengezet in mijn Claude Code Agent Skills-gids — zeker de moeite waard om daarnaast te lezen, want de scanner die ik hier behandel is een concrete toepassing van die patronen.
De architectuur: Skill + Sub-Agent
Hier is de verdeling die werkt.
De Skill is de kennislaag. Het bevat het OWASP Top 10-referentiemateriaal, de scanmethodologie en het exacte rapportformaat. Het is een map met een SKILL.md-bestand en een handvol markdownbestanden — één per OWASP-categorie — die on-demand worden geladen. De skill voert zelf geen scans uit. Het is een set instructies die wacht om opgeroepen te worden.
De Sub-Agent is de uitvoeringslaag. Dit is een gespecialiseerde Claude Code sub-agent met een eigen systeem-prompt, eigen tooltoegang (Read, Glob, Grep, Bash voor gh clones, Write voor rapporten) en expliciete instructies om de security skill te laden wanneer hij draait. De sub-agent is wat je daadwerkelijk aanroept. De skill is wat hij raadpleegt.
Deze splitsing is om drie redenen belangrijk.
Ten eerste: scheiding van verantwoordelijkheden. De kennis wordt onafhankelijk van de uitvoeringslogica versiebeheerd. Toen OWASP de Top 10 bijwerkte in 2025 — SSRF werd onder Broken Access Control geplaatst, Software Supply Chain Failures toegevoegd als nieuwe topcategorie, en Mishandling of Exceptional Conditions werd toegevoegd — heb ik twee markdownbestanden in de skill aangepast. De sub-agent hoefde niet te veranderen. In het oude alles-in-een-prompt-tijdperk had ik de hele systeem-prompt moeten herschrijven.
Ten tweede: deelbaarheid. De skill is een map. Ik kan hem zippen, naar een teamgenoot sturen, in een gedeelde repo zetten of onderbrengen in een team skills registry. De sub-agent is een enkel markdownbestand met frontmatter. Zelfde verhaal. Iemand anders kan ze beide clonen, aan elkaar koppelen in vijf minuten en krijgt identieke scanresultaten als ik.
Ten derde: composeerbaarheid. De scan-sub-agent kan rechtstreeks worden aangeroepen — @security-scanner audit deze repo — of worden gekoppeld via een hogere coordinator-agent. Ik heb een hoofdagent voor code review; als daar security: in een commit message verschijnt, wordt doorgeschakeld naar de scanner. Dat patroon zou onmogelijk zijn als security in de prompt van de reviewagent zelf zat. Ik heb dit soort overdracht uitgebreider beschreven in mijn agent swarm architecture post, en de scanner is daar een duidelijk voorbeeld van.
De rest van dit artikel beschrijft hoe je dit daadwerkelijk bouwt.
De SKILL.md om te Kopiëren
De skill bevindt zich in .claude/skills/security-vulnerability/. Hier is het SKILL.md-bestand — dit is het sjabloon dat ik daadwerkelijk gebruik, iets opgeschoond:
---
name: security-vulnerability
description: Scan een codebase of GitHub-repository op kwetsbaarheden uit de OWASP Top 10 2025. Gebruik dit wanneer de gebruiker vraagt om een security audit, kwetsbaarheidsscan, OWASP-review of een pre-deployment security check op een lokale directory of GitHub-URL.
---
Je voert een security audit uit op basis van de OWASP Top 10 2025-categorieën. Volg de onderstaande uitvoeringsstappen exact. Sla geen stappen over. Verzin geen ernstniveaus.
## Invoerverwerking
Accepteer een van de volgende twee invoertypes:
- Een lokaal directorypad (bijv. `/Users/me/projects/app`)
- Een GitHub-URL (bijv. `https://github.com/org/repo`)
Als de invoer een GitHub-URL is, kloon dan de repository met:
gh repo clone <org/repo> /tmp/scan-<timestamp>
Stel daarna het werkpad in op de gekloonde directory.
## Uitvoeringsstroom
1. **Inventariseer de codebase.** Identificeer gebruikte programmeertalen, frameworks en pakketmanifesten (package.json, composer.json, requirements.txt, go.mod, Gemfile, pom.xml).
2. **Laad categorieverwijzingen.** Lees voor elke OWASP Top 10 2025-categorie het bijbehorende referentiebestand uit deze skillmap:
- references/A01-broken-access-control.md
- references/A02-security-misconfiguration.md
- references/A03-supply-chain-failures.md
- references/A04-injection.md
- references/A05-cryptographic-failures.md
- references/A06-insecure-design.md
- references/A07-auth-failures.md
- references/A08-data-integrity-failures.md
- references/A09-logging-alerting-failures.md
- references/A10-exceptional-conditions.md
3. **Scan per categorie.** Gebruik voor elke categorie Grep en Read op de codebase, volgens de patronen beschreven in het referentiebestand van die categorie. Leg elk bevinding vast met bestandspad, regelnummer, codefragment en categorie.
4. **Controleer afhankelijkheden.** Extraheer voor elk pakketmanifest de opgegeven versies. Vlag pakketten met bekende CVE’s aan de hand van OSV.dev of de GitHub Advisory Database. Verzin geen CVE-ID’s. Indien lookup niet mogelijk is in de huidige omgeving, registreer het pakket en de versie onder “ongeverifieerde afhankelijkheden” zonder een oordeel te verzinnen.
5. **Scoren en classificeren.** Ken elk bevinding een ernst toe: kritisch, hoog, medium of laag. Gebruik hiervoor de rubric in references/severity-rubric.md. Bereken een totale risicoscore: (kritisch × 10) + (hoog × 5) + (medium × 2) + (laag × 1).
6. **Schrijf het rapport.** Sla op in `audit/JJJJ-MM-DD/report.md` relatief aan het scantarget. Gebruik het onderstaande rapportformaat.
## Rapportformaat
Het rapport moet de volgende onderdelen bevatten, in deze volgorde:
1. **Samenvatting** — doelwit, scandatum, totaal aantal bevindingen per ernstniveau, algemene risicoscore, pass/fail-oordeel.
2. **Bevindingen per categorie** — één sectie per OWASP-categorie met bevindingen. Elke bevinding vermeldt: ernst, bestand:regel, codefragment, waarom het belangrijk is, voorgestelde oplossing.
3. **Afhankelijkheidsrisico’s** — bekende kwetsbare pakketten met CVE-referenties en upgradepaden.
4. **Niet-geverifieerde afhankelijkheden** — pakketten die gemarkeerd zijn, maar niet bevestigd.
5. **Prioriteit voor herstel** — een geordende lijst van de top tien oplossingen op basis van ernst en benodigde inspanning.
## Strikte Regels
- Verzin nooit CVE-ID's, ernstniveaus of oplossingen als je er niet zeker van bent. Geef onzekerheid expliciet aan.
- Pas nooit automatisch fixes toe. De subagent mag patches voorstellen in een apart bestand, maar het wijzigen van broncode vereist expliciete menselijke goedkeuring.
- Als een bestand meer dan 2000 regels bevat, lees het dan in delen. Sla het niet over.
- Elke bevinding moet een specifiek bestand en regelbereik vermelden.
Dat is alles. De complete skill brain. Elk referentiebestand onder references/ is een gericht document van 200-400 regels: definitie, veelvoorkomende patronen om naar te zoeken (grep), taalspecifieke voorbeelden en het ernst-rubric voor die categorie. Je kunt die referentiebestanden makkelijk in een middag opstellen als je ook maar enige beveiligingservaring hebt.
De Subagent Die Het Gebruikt
De subagent bevindt zich op .claude/agents/security-scanner.md. Hier zijn de frontmatter en de systeem-prompt — opnieuw, precies de versie die ik gebruik:
---
name: security-scanner
description: Voer een audit uit op een lokale map of GitHub-repository tegen de OWASP Top 10 2025. Gebruik proactief wanneer de gebruiker spreekt over security audit, kwetsbaarheidsscan, pentest, pre-deployment check of OWASP-review.
model: opus
tools: Read, Glob, Grep, Bash, Write
---
Je bent een senior application security engineer. Je taak is om een grondige OWASP Top 10 2025-audit uit te voeren op een doelcodebase en een rapport op te stellen waar een menselijke reviewer direct mee aan de slag kan.
Bij activering:
1. Bevestig het scantarget met de gebruiker als dit onduidelijk is (lokaal pad versus GitHub-URL).
2. Laad de `security-vulnerability` skill. Volg exact de uitvoerprocedure.
3. Voer de scan uit. Wees systematisch, niet inventief. Dekking gaat boven snelheid.
4. Schrijf het rapport weg naar `audit/YYYY-MM-DD/report.md` en toon de samenvatting in de chat.
5. Zodra het rapport is opgeslagen, bied — maar voer niet uit — automatische fixes aan voor bevindingen die mechanisch veilig zijn (bv. `md5()` vervangen door `password_hash()`, een SQL-query parameteriseren). Wacht op expliciete goedkeuring vóór je enige bronbestanden aanpast.
Operationele regels:
- Je doet geen pentests op live systemen. Alleen statische analyse.
- Je gokt niet. Indien een bevinding onzeker is, markeer je dit als onzeker in het rapport.
- Je verwijdert of verplaatst geen bestanden.
- Je logt elke tool-aanroep. Bewaar het auditlog in `audit/YYYY-MM-DD/trail.log`.
Twee bestanden. Dat is de volledige scanner.
Wat er gebeurde toen ik het draaide
Ik bouwde een opzettelijk kwetsbare notitie-app als testdoelwit. Laravel-achtige stack, SQLite, een paar routes, gebruikersauthenticatie, CRUD voor notities. Ik verstopte er doelbewust zes typen kwetsbaarheden in en voegde er een paar toe die ik vergeten was dat ik ze ooit had geschreven.
De scan duurde ongeveer 90 seconden van begin tot eind op een codebase van 4.000 regels. Dit is de samenvatting die het produceerde:
- Totaal aantal bevindingen: 37
- Kritiek: 9
- Hoog: 15
- Medium: 12
- Laag: 1
- Risicoscore: 245 (kritiek)
De kritieke bevindingen betroffen A01 Broken Access Control (twee routes zonder ownership-check — elke geauthenticeerde gebruiker kon elke notitie bewerken of verwijderen door het ID te raden), A04 Injection (drie gevallen van rauwe string-concatenatie in SQL-queries), A05 Cryptografische Fouten (MD5 voor wachtwoord-hashing plus een hardcoded APP_KEY in een gecommit .env.example), en A06 Onveilig Ontwerp (een admin-endpoint dat een cmd-parameter accepteerde die rechtstreeks werd doorgegeven aan een shell-executiecall).
Het rapport zette dit allemaal netjes op een rij met bestandspaden, regelnummers, exacte fragmenten en een voorgestelde oplossing per bevinding. Voor de SQL-injectiegevallen stelde het voor om direct naar een geparameteriseerde query om te schrijven. Voor MD5-hashing wees het naar Laravel's Hash::make() en lichtte het migratiepad voor bestaande wachtwoordrecords toe.
Was het perfect? Nee. Het markeerde een loggingstatement die een gebruikers-e-mailadres afdrukte als een A09-issue, wat verdedigbaar is maar nogal streng aanvoelt — in de meeste jurisdicties zou dat geen bevinding zijn. Het miste ook een subtiele racecondition in de notitie-deel-endpoint die een menselijke reviewer in ongeveer dertig seconden zou spotten. Statistische patroonherkenning kan simpelweg niet redeneren over gelijktijdigheid, en geen enkele prompt gaat dat fixen.
Maar wat het oppikte, deed het heel goed. En ik had een rapport met datumstempel in audit/2026-04-18/ klaarstaan dat ik zo naar een klant kon sturen.
Integratie in een Pre-Commit of CI Flow
De sub-agent is op zichzelf handig. Maar het wordt pas echt nuttig wanneer deze automatisch draait.
Voor pre-commit gebruik ik een Git-hook die Claude Code in headless-modus aanroept, alleen op de staged files. Hierbij wordt de scanner sub-agent geladen, het scangebied beperkt tot de gewijzigde paden, en wordt de commit geblokkeerd als er nieuwe kritieke of hoge bevindingen worden gedetecteerd die niet in de vorige scan aanwezig waren. Het sleutelwoord hier is "nieuw" — een volledige OWASP-scan bij elke commit uitvoeren zou niet werkbaar zijn. Een delta-scan ten opzichte van de laatste goedgekeurde baseline is de juiste aanpak.
Voor CI geldt hetzelfde principe, maar in een andere wrapper. Een GitHub Action draait op pull requests, kloont de PR-branch in een tijdelijke directory, voert de scanner uit en plaatst de samenvatting als commentaar bij de PR met het volledige rapport als build-artifact. Als de risicoscore meer stijgt dan een ingestelde drempel vergeleken met de basisbranch, faalt de check. De team owner kan dit negeren met een goedgekeurd label. Dit heeft al twee dependency upgrades onderschept die via transitieve afhankelijkheden kwetsbare packages binnenhaalden, voordat ze main bereikten.
Geen van beide is de taak van de scanner zelf om te bouwen — het gaat hier om orchestration rondom de scanner. Maar beide zijn triviaal te koppelen zodra de skill + sub-agent er zijn, omdat de scanner één pad inleest en een rapport schrijft. Al het andere is slechts lijmcode.
Waar Dit Eerlijk Gezien Niet Werkt
Ik ga niet doen alsof dit een vervanger is voor een menselijke pentester, dus laten we specifiek zijn over de beperkingen.
False positives. De scanner rapporteert te veel op patroonherkenning. Elke md5()-aanroep wordt gemarkeerd, zelfs als deze voor niet-beveiligingsdoeleinden zoals content-fingerprinting wordt gebruikt. Elke potentieel gevaarlijke aanroep in een testbestand wordt gemarkeerd. Je leert uiteindelijk om te triageren, maar als je het rapport aan een niet-technische klant moet overhandigen, zul je die triage zelf moeten doen.
Geen runtime-analyse. Statische analyse ziet wat de code zegt, niet wat er gebeurt tijdens de uitvoering. Race conditions, timing attacks, businesslogic-fouten die alleen bij een specifieke volgorde van API-calls optreden — de scanner ziet dat niet. Een pentester wel. Dat is een blijvende beperking.
Afhankelijkheidsinformatie is zo goed als de bron. De scanner vergelijkt met OSV.dev en GitHub Advisories. Als een package een gepubliceerde CVE heeft, prima. Maar als een kwetsbaarheid drie dagen geleden is gemeld en nog niet in een openbare database staat, mist de scanner deze. Zero-days zijn altijd een uitdaging voor mensen, niet voor scanners.
Auto-fix is een valkuil. Dit is het punt wat ik het luidst wil maken. De sub-agent kan fixes voorstellen. Die zouden nooit zonder review doorgevoerd moeten worden. Ik weet dit omdat de eerste versie van de agent fixes direct toepaste, en vrolijk een md5()-aanroep binnen een legacy wachtwoordverificatie verving zonder een wachtwoordmigratie uit te voeren. De wijziging zorgde ervoor dat geen enkele bestaande gebruiker nog kon inloggen in een staging-omgeving. Statische fixes zonder migratiebewustzijn kunnen van een "medium" bevinding een kritieke uitval maken. Menselijke beoordeling is onmisbaar. Altijd.
Skill security zelf. Het laatste punt — en dit deed me echt nadenken na recent onderzoek van Repello over skill security — is dat skills daadwerkelijk uitvoerbare context zijn. Elke skill die je installeert kan je bestandssysteem lezen en shell-commando’s uitvoeren. Een malafide skill vermomd als security-scanner is een echt risico. Ik schrijf mijn eigen skills, ik audit elke geïmporteerde skill en houd de vertrouwensgrens zo klein mogelijk. Jij zou hetzelfde moeten doen.
Het Deel Dat De Meesten Missen
Wat ik keer op keer moet herhalen als andere ontwikkelaars mij naar dit patroon vragen, is dat de scanner zelf niet het waardevolle onderdeel is. Die scanner bouw je in een week. Elke competente engineer met Claude Code en een vrije middag kan er een maken.
Het echte waardevolle zit in de discipline die de scheiding tussen Skill en sub-agent afdwingt. Wanneer je de skill schrijft, leg je vast hoe een goede security-audit er eigenlijk uitziet — de categorieën, de patronen, de ernst-rubriek, het rapportformaat. Dat document, dat in .claude/skills/security-vulnerability/ staat, is een stuk institutionele kennis die daarvoor niet bestond. Het is waardevoller dan de agent zelf. Want volgend jaar, als OWASP de Top 10 van 2028 publiceert en de helft van de categorieën weer verschuiven, update je gewoon de referentiebestanden. De agent blijft werken. De kennis is geversioneerd, deelbaar en zit niet alleen in iemands hoofd.
Dat is de echte les uit deze build. Niet “Ik heb OWASP geautomatiseerd.” De les is dat modulaire agents je dwingen je kennis expliciet te maken — en expliciete kennis groeit exponentieel.
Kies vanavond één repo. Niet die van een klant. Eentje van jezelf. Draai een scan. Bekijk de bevindingen. Je leert in negentig seconden meer over je eigen code dan in de afgelopen zes maanden waarin je eraan hebt gewerkt.
Veelgestelde vragen
Kan Claude Code een privé GitHub-repository scannen?
Ja, zolang de gh CLI op de machine waarop Claude Code draait geauthenticeerd is met toegang tot die repo. De scanner gebruikt intern gh repo clone en neemt dus alle machtigingen over die je via gh auth login hebt toegekend. Voor organisatie-repo’s moet je token de repo scope hebben.
Vervangt de scanner tools zoals Snyk of Semgrep?
Nee. Het is een aanvulling. Snyk en Semgrep gebruiken samengestelde regels en kwetsbaarheidsdatabases die gezaghebbender zijn dan een enkele LLM-prompt. De Claude Code-scanner voegt redenering toe — hij kan datastromen volgen en contextspecifieke problemen spotten die statische regels missen. Gebruik beide tools. De scanner pakt ontwerpkwesties op; Snyk vindt bekende CVE's sneller.
Wat kost het om op deze manier een OWASP-scan uit te voeren?
Op Opus 4.7 kost een volledige scan van een codebase van 4.000 regels met de Skill + sub-agent setup mij een paar dollar aan API-gebruik per run. Delta-scans op gestage bestanden in een pre-commit hook zijn veel goedkoper. Heb je een Claude Code-abonnement met inbegrepen verbruik, dan vallen de meeste dagelijkse scans binnen dat budget.
Kan ik de skill met mijn team delen zonder mijn Claude-account te delen?
Ja. De skill is gewoon een map met markdownbestanden. Check .claude/skills/security-vulnerability/ in je projectrepo of een gedeelde skills-repo. Elk teamlid met Claude Code laadt deze automatisch vanuit het eigen account. Hetzelfde geldt voor het sub-agentbestand onder .claude/agents/.
Moet de scanner fixes automatisch toepassen?
Nee, en ik zou zeggen: nooit doen. Stel fixes voor, schrijf ze naar een apart patchbestand, en vereis menselijke review voordat bronbestanden worden aangepast. Ik heb persoonlijk een staging-omgeving kapotgemaakt door het agent toestemming te geven om een “veilige” cryptowissel uit te voeren. Veilig op zichzelf, rampzalig in combinatie met de rest van de authenticatieflow. Houd altijd een mens in de loop.
Laten We Samenwerken
Wil je AI-systemen bouwen, workflows automatiseren of je technische infrastructuur opschalen? Ik help je graag verder.
- Fiverr (maatwerk builds & integraties): fiverr.com/s/EgxYmWD
- Portfolio: mejba.me
- Ramlit Limited (enterprise-oplossingen): ramlit.com
- ColorPark (design & branding): colorpark.io
- xCyberSecurity (beveiligingsdiensten): xcybersecurity.io