Skip to content

Safe mode

Safe mode is on by default. With it on, anything that is not read-only SQL is refused before it reaches your database.

This page is about what “refused” actually means, because the guarantee is stronger for some databases than others and pretending otherwise would be the wrong kind of reassurance.

What counts as read-only

A statement is read-only when it starts with SELECT, SHOW, DESCRIBE, EXPLAIN, or WITH, and contains no write keyword outside a string or comment.

The write keywords are ALTER, CALL, COPY, CREATE, DELETE, DROP, EXEC, EXECUTE, GRANT, INSERT, INSTALL, LOAD, MERGE, REPLACE, REVOKE, SET, TRUNCATE, UPDATE, and VACUUM.

Two more rules matter:

  • Multiple statements in one call are refused for agents. One call, one statement.
  • An unknown SQL dialect is refused, not guessed at. Grain fails closed.

The classifier tokenises per dialect rather than pattern-matching the raw string, so a DROP inside a string literal or a comment is not mistaken for a write — and a write hidden behind dialect-specific quoting is not mistaken for a comment. MySQL backslash escapes, PostgreSQL dollar-quoting and nested block comments, # line comments in MySQL and BigQuery: each dialect gets its own lexer settings.

Agents and humans are treated differently

Agent You
Read-only statement runs runs
Write statement refused runs after you confirm
Several statements at once refused runs after you confirm
Unknown dialect refused runs after you confirm

An agent gets a hard refusal. You get a confirmation prompt, because it is your database and you are the one who typed the statement.

Enforced versus advised

Classification is one boundary. Some connectors have a second one — a session or engine-level guarantee that holds even if the classifier were wrong.

Connector Safe mode is
PostgreSQL enforced — the session sets default_transaction_read_only, so the engine itself rejects a write
SQLite enforced — the file is loaded into memory and never written back, so a write cannot persist
MySQL, SQL Server, DuckDB, Trino, Snowflake, BigQuery, Elasticsearch advised — classification is the only boundary

Both are on by default and both refuse the same statements. The difference is what happens in the case where the classifier is wrong: on an enforced connector nothing happens, because the database itself declines. On an advised one, there is no second net.

If that distinction matters for a particular database, the usual answer is not to turn safe mode off but to connect as a role that has no write grants. A permission your credentials do not have cannot be misclassified.

Beyond SQL

Safe mode is about statements. Tool permissions are a separate limit, and they apply whether or not safe mode is on.

An agent attached through the packaged bridge cannot change your saved connections and cannot ask for your stored credentials. Those tools are still advertised and still answer, with an explicit refusal naming the profile and the reason — so an agent learns “not allowed here” rather than concluding the capability does not exist and trying something more creative.

Turning it off

Set grain.mcpSafeMode to false in settings, or GRAIN_MCP_SAFE_MODE=false in the MCP server’s environment.

Do this when you have a reason to — a migration you want an agent to run, a scratch database, a local DuckDB file. It is a real setting, not a warning label. It is on by default because the common case is an agent exploring a database it should not be able to change.

Documentation