Skip to content

LearnTutorials

Set database guardrails for Claude Code and Cursor

Stop AI coding agents from deleting your data: give Claude Code and Cursor a read-only Postgres login and approval rules in 20 minutes.

By Recaply EditorsIntermediate20 min6 stepsUpdated

A hand sets a small padlock on top of a stack of round disks, with a pencil lying beside the stack

What you need

  1. Step 1: Create a read-only database role

    5 min

    A login that can only read can't delete rows or drop tables, whatever the agent is told to do.

    1. Connect to your staging database with psql as an admin user.
    2. Run the SQL below. Put a long random password in place of [PASSWORD] and your database name in place of [YOUR DATABASE].
    3. Once the test in step 6 passes, run the same SQL on production.
    Run this
    CREATE ROLE agent_ro WITH LOGIN PASSWORD '[PASSWORD]';
    GRANT CONNECT ON DATABASE [YOUR DATABASE] TO agent_ro;
    GRANT USAGE ON SCHEMA public TO agent_ro;
    GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_ro;
    ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO agent_ro;

    You should see CREATE ROLE, GRANT and ALTER DEFAULT PRIVILEGES printed back, with no errors.

    Your tables live in a schema other than public?

    Run the last three lines again for each schema, with its name in place of public.

    The last line covers new tables made by the role that ran it. Tables another role creates later need their own GRANT SELECT.

  2. Step 2: Give the agent only that login

    3 min

    An agent can use any password it can find, so make the read-only login the only one in reach.

    1. Move the admin connection string out of the project folder, for example into your password manager.
    2. In the terminal you start the agent from, set DATABASE_URL to the agent_ro login, as below.
    3. Check it with psql "$DATABASE_URL" -c "select current_user;".
    4. Start Claude Code from this terminal, or point Cursor's project settings at the same login.
    Run this
    export DATABASE_URL="postgresql://agent_ro:[PASSWORD]@[HOST]:5432/[YOUR DATABASE]"

    You should see agent_ro printed as the current user.

    The check prints your admin user instead?

    Something else sets DATABASE_URL after this line. Search the project's .env files and your shell profile, and remove the admin URL.

    Keep this password out of committed files and chats. Anyone with the string can read every table.

  3. Step 3: Add deny and ask rules in Claude Code

    3 min

    Rules stop Claude Code from opening secret files and make it ask before any database command runs.

    1. In your project, open or create .claude/settings.json.
    2. Paste the rules below and save the file.
    3. In Claude Code, run /permissions to check that the rules loaded.
    Your config
    {
      "permissions": {
        "deny": [
          "Read(./.env)",
          "Read(./.env.*)",
          "Bash(dropdb *)"
        ],
        "ask": [
          "Bash(psql *)"
        ]
      }
    }

    You should see your deny and ask rules in the /permissions list, marked as coming from .claude/settings.json.

    Claude Code shows a Settings Error when it starts?

    Settings files are strict JSON. Remove any trailing comma or // comment, save, and start Claude Code again.

    A Bash rule matches the command as Claude writes it, so a form like /usr/bin/psql can slip past. That's why the read-only role is the real lock.

  4. Step 4: Set the same limits in Cursor

    3 min

    Cursor hides .env files from Agent by default, but its terminal commands can still open them.

    1. In Settings > Agents > Approvals & Execution, pick Auto-review or Allowlist. Never pick Run Everything for a project with a database.
    2. In your project, create .cursor/permissions.json with the text below.
    3. In Allowlist mode, leave psql off the allowlist so it always asks.
    Your config
    {
      "autoRun": {
        "allow_instructions": [],
        "block_instructions": [
          "Every psql command and every command that connects to a database should go through approval first.",
          "Every command that drops, truncates, resets or migrates a database should go through approval first."
        ]
      }
    }

    You should see an approval card in Cursor before any psql command runs.

    Cursor ran a database command without asking?

    Check that the mode isn't Run Everything and that psql isn't on your allowlist. Auto-review is a classifier, not a lock, so keep the read-only role in place.

  5. Step 5: Plan before the agent acts

    2 min

    A plan lists every database step, so you can spot a reset or a delete before it runs.

    1. In Claude Code, press Shift+Tab until the status bar shows plan mode, or start your prompt with /plan.
    2. In Cursor, press Shift+Tab in the chat input until Plan Mode is picked.
    3. Paste the prompt below, then read the plan for migrations, deletes, resets and seed scripts.
    Your prompt
    /plan Add [YOUR FEATURE]. List every command you would run against the database, and mark each one as read or write. Don't run any of them yet.

    You should see a written plan with each database command listed and marked read or write.

    Claude Code ran a command while it was planning?

    Plan mode blocks file edits, but read-only commands still run, and auto mode may approve others. Your ask rule on psql still makes it stop and ask.

  6. Step 6: Test the guardrails on staging

    4 min

    A test you run on purpose costs nothing. A gap you find in production can cost your data.

    1. As your admin user, create a throwaway table on staging: CREATE TABLE guardrail_test (id int);
    2. Ask the agent to drop it, with the prompt below.
    3. Approve the psql request, so the command reaches the database.
    Your prompt
    Connect with the DATABASE_URL in this shell and run: DROP TABLE guardrail_test;

    You should see PostgreSQL refusing with must be owner of table guardrail_test, and the table still there.

    The table was dropped?

    The agent used a login with more rights. Ask it to run select current_user;, then find and remove the admin URL it could reach.

    Drop the test table yourself afterwards, as the admin user.

You get

A database your coding agents can read but can't change, with secret files hidden and every database command behind an approval

Did this guide work for you?

Sources