Claude veilig toegang geven tot je SQL-database
5m leestijd

Claude veilig toegang geven tot je SQL-database

Praktische gids om een AI-agent bij je database te laten zonder er wakker van te liggen: SELECT-only, queryvalidatie, field redaction voordat rijen het model bereiken, SSH-tunnels en audit-logging.

De afgelopen twee maanden bestond deze blog vooral uit mij die je waarschuwt voor MCP. In mei wiste een agent mijn productiedatabase. Ik schreef over de twaalfeneenhalfduizend MCP-servers die open aan het internet hangen. Deze week publiceerde ik een checklist om een server te controleren voordat hij ook maar in de buurt van je config komt.

De eerlijke vraag die een lezer dan mag stellen: zou ik zelf een agent op mijn databases loslaten?

Dat doe ik. Dagelijks. Via een MCP-server die ik zelf bouwde, nog voordat ik die stukken schreef. Deze post gaat over de vangrails die van "een agent op je database" een afgewogen keuze maken in plaats van een gok.

Waarom je dit überhaupt wilt

Vragen stellen aan je data in gewone taal is een van de weinige agent-toepassingen die zich meteen terugbetaalt. "Hoeveel aanmeldingen in de laatste 24 uur, en clusteren de errors ergens?" kost dertig seconden in plaats van een kwartier aan omwegen via een query-console. Een productieprobleem debuggen terwijl de agent de betrokken rijen zelf bekijkt wint het van gokken op basis van de logs.

Die aantrekkingskracht is echt, en precies daarom sluiten zoveel mensen een agent rechtstreeks op hun database aan, met een account dat alles mag, en gaan ze verder met hun dag. Daar moeten we het over hebben.

Wat read-only níét voor je regelt

Het reflexantwoord op databaseveiligheid is "zet hem op read-only". Prima begin. Kijk nu wat een SELECT nog steeds doet.

Alles wat een query teruggeeft belandt in de context van het model, en alles in de context gaat naar de API. Eén onschuldige SELECT * FROM users LIMIT 50 en de e-mailadressen, telefoonnummers en adressen van vijftig klanten hebben je infrastructuur verlaten. Niemand heeft je aangevallen. Je deed het zelf, keurig over TLS.

Data is bovendien onbetrouwbare invoer. Een rij in je database kan een geïnjecteerde instructie bevatten, net als een vergiftigde tool-beschrijving, en je agent leest queryresultaten met dezelfde goedgelovige blik. Het supply-chain-probleem houdt niet op bij de grens van het package.

En een agent schrijft queries als een enthousiaste stagiair: een negenvoudige join over je twee grootste tabellen ligt op de loer achter een antwoord dat plausibel klinkt. Read-only doet niets tegen een query die je productiedatabase twintig minuten platlegt.

Het dreigingsmodel heeft dus drie poten: lekkage, injectie en belasting. Rechten alleen vangen geen van de drie af.

Vangrails vóór rechten

Dit is wat de server afdwingt, laag voor laag, en elke laag hoort bij een poot van dat dreigingsmodel:

  • SELECT-only, per database. Productie krijgt select_only=true, zonder uitzonderingen. Een lokale kladdatabase in SQLite mag schrijfbaar blijven. Het punt is dat het beleid in config staat die jij controleert, per verbinding, in plaats van in het oordeel van de agent.
  • Queryvalidatie en complexiteitslimieten. Elke query wordt geparset en geanalyseerd voordat hij draait: injectiepatronen worden geweigerd, en er zitten harde grenzen op joins, subqueries en een samengestelde complexiteitsscore. De negenvoudige join sneuvelt bij de poort, met een uitleg waar de agent iets mee kan.
  • Rijlimieten en timeouts. Een resultaat is begrensd (duizend rijen standaard) en een query krijgt een deadline. Lekkage en belasting krimpen allebei zodra "de hele tabel" geen mogelijk antwoord meer is.
  • Field redaction. De onderschatte laag. Gevoelige kolommen worden gemaskeerd vóórdat het resultaat het model bereikt: john.doe@example.com komt aan als j******.e@*****.com, een SSN als [PROTECTED]. De agent kan nog steeds tellen, groeperen en redeneren over de vorm van de data. Hij krijgt de PII alleen nooit in handen.
  • SSH-tunnels, geen open poorten. De server bereikt externe databases via een bastion-host over SSH. Je database komt nooit aan het internet te hangen, en dat kun je van twaalfeneenhalfduizend MCP-servers niet zeggen.
  • Audit-logging. Elke query, elke verbinding, elke keer dat een gemaskeerd veld wordt geraadpleegd: gelogd. Als iets er vreemd uitziet, speel je terug wat de agent vroeg, in plaats van wat je hoopt dat hij vroeg.

In config zien de interessante delen er zo uit:

ini
[database.production]
type=postgresql
host=internal-db.company.local
database=production_app
username=readonly_user
select_only=true

redaction_enabled=true
redaction_rules=*email*:partial_mask,*phone*:full_mask,ssn:replace:[PROTECTED]

ssh_host=bastion.company.com
ssh_private_key=/secure/path/ssh_key

[security]
max_joins=5
max_subqueries=3
max_complexity_score=50

Let op de username. Het account zelf is readonly_user: zelfs als elke vangrail in de server tegelijk zou falen, dwingt de database zelf de volgende muur af. Vangrails in de tool, rechten bij de bron. Riem én bretels, bewust in die volgorde.

Hij moet zijn eigen sollicitatie doorstaan

Eerlijk is eerlijk: haal deze server door de checklist die ik deze week publiceerde. De uitgever klopt met de code, want beide zijn ondergetekende, en elke regel staat op GitHub zodat je het met me oneens kunt zijn. De werkwoorden passen bij de klus: query-, schema- en monitoringtools, niets dat een shell opent, niets dat bestanden schrijft. Je installeert hem vanaf de bron en pint hem daar vast, dus niemand kan je een verrassende zestiende release sturen. En het wachtwoord dat je hem geeft hoort het readonly-account uit de config hierboven te zijn, intrekbaar zonder een rotmiddag.

Het is TypeScript, MIT-gelicenseerd, met 1.181 tests eronder, en hij bestaat omdat ik mezelf de vraag stelde die ik later tot een post verwerkte: is een server wel de juiste vorm voor dit probleem? Voor databasetoegang is dat zo, juist omdat de waarde in de vangraillaag zit en niet in het loodgieterswerk.

Behandel de agent als een externe kracht

Een externe kracht geef je op dag één ook niet het root-wachtwoord. Die krijgt een eigen account, precies breed genoeg voor de klus, ziet geen klantgegevens die er niet toe doen, en het pand houdt bij welke deuren er opengingen.

Een agent verdient exact dezelfde afspraak. Een eigen readonly-account. Gemaskeerde kolommen voor alles wat gevoelig is. Een log dat je kunt terugspelen. En laat hem daarna gewoon werken, want binnen die muren is hij oprecht nuttig.

De agent die in mei mijn database wiste had toegang die hij nooit nodig had. De agent die vandaag mijn databases leest krijgt een afgebakend account, gemaskeerde PII en een audit trail. Dezelfde technologie. Een ander contract.