From 1a3833670581d0836ff9acb75eb8a3273bc5b0a1 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Wed, 7 Oct 2026 18:12:47 +0100 Subject: [PATCH 1/2] Document that SQLToolset does not check functions without allowed_tables Without allowed_tables, a SELECT can still call side-effecting functions such as nextval or pg_terminate_backend. Recommend a least-privilege role. --- providers/common/ai/docs/agent_security.rst | 6 +++++- providers/common/ai/docs/toolsets/sql.rst | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/providers/common/ai/docs/agent_security.rst b/providers/common/ai/docs/agent_security.rst index 08a6fbd96a55d..b0914980739a8 100644 --- a/providers/common/ai/docs/agent_security.rst +++ b/providers/common/ai/docs/agent_security.rst @@ -112,7 +112,11 @@ No single layer is sufficient on its own. They work together. (``DESCRIBE``/``SHOW``) statements pass; INSERT, UPDATE, DELETE, DROP, and writes hidden behind ``EXPLAIN`` are rejected. - Does not prevent the agent from reading sensitive data that the - database user has SELECT access to. + database user has SELECT access to. Without ``allowed_tables`` the check is + statement-level only: a side-effecting function inside a SELECT + (``nextval``, ``pg_terminate_backend``, ``dblink_exec``) is not blocked. Use a + least-privilege role, or a read-only transaction where the database supports + one. * - **DataFusionToolset: read-only by default** - ``allow_writes=False`` (default) validates every SQL query through ``validate_sql()`` and rejects CREATE TABLE, CREATE VIEW, INSERT diff --git a/providers/common/ai/docs/toolsets/sql.rst b/providers/common/ai/docs/toolsets/sql.rst index 51a8d60207131..69a9ac4e4503c 100644 --- a/providers/common/ai/docs/toolsets/sql.rst +++ b/providers/common/ai/docs/toolsets/sql.rst @@ -65,6 +65,15 @@ rejects by scanning the parsed statement for write operations. When table, so its target must be on the list, while ``SHOW`` enumerates objects beyond any single table and is rejected outright (see :ref:`allowed-tables-enforcement`). +The read-only check inspects statement types, not what a function does. By default +(no ``allowed_tables``) a function call inside a ``SELECT`` is not examined, so +``SELECT nextval('seq')``, ``SELECT pg_terminate_backend(pid)`` or +``SELECT dblink_exec(...)`` pass validation and run with the connection's +privileges. Setting ``allowed_tables`` turns on the function check: every function +sqlglot cannot type is rejected unless it is listed in ``allowed_functions``. Either +way, point the connection at a least-privilege role, and where the database supports +it a read-only transaction or session, rather than relying on validation alone. + .. _sql-toolset-restricted: Restricting the agent From 938b6423f54b96d3f2cfc6112e9c5a0230c55a69 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Thu, 8 Oct 2026 19:19:25 +0100 Subject: [PATCH 2/2] Clarify SQLToolset read-only guidance and allowed_tables function check - Recommend a least-privilege role together with a read-only role/session default, and note that a transaction cannot be set from the Dag. - State that the allowed_tables function check rejects only functions sqlglot cannot type, so typed syntax such as T-SQL NEXT VALUE FOR or Snowflake seq.nextval still passes. --- providers/common/ai/docs/agent_security.rst | 12 +++++++++--- providers/common/ai/docs/toolsets/sql.rst | 13 ++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/providers/common/ai/docs/agent_security.rst b/providers/common/ai/docs/agent_security.rst index b0914980739a8..d5e1a07ae220e 100644 --- a/providers/common/ai/docs/agent_security.rst +++ b/providers/common/ai/docs/agent_security.rst @@ -114,9 +114,15 @@ No single layer is sufficient on its own. They work together. - Does not prevent the agent from reading sensitive data that the database user has SELECT access to. Without ``allowed_tables`` the check is statement-level only: a side-effecting function inside a SELECT - (``nextval``, ``pg_terminate_backend``, ``dblink_exec``) is not blocked. Use a - least-privilege role, or a read-only transaction where the database supports - one. + (``nextval``, ``pg_terminate_backend``, ``dblink_exec``) is not blocked. + Setting ``allowed_tables`` adds a function check, but it rejects only functions + sqlglot cannot type; typed syntax such as T-SQL ``NEXT VALUE FOR`` or Snowflake + ``seq.nextval`` still passes. Use a least-privilege role and, where the database + supports it, a read-only default for that role or session (for example + ``ALTER ROLE SET default_transaction_read_only = on`` on PostgreSQL). + The toolset opens its own connection per call, so you cannot set a transaction + from the Dag, and a read-only setting is defense in depth: it does not stop every + side-effecting function. * - **DataFusionToolset: read-only by default** - ``allow_writes=False`` (default) validates every SQL query through ``validate_sql()`` and rejects CREATE TABLE, CREATE VIEW, INSERT diff --git a/providers/common/ai/docs/toolsets/sql.rst b/providers/common/ai/docs/toolsets/sql.rst index 69a9ac4e4503c..9b98ea747e9e9 100644 --- a/providers/common/ai/docs/toolsets/sql.rst +++ b/providers/common/ai/docs/toolsets/sql.rst @@ -70,9 +70,16 @@ The read-only check inspects statement types, not what a function does. By defau ``SELECT nextval('seq')``, ``SELECT pg_terminate_backend(pid)`` or ``SELECT dblink_exec(...)`` pass validation and run with the connection's privileges. Setting ``allowed_tables`` turns on the function check: every function -sqlglot cannot type is rejected unless it is listed in ``allowed_functions``. Either -way, point the connection at a least-privilege role, and where the database supports -it a read-only transaction or session, rather than relying on validation alone. +sqlglot cannot type is rejected unless it is listed in ``allowed_functions``. The +check rejects only functions sqlglot cannot type; typed syntax such as T-SQL +``NEXT VALUE FOR seq`` or Snowflake ``seq.nextval`` still passes, so +``allowed_tables`` is not a guarantee against side-effecting functions. Either way, +point the connection at a least-privilege role and, where the database supports it, +a read-only default for that role or session (for example +``ALTER ROLE SET default_transaction_read_only = on`` on PostgreSQL). The +toolset opens its own connection per call, so you cannot set a transaction from the +Dag, and a read-only setting is defense in depth: it does not stop every +side-effecting function. .. _sql-toolset-restricted: