Skip to content

aql_validator

langroid/agent/special/arangodb/aql_validator.py

Security validation for LLM-generated AQL queries (GHSA-2pq5-3q89-j7cc).

ArangoChatAgent executes AQL produced by an LLM, whose text is influenceable by prompt injection -- directly, or indirectly via content the agent reads back through RAG. bind_vars only parameterizes values, so the statement text is the raw LLM string: unrestricted, an attacker who can influence the prompt can read or destroy all graph data and, where user-defined AQL functions (UDFs), Foxx, or javascript.allow-admin-execute are enabled on the database role, escalate toward RCE. This mirrors the prompt-to-SQL-to-RCE issue fixed for SQLChatAgent in CVE-2026-25879 and the sibling Cypher issue in Neo4jChatAgent.

Unless the operator opts in with allow_dangerous_operations=True:

  • the retrieval (read) path is restricted to read-only AQL -- any data-modification operation (INSERT/UPDATE/REPLACE/REMOVE/UPSERT) is rejected;
  • both paths reject user-defined-function calls (namespace::func), which can run server-side JavaScript.

The checks are keyword/pattern based, applied after stripping comments and string/identifier literals to reduce false positives. A blocklist is inherently bypassable; the real guarantee is the default-off gate plus running the agent against a least-privilege ArangoDB role. Treat this as defense in depth, not a parser-grade allowlist.

validate_aql_query(query, *, is_write, allow_dangerous)

Check an LLM-generated AQL query against the agent's safety policy.

Parameters:

Name Type Description Default
query str

The raw AQL string from the LLM.

required
is_write bool

True for the creation/write path (data-modification operations are permitted), False for the retrieval/read path (modifications are rejected so the path is read-only).

required
allow_dangerous bool

When True, skip all checks (operator opt-in).

required

Returns:

Type Description
Optional[str]

None if the query may be executed, otherwise a human-readable rejection

Optional[str]

message to relay back to the LLM.

Source code in langroid/agent/special/arangodb/aql_validator.py
def validate_aql_query(
    query: str, *, is_write: bool, allow_dangerous: bool
) -> Optional[str]:
    """Check an LLM-generated AQL query against the agent's safety policy.

    Args:
        query: The raw AQL string from the LLM.
        is_write: True for the creation/write path (data-modification
            operations are permitted), False for the retrieval/read path
            (modifications are rejected so the path is read-only).
        allow_dangerous: When True, skip all checks (operator opt-in).

    Returns:
        None if the query may be executed, otherwise a human-readable rejection
        message to relay back to the LLM.
    """
    if allow_dangerous:
        return None

    scrubbed = _strip_literals_and_comments(query)

    for pat, label in _DANGEROUS_PATTERNS:
        if pat.search(scrubbed):
            logger.warning("ArangoChatAgent rejected AQL using %s: %r", label, query)
            return (
                f"AQL query REJECTED for safety: it uses {label}, which can "
                f"execute server-side code. Rewrite the query without it, or "
                f"{_CONFIG_HINT}."
            )

    if not is_write:
        for pat, label in _WRITE_PATTERNS:
            if pat.search(scrubbed):
                logger.warning(
                    "ArangoChatAgent rejected write op %s on read path: %r",
                    label,
                    query,
                )
                return (
                    f"AQL query REJECTED for safety: the retrieval tool runs "
                    f"READ-ONLY AQL, but this query uses `{label}`. Use the "
                    f"creation tool for writes, rewrite this as a read-only "
                    f"(FOR ... RETURN) query, or {_CONFIG_HINT}."
                )

    return None