Skip to content
All posts

AI Coding Tips · Claude Code · Security

AI Coding Tip #2: How to keep Claude Code read-only on your production database

· 4 min read

A few weeks ago I needed a quick answer from one of our production databases. So I did what a lot of people do: I pasted the production database URL straight into a Claude Code session and asked my question.

That session was running with every permission prompt turned off. The URL belonged to an admin user with full write access. Anything the agent decided to run, it could run, on live customer data.

Nothing went wrong. The agent answered and moved on. But "nothing went wrong" isn't the same as "safe." One misread instruction, one overconfident cleanup step, one DELETE without a WHERE, and nobody would have been in the loop to stop it.

The problem

When you paste a production credential into an agent session, three things happen at once:

  • The agent can write. An admin database user can drop tables, rewrite rows and run migrations. The agent doesn't need to be malicious, just wrong.
  • The secret is now in your logs. Session transcripts are saved on disk, so the credential now lives in a file you never meant to store it in.
  • Nobody is in the loop. With permission prompts off, there's no moment where a human sees the command before it runs.

The answer isn't to stop using agents for data questions. Asking "how many orders came in last week?" in plain English is genuinely useful. The answer is to make sure the agent can only read.

The solution: four layers

Each layer covers a gap the others leave. Together they take about an afternoon to set up.

1. A read-only database role

Create a separate database role for analysis that can only run SELECT. It can't insert, update, delete, alter or drop anything. Even if every other layer failed, the database itself would refuse a write.

On Postgres, the setup looks roughly like this:

CREATE ROLE analyst_readonly LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE app TO analyst_readonly;
GRANT USAGE ON SCHEMA public TO analyst_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analyst_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO analyst_readonly;
ALTER ROLE analyst_readonly SET default_transaction_read_only = on;

This is the layer that matters most. Rules and prompts can be misread. Database permissions can't.

2. Keep the credential out of the session

Put the read-only URL in a gitignored file such as .env.analyst, and add a small wrapper script that reads it and runs psql. Queries then look like this:

bash scripts/analyst-sql.sh -c 'SELECT count(*) FROM orders'

The agent runs the script. It never sees, types or prints the URL, so the URL never lands in a transcript.

3. Deny rules the agent can't override

Claude Code supports permissions.deny rules in .claude/settings.json. We use 29 of them. They block the agent from:

  • Reading secrets: .env files, keys, certificates, service accounts, anything named like a credential or secret
  • Using database URLs directly: any command containing postgres://, postgresql:// or DATABASE_URL=
  • Changing the schema: prisma migrate, prisma db push, prisma db execute
  • Reaching production through the host: for us that's Railway, so railway run, railway shell, railway connect and railway variables
  • Loosening its own rules: editing .claude/settings.json itself

A short excerpt:

{
  "permissions": {
    "deny": [
      "Read(.env.*)",
      "Read(**/*credential*)",
      "Bash(*postgresql://*)",
      "Bash(*DATABASE_URL=*)",
      "Bash(*prisma migrate*)",
      "Bash(railway run *)",
      "Edit(**/.claude/settings.json)"
    ]
  }
}

4. Write the rule down for the agent

Add a section to the project's CLAUDE.md and mark it non-negotiable: data questions use only the read-only role, never ask for or accept a pasted production URL, and writes and migrations go through the documented migration process only.

The deny rules stop the agent if it tries. The CLAUDE.md rule means it doesn't try, and it will push back if you're the one pasting a URL.

Test it like you mean it

A rule you haven't tested is a hope. So try to break it. In a few separate sessions, with permission prompts turned off, ask the agent to read the file that holds the read-only URL. Every attempt should be blocked.

That's the key detail: deny rules hold even when permission prompts are switched off. Bypass mode skips the "are you sure?" questions. It doesn't skip the deny list.

Don't stop at one project

The easy mistake is to fix the project that scared you and forget the rest. If you work in several repos, the same agent with the same habits is running in all of them, and some of them probably hold credentials too.

Treat this as a default, not a patch:

  • Copy the deny list into every project's .claude/settings.json.
  • Give every production system the agent can reach a read-only login, not just the database that scared you.
  • Prefer a permission mode that keeps you in the loop for anything outside the deny list.
  • Run one blocked-read test in each project to prove the rules actually load.

Checklist

If your agents can reach production, check these today:

  • Does the agent connect with a role that can write? If yes, create a read-only role and use it for every data question.
  • Has a production credential ever been pasted into a session? If yes, rotate it. It's in your transcripts now.
  • Can the agent read your .env files? Add deny rules for them.
  • Can the agent run migrations or open a shell on your host? Deny those commands and route them through a human-run process.
  • Can the agent edit its own settings? Deny that too.
  • Have you tested any of it? Ask the agent to read a protected file and confirm it's blocked.

The agent isn't the threat. Unlimited access is. Give it the narrowest keys that still get the job done.

See whether it's working

How do you know your agents are actually locked down, in every project and not just the one that scared you? That's one of the things Vibemetric measures. It reads your Claude Code and Codex sessions on your Mac and scores how you work with the agent across 10 dimensions, including Security, which looks at whether your agents can reach only what the job needs: credentials, production data and commands included. Each score cites the sessions behind it and comes with the exact rule or prompt to fix it. It rescores every day, so you can add these four layers today and see the difference in your score.

See how your own sessions score.

Vibemetric reads your Claude Code and Codex sessions on your Mac and shows you exactly what to change.

Download for Mac