{"record":{"id":"aebf364fcc92dddb","repo":"crewAIInc/crewAI","slug":"nl2sqltool-is-configured-in-read-only-mode-and-blo-aebf36","errorCode":null,"errorMessage":"NL2SQLTool is configured in read-only mode and blocked a '{command}' statement. To allow write operations set allow_dml=True or CREWAI_NL2SQL_ALLOW_DML=true.","messagePattern":"NL2SQLTool is configured in read-only mode and blocked a '(.+?)' statement\\. To allow write operations set allow_dml=True or CREWAI_NL2SQL_ALLOW_DML=true\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py","lineNumber":374,"sourceCode":"                            f\"operations set allow_dml=True or \"\n                            f\"CREWAI_NL2SQL_ALLOW_DML=true.\"\n                        )\n                    logger.warning(\n                        \"NL2SQLTool: executing '%s' after CTE because allow_dml=True.\",\n                        main_cmd,\n                    )\n                elif main_cmd not in _READ_ONLY_COMMANDS:\n                    if not self.allow_dml:\n                        raise ValueError(\n                            f\"NL2SQLTool blocked an unrecognised SQL command '{main_cmd}' \"\n                            f\"after a CTE. Only {sorted(_READ_ONLY_COMMANDS)} are allowed \"\n                            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","sourceCodeStart":356,"sourceCodeEnd":392,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py#L356-L392","documentation":"The core read-only guard: if the first keyword of a (non-CTE, non-EXPLAIN-resolved) statement is in _WRITE_COMMANDS (INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, ...) and allow_dml=False, NL2SQLTool raises ValueError telling you to set allow_dml=True or CREWAI_NL2SQL_ALLOW_DML=true. With allow_dml=True it only logs a warning and executes.","triggerScenarios":"Calling the tool with any plain write statement ('DELETE FROM users', \"UPDATE t SET x=1\", 'DROP TABLE t', 'INSERT INTO ...') in the default read-only configuration.","commonSituations":"Agents asked to 'clean up the table'; prompts that encourage DDL; shared demo tools left read-only on purpose while a script tries to seed data; forgetting that allow_dml defaults to False.","solutions":["If the write is intended: NL2SQLTool(db_uri=..., allow_dml=True) or export CREWAI_NL2SQL_ALLOW_DML=true (env var also overrides at runtime).","If not intended: fix the agent prompt to forbid writes and re-run with a SELECT.","Point writes at a separate tool instance/connection with a write-capable, low-privilege DB user instead of flipping the shared tool."],"exampleFix":"# before\ntool = NL2SQLTool(db_uri=uri)\ntool._run(\"DELETE FROM sessions WHERE expired\")  # ValueError\n\n# after\ntool = NL2SQLTool(db_uri=uri, allow_dml=True)\ntool._run(\"DELETE FROM sessions WHERE expired\")","handlingStrategy":"validation","validationCode":"WRITE_CMDS = {\"INSERT\", \"UPDATE\", \"DELETE\", \"DROP\", \"ALTER\", \"CREATE\", \"TRUNCATE\", \"MERGE\", \"REPLACE\"}\n\ndef first_keyword(sql: str) -> str:\n    return sql.strip().lstrip(\"(\").split()[0].upper().rstrip(\";\") if sql.strip() else \"\"\n\nif not tool.allow_dml and first_keyword(sql) in WRITE_CMDS:\n    raise PermissionError(f\"write statement {first_keyword(sql)} not permitted\")","typeGuard":"def is_read_only_sql(sql: str) -> bool:\n    kw = sql.strip().lstrip(\"(\").split()[0].upper().rstrip(\";\") if sql.strip() else \"\"\n    return kw in {\"SELECT\", \"SHOW\", \"DESCRIBE\", \"EXPLAIN\"}","tryCatchPattern":"try:\n    tool._run(sql)\nexcept ValueError as e:\n    if \"read-only mode\" in str(e):\n        if is_authorized_write(sql):\n            result = write_tool._run(sql)  # allow_dml=True instance\n        else:\n            raise","preventionTips":["Validate the leading SQL keyword against the allowlist before calling the tool.","Use a dedicated write tool backed by a low-privilege DB user instead of flipping allow_dml.","Set agent prompts to generate SELECT-only queries for read-only tools."],"tags":["nl2sql","read-only","write-operations","security","validation"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}