How to give Claude Code or Cursor write access to Postgres safely

If the agent only needs to read, give it a read-only role and stop here. This guide is for when it has to write.

Last checked 13 October 2026 · SQL tested on PostgreSQL 16, 17 and 18

An AI coding agent with a database connection can do anything the connection’s role can do. So the question “is it safe to let Claude Code or Cursor write to Postgres?” is mostly a question about the role you give it. Everything below is plain Postgres until the last two steps, and all of the SQL is in one file, tested on PostgreSQL 16, 17 and 18.

1. Read-only first

Most of what an agent does with a database is reading: answering questions, checking a hypothesis, writing a query you will run yourself. For that, create a role that can SELECT and nothing else. Privileges granted with GRANT decide what a role can change, and a role that holds no write privilege can’t write, whatever the agent sends.8

-- 1. The reader: SELECT on the app schema and nothing else.
CREATE ROLE agent_reader NOLOGIN;            -- give it LOGIN and a password from your secret store
GRANT USAGE ON SCHEMA app TO agent_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO agent_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT ON TABLES TO agent_reader;
ALTER ROLE agent_reader CONNECTION LIMIT 5;
ALTER ROLE agent_reader SET statement_timeout = '5s';

Three details matter:

  • Default privileges. ALTER DEFAULT PRIVILEGES makes tables you create later readable too, so the agent doesn’t quietly lose access, and you aren’t tempted to widen the role.
  • A timeout and a connection limit. An agent can write a query that scans everything. statement_timeout aborts any statement that runs longer than the limit, and a connection limit keeps a loop from opening hundreds of sessions.2
  • The read-only setting is a fence, not the boundary. default_transaction_read_only only sets the default for each new transaction, and its default is off.2 A session can turn it off again with a plain SET. Our test does exactly that as agent_reader, and the INSERT that follows still fails, because the role has no INSERT privilege. Keep the setting as a second fence:
-- A second fence, not the boundary: any session can turn this off again.
ALTER ROLE agent_reader SET default_transaction_read_only = on;

This is not a hypothetical. Datadog Security Labs showed that the archived reference Postgres MCP server’s read-only mode ran each query in a read-only transaction that a stacked COMMIT; could end, after which later statements ran with the role’s full rights.7 A role without write privileges would not have had that problem.

If the agent only reads, you are done. You don’t need Deedglass or anything else.

2. When it must write

Sometimes the agent’s job is a write: ship an order, fix a customer record, close a ticket. Give each job its own role, with the narrowest grant that does the job.

-- 2. The writer for one job: read three columns of one table, change two of them.
CREATE ROLE agent_order_writer NOLOGIN;
GRANT USAGE ON SCHEMA app TO agent_order_writer;
GRANT SELECT (id, status, note) ON app.orders TO agent_order_writer;
GRANT UPDATE (status, note) ON app.orders TO agent_order_writer;
ALTER ROLE agent_order_writer CONNECTION LIMIT 5;
ALTER ROLE agent_order_writer SET statement_timeout = '5s';

Postgres can grant SELECT and UPDATE on single columns.1 This writer can change status and note on orders and nothing else: not total, not another table, and no DELETE. It also can’t TRUNCATE, which is a separate privilege that can only be granted on whole tables, and it can’t create tables, because it has no CREATE on the schema.1 The test checks each of these.

Then limit which rows it can touch with row-level security. Once row security is on for a table, a role with no matching policy sees no rows at all, so the reader needs its own read policy too.3

-- 3. Row-level security: the writer sees open and shipped orders and changes open ones only. Once
--    RLS is on, every role without a policy sees no rows, so the reader gets its own read policy.
ALTER TABLE app.orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY agent_reader_read ON app.orders FOR SELECT TO agent_reader USING (true);
CREATE POLICY agent_writer_open ON app.orders FOR SELECT TO agent_order_writer
  USING (status IN ('pending', 'paid', 'shipped'));
CREATE POLICY agent_writer_ship ON app.orders FOR UPDATE TO agent_order_writer
  USING (status IN ('pending', 'paid'))
  WITH CHECK (status IN ('pending', 'paid', 'shipped'));

Table owners normally bypass row security, so never connect the agent as the table’s owner.3 And try the change on staging or a database branch first; production is where the role, not your intent, decides what happens.

What the writer can and can’t do

Our test connects as each role and tries what an agent might try. The results are the same on PostgreSQL 16, 17 and 18.

  • The reader can SELECT from app.orders. Its INSERT into app.customers fails with permission denied, and it still fails after the session runs SET default_transaction_read_only = off.8
  • The writer can move a paid order to shipped.
  • The writer can’t change total, can’t DELETE a row, can’t TRUNCATE the table, can’t read app.customers and can’t create a table. Each of these fails with permission denied.
  • The writer can’t move a shipped order back to paid. That update matches no rows and returns nothing.
  • A cancelled order is invisible to the writer. An update to it matches no rows, and a count of the table shows the writer 2 of its 3 rows.

Notice that the last two don’t raise an error. Row security hides rows rather than refusing the statement, so an update that matches nothing reports zero rows. Tell the agent that zero rows is an answer, not a reason to retry with a broader role.

Narrowing a broad role

Most agents are first connected with whatever role was handy: the app’s own login, or an owner. Narrowing it is a short sequence, and you can test each step before the next one.

  1. Write down the one job. “Mark paid orders as shipped” is a job. “Work with orders” is not.
  2. Start from nothing. Until privileges are granted, only an object’s owner (or a superuser) can do anything with it.8 A new role holds no write privilege on your tables, so every grant you add is one you chose.
  3. Grant columns, not tables. These two lines are the whole writer for the order job:
