Claude veilig toegang geven tot je SQL-database
8m 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.

Ja, je kunt een agent bij je database laten, en met read-only begint dat, maar daar houdt het niet op. Een SELECT stuurt nog altijd elke opgehaalde rij de context van het model in en daarmee naar de API, leest nog altijd rijen die een geïnjecteerde instructie kunnen bevatten, en laat een agent nog altijd een negenvoudige join op je twee grootste tabellen los. Lekkage, injectie en belasting: rechten vangen geen van drieën af.

Dit vangt ze wel af. Een aparte readonly_user op de database zelf, select_only per verbinding in config die je nakijkt in plaats van in het oordeel van de agent, queryvalidatie met harde limieten op joins en subqueries, rijlimieten en timeouts, redaction van gevoelige velden voordat resultaten het model bereiken, de verbinding via een SSH-tunnel in plaats van een open poort, en een log van elke query. De rest van deze post gaat over hoe je die stuk voor stuk inricht, en waarom de volgorde uitmaakt.

De afgelopen twee maanden waarschuwde ik je op deze blog vooral 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 terugverdient. "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 kijken ze er niet meer naar om. 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.
  • Schrijfrechten alleen via de config. Een database die de agent zelf toevoegt terwijl de server draait, is altijd SELECT-only. Schrijfrechten krijg je alleen door config.ini met de hand aan te passen, dus het model kan ze zichzelf niet geven.
  • 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, stuit de query daarna op een muur die de database zelf bewaakt. Vangrails in de tool, rechten bij de bron. Dubbel beveiligd, bewust in die volgorde.

Een read-only-account aanmaken ​

Die tweede muur bouw je zelf, dus zo maak je het account aan. Beide recepten heb ik getest op PostgreSQL 16 en SQL Server 2022.

PostgreSQL. Vervang app_owner door de rol waaronder je migraties draaien, en users (id, country, created_at) door je eigen tabel en de kolommen die de agent mag zien.

sql
-- 1. A login role that can connect, and nothing more yet
CREATE ROLE readonly_user WITH LOGIN PASSWORD 'change-me' CONNECTION LIMIT 5;
GRANT CONNECT ON DATABASE production_app TO readonly_user;

-- 2. Read access to one schema, including tables your migrations add later
GRANT USAGE ON SCHEMA public TO readonly_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA public
  GRANT SELECT ON TABLES TO readonly_user;

-- 3. Sensitive columns: take the table back, hand out safe columns only
REVOKE SELECT ON users FROM readonly_user;
GRANT SELECT (id, country, created_at) ON users TO readonly_user;

-- 4. Session defaults as a second layer
ALTER ROLE readonly_user SET default_transaction_read_only = on;
ALTER ROLE readonly_user SET statement_timeout = '30s';

Het echte werk zit in de grants. default_transaction_read_only is alleen een standaardwaarde, en een sessie kan hem uitzetten. In mijn test deed ik precies dat, en toch liep de INSERT stuk op de rechten. Een kolom die je buiten de grant laat, blijft buiten bereik, maar alleen in de tabellen die je hier noemt: een nieuwe tabel met gevoelige kolommen is volledig leesbaar tot je stap 3 ook voor die tabel herhaalt. Op een tabel die je wel afschermt, faalt ook SELECT *, en moet de agent zijn kolommen expliciet noemen.

SQL Server. Hier werkt de kolomlijst andersom: vervang dbo.Users (Email, Phone) door je eigen tabel en de kolommen die de agent níét mag zien.

sql
-- 1. A server login, then a user for it in the one database it needs
CREATE LOGIN readonly_user WITH PASSWORD = 'Change-me-123!', CHECK_POLICY = ON;
GO
USE production_app;
CREATE USER readonly_user FOR LOGIN readonly_user;

-- 2. Read access to every table and view in that database
ALTER ROLE db_datareader ADD MEMBER readonly_user;

-- 3. Sensitive columns: an explicit DENY beats the role's grant
DENY SELECT ON dbo.Users (Email, Phone) TO readonly_user;
GO

db_datareader geldt ook voor tabellen die later bijkomen, dus herhaal de DENY voor elke nieuwe tabel met gevoelige kolommen. De DENY op kolommen wint het daarvan, dus SELECT Email en SELECT * op dbo.Users falen allebei, terwijl de andere kolommen leesbaar blijven. SQL Server kent geen query timeout per login, dus die stel je in de MCP-server in: in Argos zet je per verbinding een timeout. Op Azure SQL kan Argos je Azure CLI-login gebruiken in plaats van een wachtwoord, zodat er helemaal geen credential in config.ini staat. Daar maak je de gebruiker aan met CREATE USER [you@example.com] FROM EXTERNAL PROVIDER in plaats van een login, en de regels voor de rol en de DENY blijven hetzelfde.

Hij moet zijn eigen sollicitatiegesprek doorstaan ​

Eerlijk is eerlijk: haal deze server door de checklist die ik deze week publiceerde. Uitgever en code komen van dezelfde persoon: 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 de inloggegevens die je hem geeft, horen bij het readonly-account uit de config hierboven, intrekbaar zonder een rotmiddag.

Het is TypeScript, MIT-gelicenseerd, met ruim 1.600 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 de onderliggende techniek.

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 echt 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.

(13 van 36)
1Mijn Claude Code-setup: status line, plugins en terminal2Superpowers: hoe je Claude Code leert eerst te denken3Claude Code-hooks: deterministische controle over AI-workflows4Het CLAUDE.md-bestand: geef je AI permanent geheugen5Stop met vriendelijk vragen aan je agent6Wat er nieuw is in Claude Code: notities van het Londen-event7Het beste cijfer in Opus 4.8 is geen benchmark8Verouderd geheugen is erger dan geen geheugen9De agent is gewoon een loop10Bouw een MCP-server, en vraag je dan af of die moet bestaan11Skill, subagent, hook of slash command? Kies de juiste12Inloggen op MCP-servers vanuit je shell13Claude veilig toegang geven tot je SQL-database14De dag dat 'default' ineens 'Manual' heette15Claude Code-skills: zo schrijf je er een die werkt16Een goede Claude Code-subagent schrijven17Claude Code permissions instellen: de gids die ik miste18Claude Code sandboxen: toestemming is geen muur19Prompt injection voorkomen: verdediging voor wie agents bouwt20Het beste Claude-model voor code: welk model voor welke taak21Legacy code refactoren met AI: begin met characterization tests22Je MCP-server beveiligen: authenticatie, scopes en rate limits23Opus 5 is er, en je effort-instellingen kloppen niet meer24Incident response voor AI-agents: wat je doet als je coding agent de fout in gaat25Claude Code /doctor: wat hij controleert en wat er nieuw is26Context beheren in Claude Code: wanneer /clear en wanneer /compact27Git worktrees uitgelegd: parallel werken met AI-agents zonder botsingen28Claude Code plan mode: beslissen voordat de agent schrijft29Debuggen met een coding agent: geef hem het zoekwerk, de hypothese houd je zelf30Claude Code checkpoints en /rewind: wijzigingen van je agent terugdraaien31Claude Code cross-session messaging: sessies die elkaar berichten sturen32Audit logging voor AI-agents: wat Claude Code vastlegt en wanneer je iemand wakker belt33Claude Code-config delen met je team: wat de repo wel en niet afdwingt34Je Claude Code-sessie heeft geen klok35Claude Code in een grote codebase: de agent afbakenen tot wat ertoe doet36Een Claude Code-sessie terughalen die de resume-picker niet laat zien