Query validation#
Seizu validates Cypher before it is executed by the Query Console, report query execution, scheduled queries, MCP graph queries, and Cypher-backed MCP tools. The validator is implemented in reporting/services/query_validator.py.
The validator is a security guardrail for user-provided and configuration-provided Cypher. It is not a replacement for Neo4j authentication, network controls, RBAC, or careful permission assignment. Users who can run read queries can still read data available to the configured Neo4j user.
Validation flow#
Validation runs in layers:
Neo4j
EXPLAINchecks syntax and query classification. Only read-classified queries are allowed; write, read-write, schema, and DBMS operations are blocked.Seizu scans the original query, a comment-stripped query, and Cypher Unicode-decoded forms for dangerous constructs that can be read-classified by Neo4j.
Procedure calls are checked against a default allowlist of side-effect-free built-in schema procedures.
CyVer schema and property validators run after blocking checks. These produce warnings only; warnings do not prevent execution.
The regex guard currently blocks:
LOAD CSV, including comment and Unicode obfuscation variants.Neo4j administration and catalog commands such as
SHOW, database management, privilege management, and server management.USEclauses that route a query to another graph or database.Procedure calls outside the allowlist, including plugin and custom procedures.
Dangerous function namespaces that can execute dynamic Cypher, create graph-catalog state, or send data to external model providers, such as
apoc.cypher.*,gds.*,ai.*, andgenai.*.
Procedure allowlist#
The default procedure allowlist is intentionally small. It permits read-only schema introspection procedures such as db.labels, db.relationshipTypes, db.propertyKeys, and db.schema.*.
Operators can allow extra procedures with QUERY_VALIDATOR_ALLOWED_PROCEDURES. The setting is a comma-separated list:
QUERY_VALIDATOR_ALLOWED_PROCEDURES=apoc.meta.stats,custom.readOnlyProcedure,gds.
Each entry is either an exact procedure name or a namespace prefix ending in a dot. Prefixes allow matching CALL procedure invocations only. They do not permit dangerous function namespaces in the same namespace; for example, apoc. does not permit apoc.cypher.*, and gds. does not permit gds.* functions.
Use exact procedure names when possible. Namespace prefixes are broader and should only be used when every procedure in that namespace is acceptable for the Seizu deployment.
Changing validation behavior#
When changing validation, update the centralized case lists in tests/query_validator_cases.py first. The unit and live integration suites both import those cases, which keeps mocked and Neo4j-backed regression coverage aligned.
For each new risky Cypher feature or bypass attempt:
Add the query to the appropriate case family in
tests/query_validator_cases.py.Add or update targeted unit tests in
tests/unit/reporting/services/query_validator_test.pywhen the behavior depends on settings or validator internals.Add or update Hypothesis coverage in
tests/unit/reporting/fuzz/query_validator_fuzz_test.pywhen the rule can be expressed as generated permutations.Append a row to
tests/data/query-fuzzing.csvwith the technique, query, observed result, blocking layer, and notes.Update
reporting/services/query_validator.py.If you add or change a setting, update
.env.exampleand the backend configuration docs.
Prefer tests that show both sides of a rule: the dangerous form is blocked, and a nearby legitimate read-only form remains allowed. This is especially important for clause-start regexes and procedure-call parsing.
Hypothesis fuzz coverage#
tests/unit/reporting/fuzz/query_validator_fuzz_test.py uses Hypothesis to generate families of validation inputs. These tests mock Neo4j EXPLAIN as read-only, so they exercise Seizu’s own guard logic directly instead of relying on Neo4j syntax errors or query classification.
The current fuzz tests cover generated variants of:
LOAD CSVseparators and obfuscation.CALL apoc.*separators and obfuscation.SHOWadministration commands with arbitrary prefix/suffix text.USEclauses across clause-start anchors, graph reference forms, following clauses, casing, comments, newlines, and Unicode escapes.Allowed false positives near the
USEguard, such as CASE expressions using a variable nameduseand map keys nameduse.
Use Hypothesis when a validator rule has meaningful dimensions to permute. Good strategy dimensions include:
Keyword spelling: uppercase, lowercase, and Cypher Unicode escapes.
Separators: spaces, tabs, newlines, block comments, and Unicode whitespace escapes.
Clause anchors: query start,
UNION,NEXT, conditional-query branches, andCALL {}subqueries.Identifier forms: bare names, dotted names, backtick-quoted names, and names that collide with Cypher keywords.
Adjacent safe forms that should stay allowed.
Keep generated examples focused. Prefer small sampled_from(...) vocabularies that describe known Cypher shapes over unconstrained text generation. A focused strategy gives better signal and avoids spending examples on syntax that Neo4j would never accept.
When extending Hypothesis coverage:
Add reusable strategies near the top of
tests/unit/reporting/fuzz/query_validator_fuzz_test.py.Mock
EXPLAINas read-only with_validate_with_mocked_neo4j(...).Assert dangerous generated variants produce
result.has_errors.Add a paired allowed test for nearby legitimate Cypher when false positives are plausible.
Keep
max_exampleslarge enough to combine the important dimensions but small enough for the unit suite to remain fast.
Run the fuzz module directly while developing:
docker compose run --rm seizu uv run --frozen --no-sync pytest tests/unit/reporting/fuzz/query_validator_fuzz_test.py -q
If Hypothesis finds a counterexample, add the minimized query to tests/query_validator_cases.py or tests/data/query-fuzzing.csv when it represents a meaningful regression.
Validator test workflow#
Run the unit regression suite after any validator change:
docker compose run --rm seizu uv run --frozen --no-sync pytest tests/unit/reporting/services/query_validator_test.py -v
Run the generated fuzz tests when changing regex guards or keyword scanning:
docker compose run --rm seizu uv run --frozen --no-sync pytest tests/unit/reporting/fuzz/query_validator_fuzz_test.py -q
Run the live Neo4j-backed suite when the change depends on real Cypher parsing, query classification, Neo4j version behavior, or plugin behavior:
make test_query_validator_live
To compare the configured development Neo4j version with the latest supported Neo4j image, switch versions and rerun the live suite:
make neo4j_current
make down && make up
make test_query_validator_live
make neo4j_latest
make down && make up
make test_query_validator_live
The latest-version target uses a separate Neo4j data volume so the normal development database is not upgraded in place.
Before opening a pull request, run the normal project checks:
pre-commit run --all-files
make docs