{"record":{"id":"3ee5d2c8562e92b5","repo":"crewAIInc/crewAI","slug":"nl2sqltool-blocked-an-unrecognised-sql-command-c","errorCode":null,"errorMessage":"NL2SQLTool blocked an unrecognised SQL command '{command}'. Only {sorted(_READ_ONLY_COMMANDS)} are allowed in read-only mode.","messagePattern":"NL2SQLTool blocked an unrecognised SQL command '(.+?)'\\. Only (.+?) are allowed in read-only mode\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py","lineNumber":386,"sourceCode":"                            f\"in read-only mode.\"\n                        )\n            return\n\n        if command in _WRITE_COMMANDS:\n            if not self.allow_dml:\n                raise ValueError(\n                    f\"NL2SQLTool is configured in read-only mode and blocked a \"\n                    f\"'{command}' statement. To allow write operations set \"\n                    f\"allow_dml=True or CREWAI_NL2SQL_ALLOW_DML=true.\"\n                )\n            logger.warning(\n                \"NL2SQLTool: executing write statement '%s' because allow_dml=True.\",\n                command,\n            )\n        elif command not in _READ_ONLY_COMMANDS:\n            # Unknown command — block by default unless DML is explicitly enabled\n            if not self.allow_dml:\n                raise ValueError(\n                    f\"NL2SQLTool blocked an unrecognised SQL command '{command}'. \"\n                    f\"Only {sorted(_READ_ONLY_COMMANDS)} are allowed in read-only \"\n                    f\"mode.\"\n                )\n\n    @staticmethod\n    def _extract_command(sql_query: str) -> str:\n        \"\"\"Return the uppercased first keyword of *sql_query*.\"\"\"\n        stripped = sql_query.strip().lstrip(\"(\")\n        first_token = stripped.split()[0] if stripped.split() else \"\"\n        return first_token.upper().rstrip(\";\")\n\n    # Schema introspection helpers\n\n    def _fetch_available_tables(self) -> list[dict[str, Any]] | str:\n        return self.execute_sql(\n            \"SELECT table_name FROM information_schema.tables \"\n            \"WHERE table_schema = 'public';\"","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py#L368-L404","documentation":"Deny-by-default allowlist for unknown commands: if a statement's first keyword is neither in _WRITE_COMMANDS nor _READ_ONLY_COMMANDS (SELECT, SHOW, DESCRIBE, EXPLAIN), read-only mode raises ValueError listing the allowed set. This catches dialect utilities (SET, PRAGMA, VACUUM, CALL, GRANT, ...) and typos that would otherwise slip past a write-only blocklist.","triggerScenarios":"Running 'SET timezone=UTC', 'PRAGMA table_info(t)', 'CALL proc()', 'USE mydb', or a typo like 'SELEC * FROM t' with allow_dml=False.","commonSituations":"LLM emits session/dialect commands the allowlist does not know; SQLite PRAGMAs; MySQL USE statements; keyword typos from the model.","solutions":["Check the first keyword of your SQL; correct typos or rephrase to an allowed read statement (SELECT/SHOW/DESCRIBE/EXPLAIN).","Run session or admin commands through a direct SQLAlchemy connection, not NL2SQLTool.","Set allow_dml=True only if you understand it disables all read-only guarding, including this allowlist."],"exampleFix":"# before (blocked: 'SELEC' typo)\ntool._run(\"SELEC * FROM users\")\n\n# after\ntool._run(\"SELECT * FROM users\")","handlingStrategy":"validation","validationCode":"READ_ONLY = {\"SELECT\", \"SHOW\", \"DESCRIBE\", \"EXPLAIN\"}\nWRITE = {\"INSERT\", \"UPDATE\", \"DELETE\", \"DROP\", \"ALTER\", \"CREATE\", \"TRUNCATE\", \"MERGE\"}\n\ndef classify(sql: str) -> str:\n    kw = sql.strip().lstrip(\"(\").split()[0].upper().rstrip(\";\") if sql.strip() else \"\"\n    return \"read\" if kw in READ_ONLY else \"write\" if kw in WRITE else \"unknown\"\n\nif not tool.allow_dml and classify(sql) == \"unknown\":\n    raise ValueError(f\"unknown command will be blocked: {sql[:40]}\")","typeGuard":"def is_known_sql_command(sql: str) -> bool:\n    kw = sql.strip().lstrip(\"(\").split()[0].upper().rstrip(\";\") if sql.strip() else \"\"\n    return kw in {\"SELECT\", \"SHOW\", \"DESCRIBE\", \"EXPLAIN\"} | {\"INSERT\", \"UPDATE\", \"DELETE\", \"DROP\", \"ALTER\", \"CREATE\", \"TRUNCATE\", \"MERGE\"}","tryCatchPattern":"try:\n    tool._run(sql)\nexcept ValueError as e:\n    if \"unrecognised SQL command\" in str(e):\n        sql = repair_keyword_typo(sql)  # e.g. SELEC -> SELECT\n        tool._run(sql)","preventionTips":["Allowlist-check the first keyword before submitting agent-generated SQL.","Route SET/PRAGMA/CALL and other utilities through a direct SQLAlchemy connection.","Add few-shot examples of valid SELECT queries to the agent prompt."],"tags":["nl2sql","allowlist","read-only","validation","unknown-command"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}