Files
crewAI/lib/crewai-tools/tests/tools/test_nl2sql_security.py
Rip&Tear 7f6367a21c fix(tools): close NL2SQL read-only mode bypasses
NL2SQLTool's read-only mode could be bypassed three ways, all confirmed by
executing the validators directly.

1. `_AS_PAREN_RE` was `\bAS\s*\(`, which never matches PostgreSQL's
   `AS [NOT] MATERIALIZED (`. `WITH d AS MATERIALIZED (DELETE FROM users
   RETURNING *) SELECT * FROM d` therefore parsed as having no CTE body at
   all, and `_validate_statement` returned without running a single check.

2. `_resolve_explain_command` scanned raw text, so a comment between the
   keywords (`EXPLAIN /*x*/ ANALYZE DELETE FROM users`) stalled option
   parsing and the statement was treated as an inert EXPLAIN. EXPLAIN
   ANALYZE executes its argument.

3. The first-keyword allowlist admits statements that begin with SELECT but
   write, and those survive a transaction rollback: MySQL
   `SELECT ... INTO OUTFILE` writes a file on the DB server, and
   `pg_read_file` / `lo_import` / `dblink_exec` reach its filesystem or open
   a connection outside the transaction.

Changes:

- Analyse statements over a mask that blanks string literals, dollar-quoted
  strings, quoted identifiers and comments while preserving offsets, so a
  keyword in a literal is never matched and one behind a comment always is.
  MySQL executable comments (`/*! ... */`) are left visible because the
  server runs them.
- Match the `AS [NOT] MATERIALIZED (` spelling.
- Validate CTE bodies against an allowlist of read-only leading keywords
  instead of a write-command denylist, and fail closed: a WITH statement
  whose CTE bodies cannot be located, or which has no query after them, is
  now rejected rather than passed through.
- Block INTO OUTFILE/DUMPFILE and known server-filesystem functions.
- Mark the transaction `SET TRANSACTION READ ONLY` in read-only mode where
  the backend supports it, so enforcement no longer rests on parsing alone.
  Backends without the syntax log and fall back.
- Split statements on semicolons outside strings and comments, which also
  stops a semicolon in a literal from being rejected as multi-statement.
- Document that a least-privileged read-only DB role is the actual control
  and these checks are defence in depth.

Adds 30 regression tests. Full file: 111 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:38:07 +08:00

30 KiB