Claude read-only toegang geven tot je database: rol, flag en Azure zonder wachtwoord
6m leestijd

Claude read-only toegang geven tot je database: rol, flag en Azure zonder wachtwoord

Elke SQL MCP-server belooft een read-only modus, en bij veertien ervan bleek die te omzeilen. Ik liet de bypasses los op een read-only rol in Postgres en SQL Server, en keek daarna wat er met diezelfde opzet gebeurt als je op Azure zonder wachtwoord inlogt.

bewijs0/7 schrijfpogingen voorbij de rolPostgreSQL 16.15, SQL Server 2025 RTM-CU105 bronnen30 oktober 2026
eigen meting
zeven schrijf- en escalatiepogingen als read-only rol, Postgres en SQL Server in Docker
getest op
PostgreSQL 16.15, SQL Server 2025 RTM-CU10
gecontroleerd
30 oktober 2026

Dit is de truc waarmee de eigen Postgres MCP-server van Anthropic onderuitging. De server stopte elke query in een read-only transactie, maar controleerde nooit of je meer dan één statement meestuurde.

sql
BEGIN TRANSACTION READ ONLY;
SELECT 1;
COMMIT;
DELETE FROM orders;

De COMMIT sluit de read-only transactie af, en de DELETE draait in een nieuwe, met alle rechten die de verbinding heeft. Ik heb het vanochtend uitgeprobeerd op Postgres 16 in Docker. Als superuser was de rij weg. Als rol met alleen SELECT kreeg ik cannot execute DELETE in a read-only transaction terug, en nadat ik ook die read-only instelling had uitgezet, permission denied for table orders.

Daarmee is de hele post eigenlijk al verteld. De read-only schakelaar in je MCP-server is een filter. De rol in je database is een grens. Regel die grens als eerste.

Vier bypasses glippen door de gaten in het MCP-filter en stranden allemaal op de databaserol: 0/7

De read-only modus van een MCP-server is een filter ​

Datadog Security Labs publiceerde de COMMIT-bypass in augustus 2025. De referentieserver was toen al zes weken deprecated, en het npm-package werd nog altijd 21.000 keer per week gedownload. Vanochtend keek ik op npm: in de week tot 28 oktober waren het er 179.402.

Het bleef niet bij dat ene package. In juli 2026 publiceerde Maximilian Hildebrand read-only bypasses in 14 SQL MCP-servers, waaronder de officiële van MariaDB en drie voor SQL Server. De bypasses vallen in een paar groepen uiteen:

  • Denylists die een werkwoord missen. Geen enkele denylist die hij testte, bevatte SET. Eén server voor SQL Server zocht naar DROP gevolgd door een spatie, dus DROP/**/ liep er zo langs.
  • Alleen het eerste statement controleren. SELECT 1; en daarna wat je maar wilt.
  • WITH als verpakking. Zes servers lieten WITH toe zonder te kijken wat erna kwam.

In september publiceerde AWS bulletin 2026-104 over de eigen Postgres MCP-server: een COPY ... TO PROGRAM-statement kon door de read-only validatie glippen en commando's uitvoeren op het besturingssysteem. Dat gold voor Postgres in eigen beheer, als de databaserol superuser was of pg_execute_server_program had. AWS geeft zelf precies het advies waar deze post op rust: verbind met een aparte rol die alleen de rechten heeft die hij nodig heeft.

Een filter moet elke manier herkennen waarop SQL kan schrijven. De database weet dat al, want die schrijft zelf.

Dat geldt ook voor dat van mij. Toen ik dezelfde families losliet op Argos, de MCP-server die ik voor databasetoegang bouwde, kwamen er elf gevallen doorheen. Daaronder set_config, waarmee een read-only sessie weer kan schrijven, en een zero-width space midden in een keyword. Die zijn inmiddels gedicht, maar het punt blijft staan: ik schreef dat filter met zorg, en er zaten toch gaten in.

De rol, stap voor stap ​

Het volledige recept, met rechten per kolom, staat in mijn gids voor veilige SQL-toegang voor Claude. Hieronder het minimum, plus wat ik erop heb losgelaten.

PostgreSQL.

sql
CREATE ROLE claude_ro LOGIN PASSWORD 'change-me';
GRANT CONNECT ON DATABASE app TO claude_ro;
GRANT USAGE ON SCHEMA public TO claude_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO claude_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO claude_ro;
ALTER ROLE claude_ro SET default_transaction_read_only = on;
ALTER ROLE claude_ro SET statement_timeout = '15s';

Als claude_ro probeerde ik drie dingen. De COMMIT-truc, tegengehouden door de read-only instelling. SET default_transaction_read_only = off met daarna de DELETE, tegengehouden door de rechten. COPY orders TO PROGRAM 'id', de bypass uit het AWS-bulletin, tegengehouden omdat de rol geen pg_execute_server_program heeft. Na afloop stond er nog steeds één rij in de tabel.

SQL Server.

sql
CREATE LOGIN claude_ro WITH PASSWORD = 'Change-me-123!';
CREATE USER claude_ro FOR LOGIN claude_ro;
ALTER ROLE db_datareader ADD MEMBER claude_ro;
DENY SELECT ON dbo.Users (Email) TO claude_ro;