GRANT SELECT (id, status, note) ON app.orders TO agent_order_writer;
GRANT UPDATE (status, note) ON app.orders TO agent_order_writer;
  1. Limit the rows. Add a policy that says which rows the role may see and which it may change, as in step 3 above.
  2. Cap the session. A connection limit and a statement timeout stop a loop or a runaway query:
ALTER ROLE agent_order_writer CONNECTION LIMIT 5;
ALTER ROLE agent_order_writer SET statement_timeout = '5s';
  1. Switch the connection last. Point the agent at the narrow role and run its usual task. If it needs something you left out, the error says permission denied; add that one grant, not a broader role. Then retire the old broad credential.

3. A person before destructive verbs

Grants answer “may this role ever do this?” They can’t answer “should this particular delete run right now?” For that, put a gate in front of the agent’s tool calls that pauses risky statements until a person approves them. Any tool that does this works: a firewall that parses database traffic, an MCP gateway with approvals, or Deedglass. The rule you want looks like this, whatever the tool:

- id: writes-need-a-person
  match: { tool: [postgres.execute] }
  when: sql.writes
  decision: require_approval
  reason: Writes to production need a person to approve them.
  approval: { approvers: [role:admin], required: 1, expires_in: 30m }

Keep the list of approved verbs short. Deletes, schema changes and anything that touches money are where a person earns their time.

4. Check what committed, and keep a way back

An agent reports what its tool told it, usually “1 row updated”. Postgres may have changed more: a trigger fired, a foreign key cascaded, a rule rewrote the statement. In our demo, one UPDATE that names a single order changes 301 rows, because a legacy trigger ships every open order in the same batch.

The do-it-yourself path:

  • Log every write. pgAudit logs sessions and object access through Postgres’s standard logging, including a write class for INSERT, UPDATE, DELETE and TRUNCATE, and supports PostgreSQL 14 and later.4
  • Keep backups the agent can’t reach. The agent’s credentials should never be able to delete or alter them.
  • Read the log after each change. Compare the rows changed with the rows the agent said it changed.

Deedglass does the last step for you. It reads Postgres’s change log for each action, matches the committed rows to the action, and reports Matches or Doesn’t match. Each action is sealed into a record that anyone with the public key can check offline, and the rows one action changed can be undone, with checks before the undo runs.

5. Wiring it into Claude Code and Cursor

Both tools can connect to a database through an MCP server. Give the server the connection string of the narrow role, never an owner or superuser.

In Claude Code, add a local server with claude mcp add. Everything after -- is passed to the server untouched, and --scope project stores the server in a .mcp.json file at the project root that you can check in.5

claude mcp add --scope project orders-db -- <your Postgres MCP server command>

In Cursor, add the server to .cursor/mcp.json in the project, or to ~/.cursor/mcp.json for every project, under mcpServers with its command, args and env.6

{
  "mcpServers": {
    "orders-db": {
      "command": "<your Postgres MCP server command>",
      "args": [],
      "env": { "DATABASE_URL": "postgres://agent_order_writer@db.internal/app" }
    }
  }
}

Keep the password out of the file: use your secret manager or a variable from the environment.

To put the same server behind Deedglass’s rules, wrap the command (deedglass mcp wrap --connector <name> -- <server command>) and use that as the server command in either file.

6. Managed Postgres

The SQL in this guide runs as the role that owns the tables. On a managed service that is usually an administrator role, and it is the wrong role to give an agent.

  • On Amazon RDS for PostgreSQL, the first (master) user is a member of rds_superuser.9
  • On Neon, roles created in the Console, API or CLI are members of neon_superuser, which has CREATEROLE and CREATEDB.10

Use these roles to run the setup SQL once, then connect the agent as agent_reader or agent_order_writer. Their credentials never go into an MCP config file or an environment variable the agent can read. On other providers, check the documentation for what the first user can do.

7. What this guide doesn’t cover

  • Provider-specific role names and settings beyond the two above.
  • Secrets handling beyond “not in the config file”.
  • Agents that write to anything other than Postgres.
  • Prompt injection. Narrow roles limit what injected instructions can do; they don’t stop the agent from reading them.

The SQL on this page is postgres-agent-roles.sql, run by our test on PostgreSQL 16, 17 and 18 before each release of this page.

Sources

  1. 1PostgreSQL 18 documentation: GRANT · postgresql.org · checked 13 October 2026
  2. 2PostgreSQL 18 documentation: Client connection defaults · postgresql.org · checked 13 October 2026
  3. 3PostgreSQL 18 documentation: Row security policies · postgresql.org · checked 13 October 2026
  4. 4pgaudit/pgaudit, README · github.com · checked 13 October 2026
  5. 5Claude Code documentation: Connect Claude Code to tools via MCP · code.claude.com · checked 13 October 2026
  6. 6Cursor documentation: Model Context Protocol · cursor.com · checked 13 October 2026
  7. 7Datadog Security Labs: SQL injection in the Postgres MCP server · securitylabs.datadoghq.com · checked 13 October 2026
  8. 8PostgreSQL 18 documentation: Privileges · postgresql.org · checked 13 October 2026
  9. 9Amazon RDS for PostgreSQL documentation: Understanding PostgreSQL roles and permissions · docs.aws.amazon.com · checked 1 October 2026
  10. 10Neon documentation: Manage roles · neon.com · checked 1 October 2026

Last checked 13 October 2026. We re-check this guide every month. If something is out of date, write to hello@deedglass.com.

Try it on your own setup

npx deedglass scan

Grades only your own setup. Nothing is uploaded. The package opens with the public release.

Pricing