Every read the agent or an MCP client makes is a safe read, and never asks. A safe read runs one of two ways, set per connection in Connection Settings › Safe reads (“Safe reads run as”):
- Your user. Reads run as your own user, in
READ ONLYtransactions, and saferow’s statement classifier stops writes. - A SELECT-only user. Reads run as a user the database itself holds to reading. Nothing can talk its way around it: not a clever prompt, not a stored procedure, not a second statement. This is the stronger one.
The status bar says which: “Safe reads: your user” or “Safe reads: a SELECT-only user”.
Making a SELECT-only user
Press Make a SELECT-only user for me. saferow writes the SQL, and you approve each statement, one at a time, running as your own user. It makes a strong password and puts it straight into your Keychain; the statements say :password, and saferow fills it in only as they run. On MySQL (the connection’s database must be set first):
CREATE USER 'saferow_ro'@'%' IDENTIFIED BY :password
GRANT SELECT, SHOW VIEW ON `shop`.* TO 'saferow_ro'@'%'
On Postgres, for each schema that holds tables:
CREATE ROLE "saferow_ro" LOGIN PASSWORD :password
GRANT CONNECT ON DATABASE "shop" TO "saferow_ro"
GRANT USAGE ON SCHEMA "public" TO "saferow_ro"
GRANT SELECT ON ALL TABLES IN SCHEMA "public" TO "saferow_ro"
ALTER DEFAULT PRIVILEGES IN SCHEMA "public" GRANT SELECT ON TABLES TO "saferow_ro"
ALTER ROLE "saferow_ro" SET default_transaction_read_only = on
Check it any time with SHOW GRANTS FOR 'saferow_ro'@'%' on MySQL, or \du and \dp in psql. saferow checks the user’s grants each time it connects.
When the Safe reads step fails
- “The SELECT-only user’s password was refused” or “…has no password in the Keychain.” Press Fix the SELECT-only user and set the password, or make the user again.
- “There’s no user called saferow_ro on this server.” The user was dropped, or the name is different. Fix it in Safe reads.
- “The SELECT-only user saferow_ro can also: INSERT, UPDATE.” A warning, not a failure: the user has more than reading. Take those grants away, or saferow’s promise rests on its kernel alone.
On SQLite, saferow opens the file read-only for reading, behind an authorizer that allows only reads, so no user is needed.
Agent access
The last step says what the agent and saferow mcp may do on this connection:
- Off: they can’t see it.
- Reads only: they read, as safe reads, and can’t propose changes; the kernel refuses them.
- Reads, and proposes changes you approve: every change is counted and held for you. Agent changes are part of Pro.
Run the self-test from Settings › Safety (“Verify, don’t trust”) to see which guard stops each known escape on your own database.