Vier pogingen, vier keer geweigerd: BEGIN TRAN; SELECT 1; COMMIT; DELETE, EXEC xp_cmdshell 'whoami', SELECT ... INTO een nieuwe tabel, en ALTER ROLE db_owner ADD MEMBER claude_ro. De DENY op de kolom hield ook stand bij SELECT *.

Over beide engines samen is dat 0/7: zeven pogingen om te schrijven of meer rechten te pakken, en geen enkele kwam langs de rol. Er zat geen MCP-server tussen, ik stuurde alles rechtstreeks naar de database. Daar gaat het juist om. Wat het filter van je server ook mist, deze laag vangt het op.

Azure zonder wachtwoord ​

Op Azure kan het wachtwoord helemaal weg. Azure SQL en Azure Database for PostgreSQL accepteren allebei een Microsoft Entra-token, en dat haal je op met az login. Geen secret in een configbestand, niets om te roteren.

Er zit wel een addertje onder het gras: van wie is dat token? Met az login voert de MCP-server de query's namens jou uit. Heb jij de server ingericht, dan is de kans groot dat je de Entra-admin bent, en Microsoft zegt het zonder omwegen: "Administrator accounts are members of the db_owner role in every user database, and each user database is entered as the dbo user."

De opzet zonder wachtwoord die het veiligst aanvoelt, kan de agent dus dbo geven. Dan staat alleen het filter van de MCP-server nog tussen de agent en een DELETE, en juist zo'n filter ging bij die 14 servers mis.

Zonder wachtwoord ben je het secret kwijt. De rechten blijven gewoon staan. De oplossing is dezelfde rol, gekoppeld aan een identiteit die niet de jouwe is.

Azure SQL. Maak in Entra een groep of een aparte identiteit voor de agent, en koppel die als lezer:

sql
CREATE USER [DB Readonly] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [DB Readonly];

Log daarna als die identiteit in met een apart Azure CLI-profiel, zodat je eigen az login van jou blijft:

bash
export AZURE_CONFIG_DIR="$HOME/.azure-claude-ro"
az login

Argos haalt het token met authentication=azure-cli uit de Azure CLI. Start hem met die AZURE_CONFIG_DIR in zijn omgeving en hij verbindt als de lezer. Staan er meerdere databases in één config, dan regelt azure_config_dir=~/.azure-claude-ro hetzelfde voor alleen die ene database. Controleer wie je echt bent voordat je erop vertrouwt. Laat dit je eerste query via de server zijn:

sql
SELECT USER_NAME() AS db_user,
       IS_ROLEMEMBER('db_owner') AS is_owner,
       IS_ROLEMEMBER('db_datawriter') AS is_writer;

Krijg je dbo terug als db_user, stop dan en zet eerst de identiteit recht. Argos doet zo'n controle inmiddels zelf, elke keer dat hij verbindt met een select_only-database, op alle vier de engines, en waarschuwt als de rol kan schrijven. Zet je require_readonly_role=true, dan weigert hij de verbinding.

Azure Database for PostgreSQL. Zelfde idee, andere functie. De Entra-handleiding van Microsoft koppelt een groep met pgaadauth_create_principal, en daarna geef je die net als elke andere rol SELECT:

sql
SELECT * FROM pgaadauth_create_principal('DB Readonly', false, false);
GRANT USAGE ON SCHEMA public TO "DB Readonly";
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "DB Readonly";

Het token dient als wachtwoord. Volgens Microsoft is het 5 tot 60 minuten geldig, dus de client moet voor elke verbinding een vers token ophalen. Argos doet dat met dezelfde instelling authentication=azure-cli als voor SQL Server, en dwingt een geverifieerde TLS-verbinding af, want een bearer token over een onbeveiligde lijn komt terecht bij wie er ook maar antwoordt.

Aansluiten op Claude Code ​

Registreer de server met het profiel van de read-only identiteit in zijn omgeving, zodat Claude Code jouw eigen credentials nooit te zien krijgt:

bash
claude mcp add argos --scope user \
  -e AZURE_CONFIG_DIR="$HOME/.azure-claude-ro" -- \
  argos serve --config "$HOME/.config/argos/config.ini"

Laat de read-only modus van de server zelf ook aanstaan. Het is een tweede vangnet, en een goed vangnet houdt een ontspoorde query, zoals een join over negen van je grootste tabellen, al tegen voordat de database er last van krijgt. Vertrouw er alleen nooit als enige op. Welke server je ook kiest, haal hem eerst door de checklist voor het controleren van een MCP-server voordat hij een connection string te zien krijgt.

Wat een ontbrekende grens kost, leerde ik met schade en schande, op de dag dat Claude mijn productiedatabase verwijderde. Daar kwam geen slimme bypass aan te pas, alleen een agent met meer rechten dan de taak vroeg. En juist dat sluit je uit met een handvol regels SQL.

(41 van 41)
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 het command controleert en repareert26Context 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 zien37Waar Claude Code zijn tijd aan kwijt is: een maand eigen sessies gemeten38Claude Code bijsturen tijdens het werk: Esc, de wachtrij en /btw39Claude Code auto mode: waarom je allow-regel voor Agent niets doet40Claude Code Superpowers: wat subagent-driven development aan tokens kost41Claude read-only toegang geven tot je database: rol, flag en Azure zonder wachtwoord