Skip to content

mcp-memgraph: harden read-only mode - #623

Closed
katarinasupe wants to merge 6 commits into
mainfrom
fix/mcp-read-only
Closed

katarinasupe wants to merge 6 commits into
mainfrom
fix/mcp-read-only

Conversation

@katarinasupe

@katarinasupe katarinasupe commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Read-only mode (the default) only blocked queries containing CREATE, MERGE, DELETE, REMOVE, SET or DROP. Writes without those words ran: write procedures (schema.assert, refactor.*), admin commands (ANALYZE GRAPH, FREE MEMORY, STORAGE MODE), and procedures flagged as reads that write in their own transaction or touch files and code (do.*, periodic.*, mg.create_module_file + mg.load, csv_utils.*). Reads like RETURN 'set' were refused.

Now, in layers:

  • The query text is checked for write keywords outside strings and comments (plus auth commands) and for procedures that can write despite being flagged as reads, including their APOC names.
  • The rest runs in a read transaction, where Memgraph rejects writes.
  • Outside a transaction, only SHOW ..., DATA DIRECTORY LOCK STATUS and PROFILE of a read query run.
  • Leading comments are stripped (a comment before EXPLAIN/PROFILE crashes Memgraph 3.14).

This is still a best-effort guard: the text check is not a Cypher parser, and a custom module flagged as read-only can do anything. The README says so. A sturdier version would check Memgraph's EXPLAIN plan instead of the query text. Known refusals: EXPLAIN of a write query, SET SESSION TRACE.

Tests compare database state before and after each case (25 blocked, 15 reads) against memgraph-mage:3.14.0, plus 12 reads main allowed that must pass the text check, and one test that talks to the server process over stdio; the full suite passes. Every read-flagged procedure and function in that image was checked: the only ones refused that main allowed change something (run other queries, write files, load code, change a shared model).

…d list

In read-only mode, queries now run in a read transaction, so Memgraph
rejects anything that writes, including write procedures. Queries that
can't run in a transaction are allowed only for SHOW and PROFILE of a
read query. Reads that only mention a write keyword are no longer
refused.
@katarinasupe
katarinasupe marked this pull request as draft October 28, 2026 14:47
A read transaction alone lets through procedures Memgraph flags as reads
that write through their own transaction or connection (do.*,
periodic.*), code and file procedures, and auth commands. Refuse those
and write keywords outside strings and comments before the query runs,
strip leading comments (some Memgraph versions crash on a comment before
EXPLAIN/PROFILE), and allow DATA DIRECTORY LOCK STATUS. Tests now check
that blocked queries leave the database unchanged. The README says read-only
mode is best effort.
Match procedures that can write anywhere in the query and under their APOC
names, unquote backticked names in the same pass as strings and comments,
treat U+180E and zero-width characters as whitespace, and add CLEAR, model
training and outbound-request procedures. Keywords after a dot or used as a
map key no longer count, so map.merge and n.set are allowed. The README
says the text check is not a parser and can be bypassed.
@katarinasupe katarinasupe changed the title mcp-memgraph: let Memgraph enforce read-only mode instead of a keyword list mcp-memgraph: harden read-only mode Oct 28, 2026
@katarinasupe
katarinasupe marked this pull request as ready for review October 28, 2026 15:21
Block only the procedures that change something (run other queries, write
files, load code, change a shared model, write to Elasticsearch), not
whole modules, so xml_module.parse, tgn.get, json_util.load_from_url,
llm.complete and Elasticsearch search run again. Require CALL before a
procedure name and match GRANT, REVOKE, DENY and CLEAR only at the start
of a query, so variables and labels with those names aren't refused.
Start the server as its own process with MCP_READ_ONLY=true, call
run_cypher_query through an MCP client, and check that writes are refused
without changing the database while reads still return rows.
@katarinasupe

Copy link
Copy Markdown
Contributor Author

Not a good solution.

@katarinasupe
katarinasupe deleted the fix/mcp-read-only branch October 30, 2026 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants