aggregation_query in the Neo4j client is careful about labels and property keys — it runs them through _sanitize_label / _sanitize_property_key — but then drops that discipline for the where and order_by fragments, splicing them into the query string verbatim.
aag/computing_engine/graph_query/graph_query.py:925:
where_clause = f"WHERE {where}" if where else ""
limit_clause = "LIMIT $limit" if limit is not None else ""
if limit is not None:
params["limit"] = limit
cypher = f"""
MATCH (n{label_pattern})
{where_clause}
RETURN n.`{prop}` AS {prop}, {agg_expr}
ORDER BY {order_by or agg_alias} {order_direction}
{limit_clause}
"""
Note prop is sanitized (_sanitize_property_key) and limit is parameterized, but where and order_by are trusted raw Cypher. These values originate from the natural-language query engine, i.e. they are derived from LLM output over user text — exactly the untrusted surface the sanitizers elsewhere exist to defend.
Failure scenario. The parameter extractor produces order_by = "total DESC RETURN n } WITH n MATCH (x) DETACH DELETE x //" (or a comparable where payload like 1=1 DETACH DELETE n //). Because it is concatenated directly, the resulting query breaks out of the intended read and performs a destructive write, or exfiltrates unrelated nodes. Even without malice, an order_by containing a bare property name with a space or backtick produces a syntax error that aborts the query.
Fix. Whitelist order_by against the known return aliases / sanitized property keys (reuse _sanitize_property_key), constrain order_direction to {ASC, DESC}, and build where from structured (key, op, value) triples with parameterized values instead of accepting a raw clause. The same raw-where/order_by pattern recurs in the sibling grouping branch a few dozen lines down and should get the same treatment.
aggregation_queryin the Neo4j client is careful about labels and property keys — it runs them through_sanitize_label/_sanitize_property_key— but then drops that discipline for thewhereandorder_byfragments, splicing them into the query string verbatim.aag/computing_engine/graph_query/graph_query.py:925:Note
propis sanitized (_sanitize_property_key) andlimitis parameterized, butwhereandorder_byare trusted raw Cypher. These values originate from the natural-language query engine, i.e. they are derived from LLM output over user text — exactly the untrusted surface the sanitizers elsewhere exist to defend.Failure scenario. The parameter extractor produces
order_by = "total DESC RETURN n } WITH n MATCH (x) DETACH DELETE x //"(or a comparablewherepayload like1=1 DETACH DELETE n //). Because it is concatenated directly, the resulting query breaks out of the intended read and performs a destructive write, or exfiltrates unrelated nodes. Even without malice, anorder_bycontaining a bare property name with a space or backtick produces a syntax error that aborts the query.Fix. Whitelist
order_byagainst the known return aliases / sanitized property keys (reuse_sanitize_property_key), constrainorder_directionto{ASC, DESC}, and buildwherefrom structured(key, op, value)triples with parameterized values instead of accepting a raw clause. The same raw-where/order_bypattern recurs in the sibling grouping branch a few dozen lines down and should get the same treatment.