{"record":{"id":"f9bf9b6d984710e2","repo":"crewAIInc/crewAI","slug":"security-alert-invalid-database-identifier-detect","errorCode":null,"errorMessage":"Security Alert: Invalid database identifier detected: {name}","messagePattern":"Security Alert: Invalid database identifier detected: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/tools/db2_search_tool/db2_search_tool.py","lineNumber":206,"sourceCode":"        finally:\n            self.connection = None\n            self.dbi_connection = None\n            self.cursor = None\n\n    def _validate_identifier(self, name: str, allow_period: bool = False) -> str:\n        \"\"\"\n        Validates table and column names to prevent SQL injection.\n        Simple identifiers must start with a letter and contain only letters, digits,\n        or underscores. Schema-qualified names (allow_period=True) allow exactly one\n        period separating two valid simple identifiers (e.g. myschema.mytable).\n        \"\"\"\n        pattern = (\n            r\"^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)?$\"\n            if allow_period\n            else r\"^[A-Za-z][A-Za-z0-9_]*$\"\n        )\n        if not re.match(pattern, name):\n            raise ValueError(\n                f\"Security Alert: Invalid database identifier detected: {name}\"\n            )\n        return name\n\n    def _get_openai_client(self) -> Any:\n        if self._openai_client is None:\n            api_key = os.getenv(\"OPENAI_API_KEY\")\n            if not api_key:\n                raise ValueError(\n                    \"OPENAI_API_KEY environment variable is missing. Required for default embeddings.\"\n                )\n            openai = importlib.import_module(\"openai\")\n            self._openai_client = openai.OpenAI(api_key=api_key)\n        return self._openai_client\n\n    def _generate_embedding(self, text: str) -> list[float]:\n        if self.custom_embedding_fn:\n            return self.custom_embedding_fn(text)","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/tools/db2_search_tool/db2_search_tool.py#L188-L224","documentation":"DB2VectorSearchTool._validate_identifier guards against SQL injection by regex-matching table and column names: identifiers must start with a letter and contain only letters, digits, or underscores (optionally one period between two such identifiers when allow_period=True, for schema.table). Any name failing the pattern raises ValueError with a 'Security Alert' prefix. This applies to table_name, vector_column, every entry of return_columns, and filter_by — not to user data, only to identifiers embedded in SQL.","triggerScenarios":"table_name='my-schema.my table', vector_column='vec-col', return_columns=['select'], or filter_by='dept; DROP TABLE x' — any identifier containing hyphens, spaces, quotes, semicolons, digits at the start, or non-ASCII. Also names quoted with backticks or double quotes.","commonSituations":"DB2 schemas/tables created with special characters that were quoted at DDL time; LLM-generated tool configuration echoing prose instead of identifiers; attempts to pass expressions ('COUNT(*)') or aliases where a bare column is required.","solutions":["Use plain identifiers: letters, digits, underscores, starting with a letter (e.g. 'MYSCHEMA.MYTABLE' for schema-qualified tables).","If the real DB2 name contains special characters, create a view or alias with a compliant name and point the tool at it.","Never pass SQL fragments, expressions, or quoted identifiers — only bare names.","Validate names with the same regex in your config loading to fail before tool construction."],"exampleFix":"# before\ntool = DB2VectorSearchTool(table_name='my-schema.\"my table\"', vector_column='vec-col', return_columns=['id'])\n\n# after\ntool = DB2VectorSearchTool(table_name='my_schema.my_table', vector_column='vec_col', return_columns=['id'])","handlingStrategy":"validation","validationCode":"import re\n\nIDENT = re.compile(r'^[A-Za-z][A-Za-z0-9_]*$')\nIDENT_DOTTED = re.compile(r'^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)?$')\n\ndef valid_identifiers(table: str, vector_column: str, columns: list[str]) -> bool:\n    return bool(IDENT_DOTTED.match(table) and IDENT.match(vector_column)\n                and all(IDENT.match(c) for c in columns))","typeGuard":"def is_simple_identifier(name: object) -> bool:\n    return isinstance(name, str) and bool(re.match(r'^[A-Za-z][A-Za-z0-9_]*$', name))","tryCatchPattern":"try:\n    tool = DB2VectorSearchTool(table_name=t, vector_column=v, return_columns=cols)\nexcept ValueError as e:\n    if 'Invalid database identifier' in str(e):\n        raise ConfigError(f'rename or create a view for {t!r}; identifiers must be [A-Za-z_][A-Za-z0-9_]*(.name)?') from e\n    raise","preventionTips":["Create DB2 objects with unquoted, underscore-only names from the start.","Where DDL forced special characters, add a view with a compliant name and query that.","Never accept identifiers from LLM output without running them through the same regex first."],"tags":["db2","sql-injection","security","identifier-validation","regex"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}